Repository: dotnet/csharplang Branch: main Commit: 246d64dfbb2f Files: 863 Total size: 5.9 MB Directory structure: gitextract_dhtuocpa/ ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ └── ISSUE_TEMPLATE/ │ ├── config.yml │ ├── docs-feedback.yml │ └── proposal_template.md ├── .gitignore ├── CODE-OF-CONDUCT.md ├── Communities.md ├── Design-Process.md ├── Language-Version-History.md ├── README.md ├── meetings/ │ ├── 2013/ │ │ ├── LDM-2013-10-07.md │ │ ├── LDM-2013-10-21.md │ │ ├── LDM-2013-11-04.md │ │ ├── LDM-2013-12-16.md │ │ └── README.md │ ├── 2014/ │ │ ├── LDM-2014-01-06.md │ │ ├── LDM-2014-02-03.md │ │ ├── LDM-2014-02-10.md │ │ ├── LDM-2014-04-21.md │ │ ├── LDM-2014-05-07.md │ │ ├── LDM-2014-05-21.md │ │ ├── LDM-2014-07-09.md │ │ ├── LDM-2014-08-27.md │ │ ├── LDM-2014-09-03.md │ │ ├── LDM-2014-10-01.md │ │ ├── LDM-2014-10-15.md │ │ └── README.md │ ├── 2015/ │ │ ├── LDM-2015-01-21.md │ │ ├── LDM-2015-01-28.md │ │ ├── LDM-2015-02-04.md │ │ ├── LDM-2015-02-11.md │ │ ├── LDM-2015-03-04.md │ │ ├── LDM-2015-03-10-17.md │ │ ├── LDM-2015-03-18.md │ │ ├── LDM-2015-03-24.md │ │ ├── LDM-2015-03-25-Design-Review.md │ │ ├── LDM-2015-03-25-Notes.md │ │ ├── LDM-2015-04-01-08.md │ │ ├── LDM-2015-04-14.md │ │ ├── LDM-2015-04-15.md │ │ ├── LDM-2015-04-22-Design-Review.md │ │ ├── LDM-2015-05-20.md │ │ ├── LDM-2015-05-25.md │ │ ├── LDM-2015-07-01.md │ │ ├── LDM-2015-07-07.md │ │ ├── LDM-2015-08-18.md │ │ ├── LDM-2015-09-01.md │ │ ├── LDM-2015-09-02.md │ │ ├── LDM-2015-09-08.md │ │ ├── LDM-2015-10-07-Design-Review.md │ │ ├── LDM-2015-11-02-Design-Demo.md │ │ └── README.md │ ├── 2016/ │ │ ├── LDM-2016-02-29.md │ │ ├── LDM-2016-04-06.md │ │ ├── LDM-2016-04-12-22.md │ │ ├── LDM-2016-05-03-04.md │ │ ├── LDM-2016-05-10.md │ │ ├── LDM-2016-07-12.md │ │ ├── LDM-2016-07-13.md │ │ ├── LDM-2016-07-15.md │ │ ├── LDM-2016-08-24.md │ │ ├── LDM-2016-09-06.md │ │ ├── LDM-2016-10-18.md │ │ ├── LDM-2016-10-25-26.md │ │ ├── LDM-2016-11-01.md │ │ ├── LDM-2016-11-15.md │ │ ├── LDM-2016-11-16.md │ │ ├── LDM-2016-11-30.md │ │ ├── LDM-2016-12-07-14.md │ │ └── README.md │ ├── 2017/ │ │ ├── CLR-2017-03-23.md │ │ ├── LDM-2017-01-10.md │ │ ├── LDM-2017-01-11.md │ │ ├── LDM-2017-01-17.md │ │ ├── LDM-2017-01-18.md │ │ ├── LDM-2017-02-14.md │ │ ├── LDM-2017-02-15.md │ │ ├── LDM-2017-02-21.md │ │ ├── LDM-2017-02-22.md │ │ ├── LDM-2017-02-28.md │ │ ├── LDM-2017-03-01.md │ │ ├── LDM-2017-03-07.md │ │ ├── LDM-2017-03-08.md │ │ ├── LDM-2017-03-15.md │ │ ├── LDM-2017-03-21.md │ │ ├── LDM-2017-03-28.md │ │ ├── LDM-2017-03-29.md │ │ ├── LDM-2017-04-05.md │ │ ├── LDM-2017-04-11.md │ │ ├── LDM-2017-04-18.md │ │ ├── LDM-2017-04-19.md │ │ ├── LDM-2017-05-16.md │ │ ├── LDM-2017-05-17.md │ │ ├── LDM-2017-05-26.md │ │ ├── LDM-2017-05-31.md │ │ ├── LDM-2017-06-13.md │ │ ├── LDM-2017-06-14.md │ │ ├── LDM-2017-06-27.md │ │ ├── LDM-2017-06-28.md │ │ ├── LDM-2017-07-05.md │ │ ├── LDM-2017-07-26.md │ │ ├── LDM-2017-08-07.md │ │ ├── LDM-2017-08-09.md │ │ ├── LDM-2017-08-14.md │ │ ├── LDM-2017-08-16.md │ │ ├── LDM-2017-08-21.md │ │ ├── LDM-2017-08-23.md │ │ ├── LDM-2017-08-28.md │ │ ├── LDM-2017-08-30.md │ │ ├── LDM-2017-09-25.md │ │ ├── LDM-2017-09-27.md │ │ ├── LDM-2017-10-02.md │ │ ├── LDM-2017-10-04.md │ │ ├── LDM-2017-10-09.md │ │ ├── LDM-2017-10-11.md │ │ ├── LDM-2017-10-16.md │ │ ├── LDM-2017-10-18.md │ │ ├── LDM-2017-10-25.md │ │ ├── LDM-2017-11-06.md │ │ ├── LDM-2017-11-08.md │ │ ├── LDM-2017-11-20.md │ │ ├── LDM-2017-11-27.md │ │ ├── LDM-2017-11-29.md │ │ ├── LDM-2017-12-04.md │ │ ├── LDM-2017-12-06.md │ │ └── README.md │ ├── 2018/ │ │ ├── LDM-2018-01-03.md │ │ ├── LDM-2018-01-10.md │ │ ├── LDM-2018-01-18.md │ │ ├── LDM-2018-01-22.md │ │ ├── LDM-2018-01-24.md │ │ ├── LDM-2018-01-31.md │ │ ├── LDM-2018-02-05.md │ │ ├── LDM-2018-02-07.md │ │ ├── LDM-2018-02-14.md │ │ ├── LDM-2018-02-21.md │ │ ├── LDM-2018-02-26.md │ │ ├── LDM-2018-02-28.md │ │ ├── LDM-2018-03-14.md │ │ ├── LDM-2018-03-19.md │ │ ├── LDM-2018-03-21.md │ │ ├── LDM-2018-03-28.md │ │ ├── LDM-2018-04-02.md │ │ ├── LDM-2018-04-04.md │ │ ├── LDM-2018-04-25.md │ │ ├── LDM-2018-04-30.md │ │ ├── LDM-2018-05-02.md │ │ ├── LDM-2018-05-14.md │ │ ├── LDM-2018-05-21.md │ │ ├── LDM-2018-05-23.md │ │ ├── LDM-2018-05-30.md │ │ ├── LDM-2018-06-04.md │ │ ├── LDM-2018-06-06.md │ │ ├── LDM-2018-06-25.md │ │ ├── LDM-2018-07-09.md │ │ ├── LDM-2018-07-11.md │ │ ├── LDM-2018-07-16.md │ │ ├── LDM-2018-08-20.md │ │ ├── LDM-2018-08-22.md │ │ ├── LDM-2018-09-05.md │ │ ├── LDM-2018-09-10.md │ │ ├── LDM-2018-09-19.md │ │ ├── LDM-2018-09-24.md │ │ ├── LDM-2018-09-26.md │ │ ├── LDM-2018-10-01.md │ │ ├── LDM-2018-10-03.md │ │ ├── LDM-2018-10-10.md │ │ ├── LDM-2018-10-15.md │ │ ├── LDM-2018-10-17.md │ │ ├── LDM-2018-10-22.md │ │ ├── LDM-2018-10-24.md │ │ ├── LDM-2018-10-29.md │ │ ├── LDM-2018-10-31.md │ │ ├── LDM-2018-11-05.md │ │ ├── LDM-2018-11-14.md │ │ ├── LDM-2018-11-28.md │ │ ├── LDM-2018-12-03.md │ │ ├── LDM-2018-12-05.md │ │ ├── LDM-2018-12-12.md │ │ └── README.md │ ├── 2019/ │ │ ├── LDM-2019-01-07.md │ │ ├── LDM-2019-01-09.md │ │ ├── LDM-2019-01-14.md │ │ ├── LDM-2019-01-16.md │ │ ├── LDM-2019-01-23.md │ │ ├── LDM-2019-02-13.md │ │ ├── LDM-2019-02-20.md │ │ ├── LDM-2019-02-25.md │ │ ├── LDM-2019-02-27.md │ │ ├── LDM-2019-03-04.md │ │ ├── LDM-2019-03-06.md │ │ ├── LDM-2019-03-13.md │ │ ├── LDM-2019-03-19.md │ │ ├── LDM-2019-03-25.md │ │ ├── LDM-2019-03-27.md │ │ ├── LDM-2019-04-01.md │ │ ├── LDM-2019-04-03.md │ │ ├── LDM-2019-04-15.md │ │ ├── LDM-2019-04-22.md │ │ ├── LDM-2019-04-24.md │ │ ├── LDM-2019-04-29.md │ │ ├── LDM-2019-05-13.md │ │ ├── LDM-2019-05-15.md │ │ ├── LDM-2019-07-10.md │ │ ├── LDM-2019-07-17.md │ │ ├── LDM-2019-07-22.md │ │ ├── LDM-2019-08-26.md │ │ ├── LDM-2019-08-28.md │ │ ├── LDM-2019-09-04.md │ │ ├── LDM-2019-09-11.md │ │ ├── LDM-2019-09-16.md │ │ ├── LDM-2019-09-18.md │ │ ├── LDM-2019-10-21.md │ │ ├── LDM-2019-10-23.md │ │ ├── LDM-2019-10-28.md │ │ ├── LDM-2019-10-30.md │ │ ├── LDM-2019-11-11.md │ │ ├── LDM-2019-11-13.md │ │ ├── LDM-2019-11-18.md │ │ ├── LDM-2019-11-25.md │ │ ├── LDM-2019-12-11.md │ │ ├── LDM-2019-12-16.md │ │ ├── LDM-2019-12-18.md │ │ └── README.md │ ├── 2020/ │ │ ├── LDM-2020-01-06.md │ │ ├── LDM-2020-01-08.md │ │ ├── LDM-2020-01-15.md │ │ ├── LDM-2020-01-22.md │ │ ├── LDM-2020-01-29.md │ │ ├── LDM-2020-02-03.md │ │ ├── LDM-2020-02-05.md │ │ ├── LDM-2020-02-10.md │ │ ├── LDM-2020-02-12.md │ │ ├── LDM-2020-02-19.md │ │ ├── LDM-2020-02-24.md │ │ ├── LDM-2020-02-26.md │ │ ├── LDM-2020-03-09.md │ │ ├── LDM-2020-03-23.md │ │ ├── LDM-2020-03-25.md │ │ ├── LDM-2020-03-30.md │ │ ├── LDM-2020-04-01.md │ │ ├── LDM-2020-04-06.md │ │ ├── LDM-2020-04-08.md │ │ ├── LDM-2020-04-13.md │ │ ├── LDM-2020-04-15.md │ │ ├── LDM-2020-04-20.md │ │ ├── LDM-2020-04-27.md │ │ ├── LDM-2020-05-04.md │ │ ├── LDM-2020-05-06.md │ │ ├── LDM-2020-05-11.md │ │ ├── LDM-2020-05-27.md │ │ ├── LDM-2020-06-01.md │ │ ├── LDM-2020-06-10.md │ │ ├── LDM-2020-06-15.md │ │ ├── LDM-2020-06-17.md │ │ ├── LDM-2020-06-22.md │ │ ├── LDM-2020-06-24.md │ │ ├── LDM-2020-06-29.md │ │ ├── LDM-2020-07-01.md │ │ ├── LDM-2020-07-06.md │ │ ├── LDM-2020-07-13.md │ │ ├── LDM-2020-07-20.md │ │ ├── LDM-2020-07-27.md │ │ ├── LDM-2020-08-24.md │ │ ├── LDM-2020-09-09.md │ │ ├── LDM-2020-09-14.md │ │ ├── LDM-2020-09-16.md │ │ ├── LDM-2020-09-23.md │ │ ├── LDM-2020-09-28.md │ │ ├── LDM-2020-09-30.md │ │ ├── LDM-2020-10-05.md │ │ ├── LDM-2020-10-07.md │ │ ├── LDM-2020-10-12.md │ │ ├── LDM-2020-10-14.md │ │ ├── LDM-2020-10-21.md │ │ ├── LDM-2020-10-26.md │ │ ├── LDM-2020-11-04.md │ │ ├── LDM-2020-11-11.md │ │ ├── LDM-2020-11-16.md │ │ ├── LDM-2020-12-02.md │ │ ├── LDM-2020-12-07.md │ │ ├── LDM-2020-12-14.md │ │ ├── LDM-2020-12-16.md │ │ └── README.md │ ├── 2021/ │ │ ├── LDM-2021-01-05.md │ │ ├── LDM-2021-01-11.md │ │ ├── LDM-2021-01-13.md │ │ ├── LDM-2021-01-27.md │ │ ├── LDM-2021-02-03.md │ │ ├── LDM-2021-02-08.md │ │ ├── LDM-2021-02-10.md │ │ ├── LDM-2021-02-22.md │ │ ├── LDM-2021-02-24.md │ │ ├── LDM-2021-03-01.md │ │ ├── LDM-2021-03-03.md │ │ ├── LDM-2021-03-10.md │ │ ├── LDM-2021-03-15.md │ │ ├── LDM-2021-03-24.md │ │ ├── LDM-2021-03-29.md │ │ ├── LDM-2021-04-05.md │ │ ├── LDM-2021-04-07.md │ │ ├── LDM-2021-04-12.md │ │ ├── LDM-2021-04-14.md │ │ ├── LDM-2021-04-19.md │ │ ├── LDM-2021-04-21.md │ │ ├── LDM-2021-04-28.md │ │ ├── LDM-2021-05-03.md │ │ ├── LDM-2021-05-10.md │ │ ├── LDM-2021-05-12.md │ │ ├── LDM-2021-05-17.md │ │ ├── LDM-2021-05-19.md │ │ ├── LDM-2021-05-26.md │ │ ├── LDM-2021-06-02.md │ │ ├── LDM-2021-06-07.md │ │ ├── LDM-2021-06-14.md │ │ ├── LDM-2021-06-21.md │ │ ├── LDM-2021-07-12.md │ │ ├── LDM-2021-07-19.md │ │ ├── LDM-2021-07-26.md │ │ ├── LDM-2021-08-23.md │ │ ├── LDM-2021-08-25.md │ │ ├── LDM-2021-08-30.md │ │ ├── LDM-2021-09-01.md │ │ ├── LDM-2021-09-13.md │ │ ├── LDM-2021-09-15.md │ │ ├── LDM-2021-09-20.md │ │ ├── LDM-2021-09-22.md │ │ ├── LDM-2021-10-13.md │ │ ├── LDM-2021-10-20.md │ │ ├── LDM-2021-10-25.md │ │ ├── LDM-2021-10-27.md │ │ ├── LDM-2021-11-01.md │ │ ├── LDM-2021-11-03.md │ │ ├── LDM-2021-11-10.md │ │ ├── LDM-2021-12-01.md │ │ ├── LDM-2021-12-15.md │ │ └── README.md │ ├── 2022/ │ │ ├── LDM-2022-01-03.md │ │ ├── LDM-2022-01-05.md │ │ ├── LDM-2022-01-12.md │ │ ├── LDM-2022-01-24.md │ │ ├── LDM-2022-01-26.md │ │ ├── LDM-2022-02-07.md │ │ ├── LDM-2022-02-09.md │ │ ├── LDM-2022-02-14.md │ │ ├── LDM-2022-02-16.md │ │ ├── LDM-2022-02-23.md │ │ ├── LDM-2022-02-28.md │ │ ├── LDM-2022-03-02.md │ │ ├── LDM-2022-03-09.md │ │ ├── LDM-2022-03-14.md │ │ ├── LDM-2022-03-21.md │ │ ├── LDM-2022-03-23.md │ │ ├── LDM-2022-03-28.md │ │ ├── LDM-2022-03-30.md │ │ ├── LDM-2022-04-06.md │ │ ├── LDM-2022-04-11.md │ │ ├── LDM-2022-04-13.md │ │ ├── LDM-2022-04-18.md │ │ ├── LDM-2022-04-25.md │ │ ├── LDM-2022-04-27.md │ │ ├── LDM-2022-05-02.md │ │ ├── LDM-2022-05-09.md │ │ ├── LDM-2022-05-11.md │ │ ├── LDM-2022-05-23.md │ │ ├── LDM-2022-06-06.md │ │ ├── LDM-2022-06-29.md │ │ ├── LDM-2022-07-13.md │ │ ├── LDM-2022-07-27.md │ │ ├── LDM-2022-08-03.md │ │ ├── LDM-2022-08-10.md │ │ ├── LDM-2022-08-24.md │ │ ├── LDM-2022-08-31.md │ │ ├── LDM-2022-09-21.md │ │ ├── LDM-2022-09-26.md │ │ ├── LDM-2022-09-28.md │ │ ├── LDM-2022-10-05.md │ │ ├── LDM-2022-10-10.md │ │ ├── LDM-2022-10-12.md │ │ ├── LDM-2022-10-17.md │ │ ├── LDM-2022-10-19.md │ │ ├── LDM-2022-10-26.md │ │ ├── LDM-2022-11-02.md │ │ ├── LDM-2022-11-30.md │ │ ├── LDM-2022-12-14.md │ │ └── README.md │ ├── 2023/ │ │ ├── LDM-2023-01-09.md │ │ ├── LDM-2023-01-11.md │ │ ├── LDM-2023-01-18.md │ │ ├── LDM-2023-02-01.md │ │ ├── LDM-2023-02-15.md │ │ ├── LDM-2023-02-22.md │ │ ├── LDM-2023-02-27.md │ │ ├── LDM-2023-03-01.md │ │ ├── LDM-2023-03-08.md │ │ ├── LDM-2023-03-13.md │ │ ├── LDM-2023-04-03.md │ │ ├── LDM-2023-04-10.md │ │ ├── LDM-2023-04-26.md │ │ ├── LDM-2023-05-01.md │ │ ├── LDM-2023-05-03.md │ │ ├── LDM-2023-05-08.md │ │ ├── LDM-2023-05-15.md │ │ ├── LDM-2023-05-17.md │ │ ├── LDM-2023-05-31.md │ │ ├── LDM-2023-06-05.md │ │ ├── LDM-2023-06-19.md │ │ ├── LDM-2023-07-12.md │ │ ├── LDM-2023-07-17.md │ │ ├── LDM-2023-07-24.md │ │ ├── LDM-2023-07-26.md │ │ ├── LDM-2023-07-31.md │ │ ├── LDM-2023-08-07.md │ │ ├── LDM-2023-08-09.md │ │ ├── LDM-2023-08-14.md │ │ ├── LDM-2023-08-16.md │ │ ├── LDM-2023-09-18.md │ │ ├── LDM-2023-09-20.md │ │ ├── LDM-2023-09-25.md │ │ ├── LDM-2023-09-27.md │ │ ├── LDM-2023-10-02.md │ │ ├── LDM-2023-10-04.md │ │ ├── LDM-2023-10-09.md │ │ ├── LDM-2023-10-11-specification-update.md │ │ ├── LDM-2023-10-11.md │ │ ├── LDM-2023-10-16.md │ │ ├── LDM-2023-11-15.md │ │ ├── LDM-2023-11-27.md │ │ ├── LDM-2023-12-04.md │ │ ├── LDM-2023-12-11.md │ │ └── README.md │ ├── 2024/ │ │ ├── LDM-2024-01-08.md │ │ ├── LDM-2024-01-10.md │ │ ├── LDM-2024-01-22.md │ │ ├── LDM-2024-01-29.md │ │ ├── LDM-2024-01-31.md │ │ ├── LDM-2024-02-05.md │ │ ├── LDM-2024-02-07.md │ │ ├── LDM-2024-02-21.md │ │ ├── LDM-2024-02-26.md │ │ ├── LDM-2024-02-28.md │ │ ├── LDM-2024-03-04.md │ │ ├── LDM-2024-03-11.md │ │ ├── LDM-2024-03-27.md │ │ ├── LDM-2024-04-01.md │ │ ├── LDM-2024-04-08.md │ │ ├── LDM-2024-04-15.md │ │ ├── LDM-2024-04-17.md │ │ ├── LDM-2024-04-22.md │ │ ├── LDM-2024-04-24.md │ │ ├── LDM-2024-05-01.md │ │ ├── LDM-2024-05-08.md │ │ ├── LDM-2024-05-13.md │ │ ├── LDM-2024-05-15-KeyValuePairCorrespondence.md │ │ ├── LDM-2024-05-15.md │ │ ├── LDM-2024-06-03.md │ │ ├── LDM-2024-06-10.md │ │ ├── LDM-2024-06-12.md │ │ ├── LDM-2024-06-17.md │ │ ├── LDM-2024-06-24.md │ │ ├── LDM-2024-06-26.md │ │ ├── LDM-2024-07-15-usage-data.md │ │ ├── LDM-2024-07-15.md │ │ ├── LDM-2024-07-17.md │ │ ├── LDM-2024-07-22-ref-struct-interface-examples.md │ │ ├── LDM-2024-07-22.md │ │ ├── LDM-2024-07-24.md │ │ ├── LDM-2024-08-14.md │ │ ├── LDM-2024-08-19.md │ │ ├── LDM-2024-08-21.md │ │ ├── LDM-2024-08-26.md │ │ ├── LDM-2024-08-28.md │ │ ├── LDM-2024-09-04.md │ │ ├── LDM-2024-09-11.md │ │ ├── LDM-2024-09-18.md │ │ ├── LDM-2024-09-30.md │ │ ├── LDM-2024-10-02.md │ │ ├── LDM-2024-10-07-extension-compat.md │ │ ├── LDM-2024-10-07.md │ │ ├── LDM-2024-10-09.md │ │ ├── LDM-2024-10-14-Enumerable-extension.cs │ │ ├── LDM-2024-10-14-Enumerable-extensions.cs │ │ ├── LDM-2024-10-14.md │ │ ├── LDM-2024-10-16.md │ │ ├── LDM-2024-10-28.md │ │ ├── LDM-2024-10-30.md │ │ ├── LDM-2024-11-04-patterns.md │ │ ├── LDM-2024-11-04.md │ │ ├── LDM-2024-11-13.md │ │ ├── LDM-2024-11-20.md │ │ ├── LDM-2024-12-04.md │ │ ├── LDM-2024-12-09.md │ │ └── README.md │ ├── 2025/ │ │ ├── LDM-2025-01-06.md │ │ ├── LDM-2025-01-13.md │ │ ├── LDM-2025-01-15.md │ │ ├── LDM-2025-01-22.md │ │ ├── LDM-2025-02-12.md │ │ ├── LDM-2025-02-19.md │ │ ├── LDM-2025-02-24.md │ │ ├── LDM-2025-02-26.md │ │ ├── LDM-2025-03-03.md │ │ ├── LDM-2025-03-05.md │ │ ├── LDM-2025-03-10.md │ │ ├── LDM-2025-03-12.md │ │ ├── LDM-2025-03-17.md │ │ ├── LDM-2025-03-19.md │ │ ├── LDM-2025-03-24.md │ │ ├── LDM-2025-04-02.md │ │ ├── LDM-2025-04-07.md │ │ ├── LDM-2025-04-09.md │ │ ├── LDM-2025-04-14.md │ │ ├── LDM-2025-04-16.md │ │ ├── LDM-2025-04-23.md │ │ ├── LDM-2025-05-05.md │ │ ├── LDM-2025-05-07.md │ │ ├── LDM-2025-05-12.md │ │ ├── LDM-2025-05-28.md │ │ ├── LDM-2025-06-04.md │ │ ├── LDM-2025-06-09.md │ │ ├── LDM-2025-06-11.md │ │ ├── LDM-2025-06-18.md │ │ ├── LDM-2025-06-23.md │ │ ├── LDM-2025-06-25.md │ │ ├── LDM-2025-06-30.md │ │ ├── LDM-2025-07-30.md │ │ ├── LDM-2025-08-13.md │ │ ├── LDM-2025-08-18.md │ │ ├── LDM-2025-08-20.md │ │ ├── LDM-2025-08-27.md │ │ ├── LDM-2025-09-10.md │ │ ├── LDM-2025-09-17.md │ │ ├── LDM-2025-09-24.md │ │ ├── LDM-2025-09-29.md │ │ ├── LDM-2025-10-01.md │ │ ├── LDM-2025-10-13.md │ │ ├── LDM-2025-10-29.md │ │ ├── LDM-2025-11-05.md │ │ ├── LDM-2025-11-12.md │ │ ├── LDM-2025-12-10.md │ │ ├── LDM-2025-12-17.md │ │ └── README.md │ ├── 2026/ │ │ ├── LDM-2026-01-12.md │ │ ├── LDM-2026-01-21.md │ │ ├── LDM-2026-01-26.md │ │ ├── LDM-2026-02-02.md │ │ ├── LDM-2026-02-04.md │ │ ├── LDM-2026-02-09.md │ │ ├── LDM-2026-02-11.md │ │ ├── LDM-2026-03-09.md │ │ └── README.md │ ├── README.md │ └── working-groups/ │ ├── collection-literals/ │ │ ├── CL-2022-10-06.md │ │ ├── CL-2022-10-14.md │ │ ├── CL-2022-10-21.md │ │ ├── CL-2023-04-05.md │ │ ├── CL-2023-04-28.md │ │ ├── CL-2023-05-10.md │ │ ├── CL-2023-05-26.md │ │ ├── CL-2023-06-12.md │ │ ├── CL-2023-06-26.md │ │ ├── CL-2023-07-26.md │ │ ├── CL-2023-08-03.md │ │ ├── CL-2023-08-10.md │ │ ├── CL-2023-08-11.md │ │ ├── CL-2024-01-23.md │ │ ├── CL-LDM-2023-05-31.md │ │ ├── CL-LDM-2023-08-14.md │ │ ├── Compiler-synthesized-types.md │ │ ├── Core-interface-target-type-proposal.md │ │ ├── LDM-questions-2023-08-15.md │ │ ├── collection-expressions-inferred-type.md │ │ └── collection-expressions-next.md │ ├── discriminated-unions/ │ │ ├── Case Classes.md │ │ ├── Closed Enums.md │ │ ├── Closed Hierarchies.md │ │ ├── DU-2022-10-19.md │ │ ├── DU-2022-10-24.md │ │ ├── DU-2022-10-31.md │ │ ├── DU-2022-11-07.md │ │ ├── Nominal Type Unions.md │ │ ├── Runtime Type Unions.md │ │ ├── Trade Off Matrix.md │ │ ├── TypeUnions.md │ │ ├── Union implementation challenges.md │ │ ├── allows.md │ │ ├── brace-syntax.md │ │ ├── enum-like-unions.md │ │ ├── extended-enums.md │ │ ├── original-nominal-type-unions.md │ │ ├── pre-unification-proposals/ │ │ │ ├── custom-unions.md │ │ │ ├── nominal-type-unions.md │ │ │ ├── non-boxing-access-pattern.md │ │ │ └── union-interfaces.md │ │ ├── to-nest-or-not-to-nest.md │ │ ├── type-value-conversion.md │ │ ├── union-patterns-update.md │ │ └── union-proposals-overview.md │ ├── expressions-statements/ │ │ └── ES-2022-11-30.md │ ├── extensions/ │ │ ├── Compatibility through coexistence between extension types and extension methods.md │ │ ├── Extension-API-docs.md │ │ ├── anonymous-extension-declarations.md │ │ ├── compat-mode-in-extensions.md │ │ ├── compromise-design-for-extensions.md │ │ ├── content-based-naming.md │ │ ├── disambiguation-syntax-examples.md │ │ ├── extending-extensions-a-guide-to-relaxation.md │ │ ├── extension-member-disambiguation.md │ │ ├── extension-members-unified-proposal.md │ │ ├── extensions-an-evolution-of-extension-methods.md │ │ ├── extensions-as-static-types.md │ │ ├── extensions-lookup.md │ │ ├── extensions_v2.md │ │ ├── implicit-compatibility-for-ported-extension-methods.md │ │ ├── metadata-names.md │ │ ├── rename-to-roles-and-extensions.md │ │ └── the-design-space-for-extensions.md │ ├── field-keyword/ │ │ ├── FK-2024-06-26.md │ │ ├── FK-2024-08-07 Nullability analysis with the `field` keyword.md │ │ └── FK-2024-08-07.md │ ├── interceptors/ │ │ ├── IC-2023-03-20.md │ │ ├── IC-2023-04-04.md │ │ └── interceptors-issues-2024-01.md │ ├── nullability-improvements/ │ │ ├── NI-2022-10-24.md │ │ ├── NI-2022-11-01.md │ │ ├── NI-2022-11-07.md │ │ └── NI-2022-11-22.md │ ├── params-improvements/ │ │ ├── PI-2022-10-25.md │ │ └── PI-2022-11-03.md │ ├── ref-improvements/ │ │ ├── REF-2022-11-11.md │ │ └── ignore-overloads-in-expressions.md │ ├── roles/ │ │ ├── extension-wg-2024-06-07.md │ │ ├── extension-wg-2024-06-14.md │ │ ├── extension-wg-2024-06-21.md │ │ ├── extensions-2023-02-21.md │ │ ├── extensions-wg-2023-04-27.md │ │ ├── extensions-wg-2023-06-07.md │ │ ├── extensions-wg-2024-03-05.md │ │ ├── extensions-wg-2024-08-09.md │ │ ├── roles-2022-11-10.md │ │ ├── roles-2023-01-23.md │ │ ├── roles-2023-01-25.md │ │ └── roles-2023-02-15.md │ └── unsafe-evolution/ │ └── unsafe-alternative-syntax.md ├── proposals/ │ ├── README.md │ ├── anonymous-using-declarations.md │ ├── async-main-update.md │ ├── block-bodied-switch-expression-arms.md │ ├── breaking-change-warnings.md │ ├── case-declarations.md │ ├── closed-enums.md │ ├── closed-hierarchies.md │ ├── collection-expression-arguments.md │ ├── compound-assignment-in-initializer-and-with.md │ ├── conditional-operator-access-syntax-refinement.md │ ├── csharp-10.0/ │ │ ├── GlobalUsingDirective.md │ │ ├── async-method-builders.md │ │ ├── caller-argument-expression.md │ │ ├── constant_interpolated_strings.md │ │ ├── enhanced-line-directives.md │ │ ├── extended-property-patterns.md │ │ ├── file-scoped-namespaces.md │ │ ├── improved-definite-assignment.md │ │ ├── improved-interpolated-strings.md │ │ ├── lambda-improvements.md │ │ ├── parameterless-struct-constructors.md │ │ └── record-structs.md │ ├── csharp-11.0/ │ │ ├── auto-default-structs.md │ │ ├── checked-user-defined-operators.md │ │ ├── extended-nameof-scope.md │ │ ├── file-local-types.md │ │ ├── generic-attributes.md │ │ ├── list-patterns.md │ │ ├── low-level-struct-improvements.md │ │ ├── new-line-in-interpolation.md │ │ ├── numeric-intptr.md │ │ ├── pattern-match-span-of-char-on-string.md │ │ ├── raw-string-literal.md │ │ ├── relaxing_shift_operator_requirements.md │ │ ├── required-members.md │ │ ├── static-abstracts-in-interfaces.md │ │ ├── unsigned-right-shift-operator.md │ │ └── utf8-string-literals.md │ ├── csharp-12.0/ │ │ ├── collection-expressions.md │ │ ├── experimental-attribute.md │ │ ├── inline-arrays.md │ │ ├── lambda-method-group-defaults.md │ │ ├── primary-constructors.md │ │ ├── ref-readonly-parameters.md │ │ └── using-alias-types.md │ ├── csharp-13.0/ │ │ ├── collection-expressions-better-conversion.md │ │ ├── esc-escape-sequence.md │ │ ├── lock-object.md │ │ ├── method-group-natural-type-improvements.md │ │ ├── overload-resolution-priority.md │ │ ├── params-collections.md │ │ ├── partial-properties.md │ │ ├── ref-struct-interfaces.md │ │ └── ref-unsafe-in-iterators-async.md │ ├── csharp-14.0/ │ │ ├── extension-operators.md │ │ ├── extensions.md │ │ ├── field-keyword.md │ │ ├── first-class-span-types.md │ │ ├── ignored-directives.md │ │ ├── null-conditional-assignment.md │ │ ├── optional-and-named-parameters-in-expression-trees.md │ │ ├── partial-events-and-constructors.md │ │ ├── simple-lambda-parameters-with-modifiers.md │ │ ├── unbound-generic-types-in-nameof.md │ │ └── user-defined-compound-assignment.md │ ├── csharp-6.0/ │ │ ├── empty-params-array.md │ │ ├── enum-base-type.md │ │ └── struct-autoprop-init.md │ ├── csharp-7.0/ │ │ ├── binary-literals.md │ │ ├── digit-separators.md │ │ ├── expression-bodied-everything.md │ │ ├── local-functions.md │ │ ├── out-var.md │ │ ├── pattern-matching.md │ │ ├── ref-locals-returns.md │ │ ├── task-types.md │ │ ├── throw-expression.md │ │ └── tuples.md │ ├── csharp-7.1/ │ │ ├── README.md │ │ ├── async-main.md │ │ ├── generics-pattern-match.md │ │ ├── infer-tuple-names.md │ │ └── target-typed-default.md │ ├── csharp-7.2/ │ │ ├── conditional-ref.md │ │ ├── leading-separator.md │ │ ├── non-trailing-named-arguments.md │ │ ├── private-protected.md │ │ ├── readonly-ref.md │ │ ├── readonly-struct.md │ │ ├── ref-extension-methods.md │ │ ├── ref-struct-and-span.md │ │ └── span-safety.md │ ├── csharp-7.3/ │ │ ├── auto-prop-field-attrs.md │ │ ├── blittable.md │ │ ├── enum-delegate-constraints.md │ │ ├── expression-variables-in-initializers.md │ │ ├── improved-overload-candidates.md │ │ ├── indexing-movable-fixed-fields.md │ │ ├── pattern-based-fixed.md │ │ ├── ref-local-reassignment.md │ │ ├── ref-loops.md │ │ ├── stackalloc-array-initializers.md │ │ └── tuple-equality.md │ ├── csharp-8.0/ │ │ ├── README.md │ │ ├── alternative-interpolated-verbatim.md │ │ ├── async-streams.md │ │ ├── async-using.md │ │ ├── constraints-in-overrides.md │ │ ├── constructed-unmanaged.md │ │ ├── default-interface-methods.md │ │ ├── nested-stackalloc.md │ │ ├── notnull-constraint.md │ │ ├── null-coalescing-assignment.md │ │ ├── nullable-reference-types-specification.md │ │ ├── nullable-reference-types.md │ │ ├── obsolete-accessor.md │ │ ├── patterns.md │ │ ├── ranges.cs │ │ ├── ranges.md │ │ ├── readonly-instance-members.md │ │ ├── shadowing-in-nested-functions.md │ │ ├── static-local-functions.md │ │ ├── unconstrained-null-coalescing.md │ │ └── using.md │ ├── csharp-9.0/ │ │ ├── covariant-returns.md │ │ ├── extending-partial-methods.md │ │ ├── extension-getenumerator.md │ │ ├── function-pointers.md │ │ ├── init.md │ │ ├── lambda-discard-parameters.md │ │ ├── local-function-attributes.md │ │ ├── module-initializers.md │ │ ├── native-integers.md │ │ ├── nullable-constructor-analysis.md │ │ ├── nullable-parameter-default-value-analysis.md │ │ ├── nullable-reference-types-specification.md │ │ ├── patterns3.md │ │ ├── records.md │ │ ├── skip-localsinit.md │ │ ├── static-anonymous-functions.md │ │ ├── target-typed-conditional-expression.md │ │ ├── target-typed-new.md │ │ ├── top-level-statements.md │ │ ├── unconstrained-type-parameter-annotations.md │ │ └── variance-safety-for-static-interface-members.md │ ├── deconstruction-in-lambda-parameters.md │ ├── dictionary-expressions.md │ ├── enhanced-switch-statements.md │ ├── expand-ref.md │ ├── extension-indexers.md │ ├── fieldof.md │ ├── final-initializers.md │ ├── immediately-enumerated-collection-expressions.md │ ├── inactive/ │ │ ├── README.md │ │ ├── list-patterns-enumerables.md │ │ ├── pointer-null-coalescing.md │ │ └── repeated-attributes.md │ ├── inference-for-constructor-calls.md │ ├── inference-for-type-patterns.md │ ├── interpolated-string-handler-argument-value.md │ ├── iterators-in-lambdas.md │ ├── labeled-break-continue.md │ ├── left-right-join-in-query-expressions.md │ ├── multiple-using-var-discards.md │ ├── null-conditional-await.md │ ├── pattern-variables.md │ ├── proposal-template.md │ ├── readonly-parameters.md │ ├── readonly-setter-calls-on-non-variables.md │ ├── rejected/ │ │ ├── README.md │ │ ├── collection-expressions-in-foreach.md │ │ ├── declaration-expressions.md │ │ ├── discriminated-unions.md │ │ ├── fixed-sized-buffers.md │ │ ├── format.md │ │ ├── interpolated-string-handler-method-names.md │ │ ├── intptr-operators.md │ │ ├── intrinsics.md │ │ ├── nullable-enhanced-common-type.md │ │ ├── param-nullchecking.md │ │ ├── params-span.md │ │ ├── readonly-locals.md │ │ ├── records.md │ │ ├── recordsv2.md │ │ ├── self-constraint.md │ │ └── static-delegates.md │ ├── relaxed-partial-ref-ordering.md │ ├── speclet-disclaimer.md │ ├── standard-unions.md │ ├── target-typed-generic-type-inference.md │ ├── target-typed-static-member-access.md │ ├── top-level-members.md │ ├── type-parameter-inference-from-constraints.md │ ├── unions.md │ ├── unsafe-evolution.md │ └── unsigned-sizeof.md └── spec/ ├── LICENSE.md ├── README.md ├── arrays.md ├── attributes.md ├── basic-concepts.md ├── classes.md ├── conversions.md ├── delegates.md ├── documentation-comments.md ├── enums.md ├── exceptions.md ├── expressions.md ├── interfaces.md ├── introduction.md ├── lexical-structure.md ├── namespaces.md ├── statements.md ├── structs.md ├── types.md ├── unsafe-code.md └── variables.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ * text=auto ================================================ FILE: .github/CODEOWNERS ================================================ * @dotnet/roslyn-compiler ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Propose a language idea or ask a question url: https://github.com/dotnet/csharplang/discussions/new/choose about: Starting with discussion is the way to create a new proposal. ================================================ FILE: .github/ISSUE_TEMPLATE/docs-feedback.yml ================================================ name: Learn feedback control. description: | ⛔ This template is hooked into the feedback control on the bottom of every page on the learn.microsoft.com site. It automatically fills in several fields for you. Don't use for other purposes. ⛔ assignees: - BillWagner labels: - Area-Speclets - untriaged body: - type: markdown attributes: value: "## Issue information" - type: markdown attributes: value: Select the issue type, and describe the issue in the text box below. Add as much detail as needed to help us resolve the issue. - type: dropdown id: issue-type attributes: label: Type of issue options: - Typo - Spec incorrect - Spec incomplete - Other (describe below) validations: required: true - type: textarea id: feedback validations: required: true attributes: label: Description - type: markdown attributes: value: "## 🚧 Article information 🚧" - type: markdown attributes: value: "*Don't modify the following fields*. They are automatically filled in for you. Doing so will disconnect your issue from the affected article. *Don't edit them*." - type: input id: pageUrl validations: required: true attributes: label: Page URL - type: input id: contentSourceUrl validations: required: true attributes: label: Content source URL ================================================ FILE: .github/ISSUE_TEMPLATE/proposal_template.md ================================================ --- name: Create a language specification about: For proposals that have been invited by a team member. title: "[Proposal]: [FEATURE_NAME]" --- # FEATURE_NAME * Specification: Link to a filled out [proposal template](../../proposals/proposal-template.md). If not yet available, link to the PR adding the specification. * Discussion: Link to the discussion topic for this feature. ## Summary [summary]: #summary ## Design meetings ================================================ FILE: .gitignore ================================================ .vscode/* # Ignore temporary files ~$* *~ ================================================ FILE: CODE-OF-CONDUCT.md ================================================ # 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). ================================================ FILE: Communities.md ================================================ **Disclaimer**: This document is maintained by the C# community and not the responsibility of the C# Language Design Team (LDT). Please do not contact the LDT for any errors in this document; however, PRs are welcome. **Channels:** - [Dotnet Discord](https://aka.ms/dotnet-discord-csharp) - github.com/dotnet discord for discussing dotnet repositories (including csharplang). [![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. [![Join the chat at https://www.irccloud.com/invite?channel=%23%23csharp&hostname=irc.freenode.net&port=6697&ssl=1](https://img.shields.io/badge/IRC-%23%23csharp-1e72ff.svg?style=flat)](https://www.irccloud.com/invite?channel=%23%23csharp&hostname=irc.freenode.net&port=6697&ssl=1) Servers: irc.freenode.net, chat.freenode.net Channel: ##csharp [![Join the chat at https://www.irccloud.com/invite?channel=%23c%23&hostname=irc.quakenet.org&port=6697&ssl=1](https://img.shields.io/badge/IRC-%23c%23-1e72ff.svg?style=flat)](https://www.irccloud.com/invite?channel=%23c%23&hostname=irc.quakenet.org&port=6697&ssl=1) Servers: irc.quakenet.org Channel: #c# Recommended IRC Clients: HexChat, mIRC. **Forums:** - [Stack Overflow](https://stackoverflow.com) Please read [this](https://stackoverflow.com/help/dont-ask) before posting. - [Reddit](https://www.reddit.com/r/csharp/) Please read [this](https://www.reddit.com/r/csharp/comments/3xn6sm/welcome_to_rcsharp_read_this_post_before) before posting. ================================================ FILE: Design-Process.md ================================================ # Language Design Process The language design process is the steps that a proposal takes throughout its life, going from an initial seed of an idea, to a championed proposal that is being considered for inclusion in the language, all the way to the final specification representing a feature that has been shipped as part of a .NET release. It is very important to the language design team that we have a clear process and organization for this, for multiple reasons: * Our community is very active and vocal on this repo, and we want to make sure that feedback can be heard and impact the design and direction of the language, as well as ensuring that the community can follow the state of designs. * We want to make sure that we are using our design energy effectively, and that we can see the status of previous meetings as we drive a feature to completion. * We want to be able to look back historically to use previous design decisions to inform new language features, as well as to ensure that when a feature is incorporated into the ECMA spec, it captures the full nuances of what was designed. To achieve these goals, this repository covers the actual proposed text for new language features (often called speclets), notes from language design meetings (called LDM), intermediate documents being worked on as part of the development of proposals, issues tracking features that we want to include in the C# language (champion issues), and discussion topics for those features. In order to keep things organized, we keep discussion of proposals to actual discussions; issues are for tracking purposes only. This policy is changed from previous history in the csharplang repo, so many (most) issues will have some historical discussion in them. However, threaded discussion topics are better for the types of branching conversations that language features have, so all new discussion will happen in the Discussion forum, rather than on issues. ## Steps of the process There are a few steps along the path from the seed of an idea all the way to an implemented language feature that is in an official ECMA specification. While much of that process takes place outside of this repository (https://github.com/dotnet/roslyn for the language feature implementation, https://github.com/dotnet/runtime for supporting BCL APIs and runtime changes, https://github.com/dotnet/csharpstandard/ for the specification changes, just to name a few), we track the overall implementation of the feature in this repository, and take the following steps to make understanding the current status easier. ### Proposed feature New ideas are submitted as [discussions](https://github.com/dotnet/csharplang/discussions). These ideas can be very freeform, though we ask that you search for duplicates before opening a new discussion, as the most common first comment on new discussions is one or more links to existing discussions that cover the idea. While ideas are welcome, there is no guarantee that an idea will be adopted into the language; even among things that have been triaged for eventual inclusion in the language, there is more work than can be done in a single lifetime. In order to move forward, a member of the language design team (LDT) has to decide to "champion" the idea. This is effectively the LDT member deciding to sponsor the idea, and to bring it forward at a future LDM. Most features do not make it out of this stage. In order to move to the next stage, there needs to be enough detail to fill out the [proposal template](proposals/proposal-template.md) with at least some amount of detail. While we do not need exact spec language at this point, there should be enough information that other LDT members can get a general idea of the feature, what areas of the language it will impact, and where the complicated aspects are likely to be. In order to be triaged as part of an LDM, this template will need to be checked into the repo. #### Stage Lifecycle * Starts when a new discussion is opened * Moves to [Championed feature](#championed-feature) when an LDT member decides to champion * For LDT members, see [these instructions](#steps-to-move-a-discussion-to-a-champion-feature) for how to move to the next stage. ### Championed feature A championed feature is an idea for a C# language feature that an LDT member has decided to sponsor, or "champion", for possible inclusion into C#. You can identify issues in this category by looking for issues with [this query](https://github.com/dotnet/csharplang/issues?q=is%3Aissue%20state%3Aopen%20no%3Amilestone%20label%3A%22Proposal%20champion%22), issues with the `Proposal Champion` label and no milestone. For these issues, one or more LDT members have indicated that they are interested in the idea, but the entire LDM has not met to discuss the idea and give an official blessing. We try to triage these every few months, though when we start wrapping up a particular release and design time is needed for active questions on features currently under development, we can lag behind here. #### Stage Lifecycle * Starts when an LDT member decides to champion a [proposed feature](#proposed-feature) * Moves to [rejected feature](#rejected-feature) if rejected at LDM * Moves to [triaged feature](#triaged-feature) if approved at LDM and assigned to a development milestone ### Triaged feature A triaged feature is a championed issue that has been approved at LDM for inclusion in a future release of C#. We have quite a few issues in this bucket; they are visible by looking at any issues labeled `Proposal Champion` that have been assigned to one of the development milestones, `Any Time`, `Backlog`, `Needs More Work`, or `Working Set`. [This query](https://github.com/dotnet/csharplang/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Proposal%20champion%22%20(milestone%3ABacklog%20OR%20milestone%3A%22Any%20Time%22%20OR%20milestone%3A%22Needs%20More%20Work%22%20OR%20milestone%3A%22Working%20Set%22%20)) shows these issues. The development milestones mean the following: * `Working Set` - These are features that are being actively worked on by LDT and/or Roslyn compiler members in some form; whether that's design work behind the scenes, active LDMs discussing the topics, or other actions. * `Backlog` - These are features that have been approved for inclusion in C# at some LDM in the past, but are not currently being actively worked on. These are not open to community implementation; they are usually too large or involved to devote LDM time to unless we're willing to make an active effort to get them into the language. * `Needs More Work` - These are features that have been approved for inclusion in C# at some LDM in the past, but there are currently design aspects or blocking issues that prevent active work from proceeding at this point. * `Any Time` - These are features that have been approved for inclusion in C# at some LDM in the past that are open for community members to contribute to C#. Please do keep in mind that the C# compiler team is constrained by resource limits, and will need to devote significant time to helping get even the simplest of features into the language; please ask _before_ starting to work on one of these features to make sure the team is currently able to devote that time. Features in this category can be in one of two states, denoted by labels on the issue: * `Needs Approved Specification` - LDT has approved this in theory, but has not been presented with a precise specification for how the feature will work. Before implementation can proceed, a complete specification needs to be created and approved at an LDM. * `Needs Implementation` - A specification for this feature has been approved at a previous LDM, and needs to be implemented in the C# compiler. This state is the one that will consume most of an approved feature's lifecycle, on average. It is not uncommon for a feature that is approved in theory to spend years in the backlog and/or working set before being implemented. #### Stage Lifecycle * Starts when a [championed feature](#championed-feature) is approved at LDM and assigned to a development milestone * Ends when the feature is [implemented](#implemented-feature) as part of a C# release * For LDT members, see [these instructions](#steps-to-move-a-triaged-feature-to-an-implemented-feature) for steps to take when shipping a feature. * Ends if the feature is reconsidered at an LDM and then [rejected](#rejected-feature) ### Implemented feature Once a feature has been implemented in the [Roslyn](https://github.com/dotnet/roslyn) C# compiler and been released as part of an official C# release, it is considered implemented. At this point, it will have a complete speclet available in the [proposals/csharp-\](proposals) folder (note that some older C# features, particularly the C# 7.X and prior features, did not follow this, and have incomplete or non-existent speclets). At this point, the issue will be labeled `Implemented Needs ECMA Specification`, but it will not be closed until the ECMA-334 specification is updated with the feature. This can take some time; the ECMA-334 committee is working on catching up as fast as they can, but is several years behind the language implementation. #### Stage Lifecycle * Starts when a [triaged feature](#triaged-feature) is shipped as part of a C# release * Ends when the feature is fully incorporated into a version of the ECMA-334 specification ### ECMA-specified feature At this point, the feature has been fully incorporated by ECMA-TC49-TG2, the C# standards committee, into the [official C# ECMA specification](https://github.com/dotnet/csharpstandard/). When this happens, we close the issue as completed, and all development work on the feature is complete. #### Stage Lifecycle * Starts when an [implemented feature](#implemented-feature) is shipped as part of a C# release * This is the final state for a feature that is included in C#, no further state changes occur ### Rejected feature When a feature is explicitly considered during an LDM, and the LDT decides as a group to reject it, it moves to this state. At this point, close the champion issue as not planned and set the milestone to `Likely Never`. It's not impossible for an issue to be pulled back out of this state and included in the language in the future, but generally, this state means that the feature will never be part of C#. #### Stage Lifecycle * Starts when a [championed feature](#championed-feature) is considered at LDM and rejected * While it is possible that some rejected features end up getting reconsidered, this is generally the final state for language features that are explicitly considered and rejected during LDM ## Language Design Team processes These are various processes and actions taken by LDT members during the development of a feature. Community members should not perform these actions unless invited to do so by an LDT member. ### Steps to move a [Discussion](#proposed-feature) to a [Champion feature](#championed-feature) When an LDT member decides to champion a discussion, they take the following steps: 1. Create a new proposal champion issue. * If preferred, the LDT member can ask the original proposer to create this issue. * Note: it can be easier to create the PR for step 6 first, get that into a ready-to-merge state, and then create the champion issue at that point, depending on the complexity of the feature. * The champion issue should have a short summary of the feature, but not the full proposal; there should be enough detail to jog the memory and/or get someone interested in reading the full specification, but should not have detail that will end up needing to be edited often as a proposal evolves. 2. Assign themselves to the champion issue. 3. Apply the `Proposal Champion` label to the new issue, as well as to the original discussion. 4. Link to the original discussion from the champion issue. 5. Lock the proposal champion issue for comments to ensure that discussion continues in the discussion area, rather than on the champion issue. 6. Fill out and check in a [proposal template](proposals/proposal-template.md) for the feature. Exact spec language is not required, but there should be enough detail to have a meaningful triage session. * This is also something the LDT member can ask a community member to open a PR for, if they are willing. * The filled out proposal should include a link to the champion issue for easy navigation. ### Bringing open questions to LDM During the course of development of a feature, there are several different types of questions that need to get brought to LDM for answers. The most important overriding factor for any question is that there is a checked-in commit that contains the question. The document and commit will be linked as part of the notes so that future readers of the notes can understand the full context in which the question was asked. #### Alternative proposals, supplemental documentation As part of the initial design of a feature, a number of different proposals may be brought as part of the design process, either as alternatives to an initial design, or as supplemental materials to an existing design to help drive conversation in LDM. We want to keep these "supplemental" materials in one place, rather than scattered throughout the repo as different issues, discussions, and other documents. For such material, they should go in the [working group folder](meetings/working-groups/) for that feature. Not all features will have such a folder; indeed, most will not. For these documents, please check them in _before_ bringing them to an LDM. The LDM organizer should be able to link to an exact document, not to a PR. In the event the proposer wants to solicit input before LDM, they can leave the PR open until a day or two before LDM; in such a case the LDM organizer may decide to link to the PR in the schedule instead of a specific document. However, the PR must be merged before LDM. #### Specific implementation questions During the implementation process, we will often come up with specific scenarios that need to be brought to an LDM and discussed. These questions should be placed in the proposal specification, in an `Open Questions` section below the main specification text. Each question should have a _linkable_ header, such that the notes that go over the question can link to the exact question being asked. For these questions, please check them in _before_ bringing them to an LDM. The LDM organizer should be able to link to a specific heading in a specific document, not to a PR. In the event the questioner wants to solicit input before LDM, they can leave the PR open until a day or two before LDM; in such a case the LDM organizer may decide to link to the PR in the schedule instead of a specific document. However, the PR must be merged before LDM. Once a question has been answered, the specification should be updated to include any changes required, and the question should be removed. We link to exact commits in the notes to ensure that questions can still be found, while keep speclets neat and free of potentially confusing syntax examples that may be rejected at LDM. #### Proposed specification updates Sometimes during implementation, a specification needs to be updated. These updates are often best viewed by looking at a PR diff; however, PRs present a problem for historical recording keeping. While GitHub does keep around commits that were only ever part of a PR (either because the PR was closed, or because it was squashed/rebased), reusing a PR across multiple LDM sessions can make it difficult to understand the exact state of the PR when it was reviewed by LDM. Whenever possible, do not reuse PRs between multiple LDM sessions. When a PR is reviewed by LDM, either close or merge it, and make a new PR for the next LDM to pick up where it left off. This is a guideline, not a rule; there will be times this cannot happen for whatever reason. But the following rules _must_ be followed: 1. Do not force push over commits that have been reviewed by LDM. 2. When scheduling your topic for LDM, please use GitHub commit URL or commit range URL. The PR link can be included as well, but the commit (range) is required for inclusion in the notes. The LDM organizer should be able to link to exactly what will be reviewed in the LDM session. ### Steps to move a [triaged feature](#triaged-feature) to an [implemented feature](#implemented-feature) Once a feature has been implemented and has or soon will be shipped, take the following steps (these are usually done in bulk when a release nears): 1. If a folder for the C# release does not exist yet, create it. 2. Move the specification for the feature into that folder and review relative links it contains. 3. Update the champion issue as follows: 1. Update the specification link to point at the new location. 2. Update the milestone of the issue to be the C# release it has shipped/will ship in, creating it if it doesn't exist. 3. Add the version of .NET and VS it will/did ship in to the issue title. * As an example, `[Proposal]: Params Collections` became `[Proposal]: Params Collections (VS 17.10, .NET 9)` 4. Add the `Implemented Needs ECMA Spec` label to the issue. 4. Add the feature to the [language version history](Language-Version-History.md) document. ### Publishing notes (for the LDM notetaker) When publishing a set of notes, take the following steps: 1. Put the notes in the appropriate `meetings/` folder. Notes should follow the `LDM---.md` format. 1. Any supplemental documents for the meeting are also included here with the same prefix to ensure good sorting. 2. Include an agenda at the top with document-relative links to each section corresponding to the topics discussed during LDM. 3. Include both the champion issue and the reviewed specification for a given topic. 4. Ensure there are two spaces at the end of lines that do not have an extra line break (such as between entries for the champion issue and specification). 5. All links should be permalinks (rather than branch links). If you press "y" on the page, the URL in the browser will update to the permalink. 2. Update the `meetings//README.md` to: 1. Move the date into the `C# Language Design Notes for ` section 2. Update the agenda to match what is in the meeting notes, with the document-relative links removed. 3. Include a link to the notes. This format is usually `[C# Language Design Meeting for , ](absolute-note-link)` (use the branch link here). 4. When entering the questions discussed, use the question rather than the outcome. 5. If a topic was not discussed during LDM, or not fully finished, move the topic line back to `Schedule ASAP`. 3. Commit the updates. Prefer using spelled out dates (ie, January 1st, 1970), rather than abbreviations, to avoid confusion. 4. Update the champion issues of discussed topics with a link to the notes. Prefer using an exact link to the heading for that set of notes. 5. Create a discussion for the new notes. The title format is `LDM Notes for , `. Set the category to `LDM Notes`. 1. The discussion should link to the full notes, and copy the agenda from the README. 6. Post the discussion to various communities to let people know the notes are up; at a minimum, to the C# LDM teams chat. We often post to discord as well, but that is dependent on people being who are on discord not being on vacation. ================================================ FILE: Language-Version-History.md ================================================ Features Added in C# Language Versions ==================== # C# 14.0 - .NET 10 and Visual Studio 2026 version 18.0 - [Extension methods and properties](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/extensions.md): allows extending an existing type with instance or static methods and properties. - [Extension operators](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/extension-operators.md): allows extending an existing type with operators. - [`field` keyword in properties](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/field-keyword.md): `field` allows access to the property's backing field without having to declare it. - [Partial events and constructors](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/partial-events-and-constructors.md): allows the partial modifier on events and constructors to separate declaration and implementation parts. - [User-defined compound assignment operators](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/user-defined-compound-assignment.md): allow user types to customize behavior of compound assignment operators in a way that the target of the assignment is modified in-place (`public void operator +=(int x)`). - [First-class `Span` types](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/first-class-span-types.md): streamlines usage of `Span`-based APIs by improving type inference and overload resolution. - [Null-conditional assignment](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/null-conditional-assignment.md): permits assignment to occur conditionally within a `a?.b` or `a?[b]` expression (`a?.b = c`). - [Unbound generic types in `nameof`](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/unbound-generic-types-in-nameof.md): relaxes some restrictions on usage of generic types inside `nameof` (`nameof(List<>)`). - [Simple lambda parameters with modifiers](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/simple-lambda-parameters-with-modifiers.md): allows lambda parameters to be declared with modifiers without requiring their types (`(ref entry) => ...`). - [Ignored directives](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/ignored-directives.md): add `#:` directive prefix to be used by `dotnet run app.cs` tooling but ignored by the language. - [Optional and named arguments in `Expression` trees](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/optional-and-named-parameters-in-expression-trees.md): relaxes some restrictions on `Expression` trees (`Expression<...> e = (a, i) => a.Contains(i, comparer: null);`). # C# 13.0 - .NET 9 and Visual Studio 2022 version 17.12 - [ESC escape sequence](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/esc-escape-sequence.md): introduces the `\e` escape sequence to represent the ESCAPE/ESC character (U+001B). - [Method group natural type improvements](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/method-group-natural-type-improvements.md): look scope-by-scope and prune inapplicable candidates early when determining the natural type of a method group. - [`Lock` object](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/lock-object.md): allow performing a `lock` on `System.Threading.Lock` instances. - Implicit indexer access in object initializers: allows indexers in object initializers to use implicit Index/Range indexers (`new C { [^1] = 2 }`). - [`params` collections](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/params-collections.md): extends `params` support to collection types (`void M(params ReadOnlySpan s)`). - [`ref`/`unsafe` in iterators/async](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/ref-unsafe-in-iterators-async.md): allows using `ref`/`ref struct` locals and `unsafe` blocks in iterators and async methods between suspension points. - [`ref struct` interfaces](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/ref-struct-interfaces.md): allows `ref struct` types to implement interfaces and introduces the `allows ref struct` constraint. - [Overload resolution priority](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/overload-resolution-priority.md): allows API authors to adjust the relative priority of overloads within a type using `System.Runtime.CompilerServices.OverloadResolutionPriority`. - [Partial properties](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/partial-properties.md): allows splitting a property into multiple parts using the `partial` modifier. - [Better conversion from collection expression element](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md): improves overload resolution to account for the element type of collection expressions. # C# 12.0 - .NET 8 and Visual Studio 2022 version 17.8 - [Collection expressions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md): provides a uniform and efficient way of creating collections using collection-like types (`List list = [1, 2, 3];`) - [Primary Constructors](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/primary-constructors.md): helps reduce field and constructor boilerplate (`class Point(int x, int y);`) - [Inline Arrays](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/inline-arrays.md): provides a general-purpose and safe mechanism for declaring arrays using the `[InlineArray(size)]` attribute. - [Using aliases for any type](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/using-alias-types.md): relaxes many restrictions on `using` alias declarations, allowing built-in types, tuple types, pointer types, array types (`using Point = (int x, int y);`) - [Ref readonly parameters](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/ref-readonly-parameters.md): `ref readonly` parameters mandate that arguments are passed by reference instead of potentially copied, can’t be modified, and warn if a temporary variable must be created. - [Nameof accessing instance members](https://github.com/dotnet/csharplang/issues/4037): relaxes some restrictions on usage of instance members inside `nameof` (`nameof(field.ToString)`) - [Lambda optional parameters](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/lambda-method-group-defaults.md): allows lambda parameters to declare default values (`(int i = 42) => { }`) # C# 11.0 - .NET 7 and Visual Studio 2022 version 17.4 - [Raw string literals](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/raw-string-literal.md): introduces a string literal where the content never needs escaping (`var json = """{ "summary": "text" }""";` or `var json = $$"""{ "summary": "text", "length": {{length}} }""";`). - [UTF-8 string literals](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/utf8-string-literals.md): UTF-8 string literals with the `u8` suffix (`ReadOnlySpan s = "hello"u8;`) - [Pattern match `Span` on a constant string](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/pattern-match-span-of-char-on-string.md): an input value of type `Span` or `ReadonlySpan` can be matched with a constant string pattern (`span is "123"`). - [Newlines in interpolations](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/new-line-in-interpolation.md): allows newline characters in single-line interpolated strings. - [List patterns](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/list-patterns.md): allows matching indexable types (`list is [1, 2, ..]`). - [File-local types](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/file-local-types.md): introduces the `file` type modifier (`file class C { ... }`). - [Ref fields](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md): allows `ref` field declarations in a `ref struct` (`ref struct S { ref int field; ... }`), introduces `scoped` modifier and `[UnscopedRef]` attribute. - [Required members](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/required-members.md): introduces the `required` field and property modifier and `[SetsRequiredMembers]` attribute. - [Static abstract members in interfaces](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/static-abstracts-in-interfaces.md): allows an interface to specify abstract static members. - [Unsigned right-shift operator](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/unsigned-right-shift-operator.md): introduces the `>>>` operator and `>>>=`. - [`checked` user-defined operators](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/checked-user-defined-operators.md): numeric and conversion operators support defining `checked` variants (`public static Int128 operator checked +(Int128 lhs, Int128 rhs) { ... }`). - [Relaxing shift operator requirements](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/relaxing_shift_operator_requirements.md): the right-hand-side operand of a shift operator is no longer restricted to only be `int` - [Numeric IntPtr](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/numeric-intptr.md): `nint`/`nuint` become simple types aliasing `System.IntPtr`/`System.UIntPtr`. - [Auto-default structs](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/auto-default-structs.md): struct constructors automatically default fields that are not explicitly assigned. - [Generic attributes](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/generic-attributes.md): allows attributes to be generic (`[MyAttribute]`). - [Extended `nameof` scope in attributes](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/extended-nameof-scope.md): allows `nameof(parameter)` inside an attribute on a method or parameter (`[MyAttribute(nameof(parameter))] void M(int parameter) { }`). # C# 10.0 - .NET 6 and Visual Studio 2022 version 17.0 - [Record structs](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/record-structs.md) (`record struct Point(int X, int Y);`, `var newPoint = point with { X = 100 };`). - [With expression](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/record-structs.md#allow-with-expression-on-structs) on structs and anonymous types. - [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# 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* 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. - [Default interface members](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/default-interface-methods.md): interfaces can now have members with default implementations, as well as static/private/protected/internal members except for state (ie. no fields). - [Recursive patterns](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/patterns.md): positional and property patterns allow testing deeper into an object, and switch expressions allow for testing multiple patterns and producing corresponding results in a compact fashion. - [Async streams](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/async-streams.md): `await foreach` and `await using` allow for asynchronous enumeration and disposal of `IAsyncEnumerable` collections and `IAsyncDisposable` resources, and async-iterator methods allow convenient implementation of such asynchronous streams. - [Enhanced using](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/using.md): a `using` declaration is added with an implicit scope and `using` statements and declarations allow disposal of `ref` structs using a pattern. - [Ranges and indexes](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md): the `i..j` syntax allows constructing `System.Range` instances, the `^k` syntax allows constructing `System.Index` instances, and those can be used to index/slice collections. - [Null-coalescing assignment](https://github.com/dotnet/csharplang/issues/34): `??=` allows conditionally assigning when the value is null. - [Static local functions](https://github.com/dotnet/csharplang/issues/1565): local functions modified with `static` cannot capture `this` or local variables, and local function parameters now shadow locals in parent scopes. - [Unmanaged generic structs](https://github.com/dotnet/csharplang/issues/1744): generic struct types that only have unmanaged fields are now considered unmanaged (ie. they satisfy the `unmanaged` constraint). - [Readonly members](https://github.com/dotnet/csharplang/issues/1710): individual members can now be marked as `readonly` to indicate and enforce that they do not modify instance state. - [Stackalloc in nested contexts](https://github.com/dotnet/csharplang/issues/1412): `stackalloc` expressions are now allowed in more expression contexts. - [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 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/csharpstandard/blob/draft-v6/standard/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) ================================================ FILE: README.md ================================================ # 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) [![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. C# is designed by the C# Language Design Team (LDT) in close coordination with the [Roslyn](https://github.com/dotnet/roslyn) project, which implements the language. You can find: - Active C# language feature proposals in the [proposals folder](proposals) - Notes from C# language design meetings in the [meetings folder](meetings) - Summary of the [language version history here](Language-Version-History.md). If you discover bugs or deficiencies in the above, please leave an issue to raise them, or even better: a pull request to fix them. For *new feature proposals*, however, please raise them for [discussion](https://github.com/dotnet/csharplang/labels/Discussion), and *only* submit a proposal as an issue or pull request if invited to do so by a member of the Language Design Team (a "champion"). The complete design process is described [here](Design-Process.md). A shorter overview is below. ## Discussions 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 Discussion topic](https://github.com/dotnet/csharplang/discussions/new). 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. 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/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 When a member of the C# LDM finds that a proposal merits consideration by the broader team, 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. Proposals are always discussed in linked discussions, not in the champion issue. We didn't always follow this policy, so many champion issues will have discussion on them; we now lock issues to prevent new discussion from occurring on them. Each champion issue will have a discussion link on it. ## Design Process [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: - Parsing (if applicable) should be resilient to experimentation: typing should not cause crashes. - Include minimal tests demonstrating the feature at work end-to-end. - Include minimal IDE support (keyword coloring, formatting, completion). Once approved, a feature should be fully implemented in [Roslyn](https://github.com/dotnet/roslyn), and fully specified in the [language specification](spec), whereupon the proposal is moved into the appropriate folder for a completed feature, e.g. [C# 7.1 proposals](proposals/csharp-7.1). **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 compatibility 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. ## Language Specification The current ECMA-334 specification can be found in markdown form on the [C# Language Standard](https://github.com/dotnet/csharpstandard/) repository. ## Implementation The reference implementation of the C# language can be found in the [Roslyn repository](https://github.com/dotnet/roslyn). This repository also tracks the [implementation status for language features](https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md). Until recently, that was also where language design artifacts were tracked. Please allow a little time as we move over active proposals. ================================================ FILE: meetings/2013/LDM-2013-10-07.md ================================================ # C# Language Design Notes for Oct 7, 2013 ## Agenda We looked at a couple of feature ideas that either came up recently or deserved a second hearing. 1. Invariant meaning of names <_scrap the rule_> 2. Type testing expression <_can’t decide on good syntax_> 3. Local functions <_not enough scenarios_> 4. nameof operator <_yes_> ## Invariant meaning of names C# has a somewhat unique and obscure rule called “invariant meaning in blocks” (documented in section 7.6.2.1 of the language specification) which stipulates that if a simple name is used to mean one thing, then nowhere in the immediately enclosing block can the same simple name be used to mean something else. The idea is to reduce confusion, make cut & paste refactoring a little more safe, and so on. It is really hard to get data on who has been saved from a mistake by this rule. On the other hand, everyone on the design team has experience being limited by it in scenarios that seemed perfectly legit. The rule has proven to be surprisingly expensive to implement and uphold incrementally in Roslyn. This has to do with the fact that it cannot be tied to a declaration site: it is a rule about use sites only, and information must therefore be tracked per use – only to establish in the 99.9% case that no, the rule wasn’t violated with this keystroke either. ### Conclusion The invariant meaning rule is well intentioned, but causes significant nuisance for what seems to be very little benefit. It is time to let it go. ## Type testing expressions With declaration expressions you can now test the type of a value and assign it to a fresh variable under the more specialized type, all in one expression. For reference types and nullable value types: ``` c# if ((var s = e as string) != null) { … s … } // inline type test ``` For non-nullable value types it is a little more convoluted, but doable: ``` c# if ((var i = e as int?) != null) { … i.Value … } // inline type test ``` One can imagine a slightly nicer syntax using a `TryConvert` method: ``` c# if (MyHelpers.TryConvert(e, out string s)) { … s … } ``` The signature of the `TryConvert` method would be something like ``` c# public static bool TryConvert(TSource src, out TResult res); ``` The problem is that you cannot actually implement `TryConvert` efficiently: It needs different logic depending on whether `TResult` is a non-nullable value type or a nullable type. But you cannot overload a method on constraints alone, so you need two methods with different names (or in different classes). This leads to the idea of having dedicated syntax for type testing: a syntax that will a) take an expression, a target type and a fresh variable name, b) return a boolean for whether the test succeeds, c) introduce a fresh variable of the target type, and d) assign the converted value to the fresh variable if possible, or the default value otherwise. What should that syntax be? A previous proposal was an augmented version of the “is” operator, allowing an optional variable name to be tagged onto the type: ``` c# if (e is string s) { … s … } // augmented is operator ``` Opinions on this syntax differ rather wildly. While we agree that some mix of “is” and “as” keywords is probably the way to go, no proposal seems appealing to everyone involved. Here are a few: ``` c# e is T x T x is e T e as x ``` (A few sillier proposals were made: ``` c# e x is T T e x as ``` But this doesn’t feel like the right time to put Easter eggs in the language.) ### Conclusion Probably 90% of cases are with reference (or nullable) types, where the declaration-expression approach is not too horrible. As long as we cannot agree on a killer syntax, we are fine with not doing anything. ## Local functions When we looked at local functions on Apr 15, we lumped them together with local class declarations, and dismissed them as a package. We may have given them somewhat short shrift, and as we have had more calls for them we want to make sure we do the right thing with local functions in their own right. A certain class of scenarios is where you need to declare a helper function for a function body, but no other function needs it. Why would you need to declare a helper function? Here are a few scenarios: * Task-returning functions may be fast-path optimized and not implemented as async functions: instead they delegate to an async function only when they cannot take the fast path. * Iterators cannot do eager argument validation so they are almost always wrapped in a non-iterator function which does validation and delegates to a private iterator. * Exception filters can only contain expressions – if they need to execute statements, they need to do it in a helper function. Allowing these helper functions to be declared inside the enclosing method instead of as private siblings would not only avoid pollution of the class’ namespace, but would also allow them to capture type parameters, parameters and locals from the enclosing method. Instead of writing ``` c# public static IEnumerable Filter(IEnumerable s, Func p) { if (s == null) throw new ArgumentNullException("s"); if (p == null) throw new ArgumentNullException("p"); return FilterImpl(s, p); } private static IEnumerable FilterImpl(IEnumerable s, Func p) { foreach (var e in s) if (p(e)) yield return e; } ``` You could just write this: ``` c# public static IEnumerable Filter(IEnumerable s, Func p) { if (s == null) throw new ArgumentNullException("s"); if (p == null) throw new ArgumentNullException("p"); IEnumerable Impl() // Doesn’t need unique name, type params or params { foreach (var e in s) // s is in scope if (p(e)) yield return e; // p is in scope } return Impl(s, p); } ``` The underlying mechanism would be exactly the same as for lambdas: we would generate a display class with a method on it. The only difference is that we would not take a delegate to that method. While it is nicer, though, it is reasonable to ask if it has that much over private sibling methods. Also, those scenarios probably aren’t super common. ### Inferred types for lambdas This did bring up the discussion about possibly inferring a type for lambda expressions. One of the reasons they are so unfit for use as local functions is that you have to write out their delegate type. This is particularly annoying if you want to immediately invoke the function: ``` c# ((Func)(x => x*x))(3); // What??!? ``` VB infers a type for lambdas, but a fresh one every time. This is ok only because VB also has more lax conversion rules between delegate types, and the result, if you are not careful, is a chain of costly delegate allocations. One option would be to infer a type for lambdas only when there happens to be a suitable `Func<…>` or `Action<…>` type in scope. This would tie the compiler to the pattern used by the BCL, but not to the BCL itself. It would allow the BCL to add more (longer) overloads in the future, and it would allow others to add different overloads, e.g. with ref and out parameters. ### Conclusion At the end of the day we are not ready to add anything here. No local functions and no type inference for lambdas. ## The nameof operator The `nameof` operator is another feature that was summarily dismissed as a tag-along to a bigger, more unwieldy feature: the legendary `infoof` operator. ``` c# PropertyInfo info = infoof(Point.X); string name = nameof(Point.X); // "X" ``` While the `infoof` operator returns reflection information for a given language element, `nameof` just returns its name as a string. While this seems quite similar at the surface, there is actually quite a big difference when you think about it more deeply. So let’s do that! First of all, let’s just observe that when people ask for the `infoof` operator, they are often really just looking to get the name. Common scenarios include throwing `ArgumentException`s and `ArgumentNullException`s (where the parameter name must be supplied to the exception’s constructor), as well as firing `Notify` events when properties change, through the `INotifyPropertyChanged` interface. The reason people don’t want to just put strings in their source code is that it is not refactoring safe. If you change the name, VS will help you update all the references – but it will not know to update the string literal. If the string is instead provided by the compiler or the reflection layer, then it will change automatically when it’s supposed to. At a time when we are trying to minimize the use of reflection, it seems wrong to add a feature, `infoof`, that would contribute significantly to that use. `nameof` would not suffer from that problem, as the compiler would just replace it with a string in the generated IL. It is purely a design time feature. There are deeper problems with `infoof`, pertaining to how the language element in question is uniquely identified. Let’s say you want to get the `MethodInfo` for an overload of a method `M`. How do you designate which overload you are talking about? ``` c# void M(int x); void M(string s); var info = infoof(M…); // Which M? How do you say it? ``` We’d need to create a whole new language for talking about specific overloads, not to mention members that don’t have names, such as user defined operators and conversions, etc. Again, though, `nameof` does not seem to have that problem. It is only for named entities (why else would you want the name), and it only gives you the name, so you don’t need to choose between overloads, which all have the same name! All in all, therefore, it seems that the concerns we have about `infoof` do not apply to `nameof`, whereas much (most?) of its value is retained. How exactly would a `nameof` operator work? ### Syntax We would want the operator to be syntactically analogous to the existing `typeof(…)`. This means we would allow you to say `nameof(something)` in a way that at least sometimes would parse as an invocation of a method called `nameof`. This is ambiguous with existing code only to the extent that such a call binds. We would therefore take a page out of `var`’s book and give priority to the unlikely case that this binds as a method call, and fall back to `nameof` operator semantics otherwise. It would probably be ok for the IDE to always highlight `nameof` as a keyword. The operand to `nameof` has to be “something that has a name”. In the spirit of the `typeof` operator we will limit it to a static path through names of namespaces, types and members. Type arguments would never need to be provided, but in every name but the last you may need to provide “generic dimension specifiers” of the shape `<,,,>` to disambiguate generic types overloaded on arity. The grammar would be something like the following: > _nameof-expression:_ > * `nameof` `(` _identifier_ `)` > * `nameof` `(` _identifier_ `::` _identifier_ `)` > * `nameof` `(` _unbound-type-name_ `.` _identifier_ `)` Where _unbound-type-name_ is taken from the definition of `typeof`. Note that it always ends with an identifier. Even if that identifier designates a generic type, the type parameters (or dimension specifiers) are not and cannot be given. ### Semantics It is an error to specify an operand to `nameof(…)` that doesn’t “mean” anything. It can designate a namespace, a type, a member, a parameter or a local, as long as it is something that is in fact declared. The result of the `nameof` expression would always be a string containing the final identifier of the operand. There are probably scenarios where it would be desirable to have a “fully qualified name”, but that would have to be constructed from the parts. After all, what would be the syntax used for that? C# syntax? What about VB consumers, or doc comments, or comparisons with strings provided by reflection? Better to leave those decisions outside of the language. ### Conclusion We like the `nameof` operator and see very few problems with it. It seems easy to spec and implement, and it would address a long-standing customer request. With a bit of luck, we would never (or at least rarely) hear requests for `infoof` again. While not the highest priority feature, we would certainly like to do this. ================================================ FILE: meetings/2013/LDM-2013-10-21.md ================================================ # C# Design Notes for Oct 21, 2013 ## Agenda Primary constructors is the first C# 6.0 feature to be spec’ed and implemented. Aleksey, who is doing the implementing joined us to help settle a number of details that came up during this process. We also did a rethink around Lightweight Dynamic. 1. Primary Constructors \ 2. Lightweight Dynamic \ ## Primary Constructors Primary constructors were first discussed in the design meeting on Apr 15, where we decided to include them, but have been around as a suggestion for much longer. It has great synergy with features such as initializers on auto-properties (May 20), because it puts constructor parameters in scope for initializers. The evolving “speclet” for primary constructors can be found in the “Csharp 6.0 Speclets” working document in the design notes archive (see link above). It reflects a number of implementation choices, and also (at the time of the meeting) left some issues open, which we settled as best we could. Some decisions may change as we get experience with the feature – indeed the early focus on implementing this feature is so that we can incorporate feedback from usage. ### Partial types Primary constructors involve a parameter list on “the” class or struct header, as well as an optional argument list on “the” base class specification. But what if the type declaration is split over multiple partial types? There is different precedence with partial classes. Sometimes elements can occur in only one part (members), sometimes they must occur and be the “same” in all parts (type parameters), sometimes they are optional but must agree where present (accessibility and constraints) and sometimes they are cumulative over parts (attributes). There seems to be little value in allowing class parameters for primary constructors to occur in multiple parts – indeed it would require us to come up with a notion of sameness that only adds complication to the language. So class parameters are allowed only on one of the parts. Arguments to the base class are allowed only in the part where the class parameters occur. The class parameters are in scope in all the parts. This is also the approach that most resembles what is the case today: constructor parameters occur only in one place, and the privates used to hold their values are in scope in all the parts. ### User-provided constructor body As currently specified, primary constructors give rise to a constructor that is entirely compiler-generated. There is no syntax for the developer to specify statements to be executed as part of the primary constructor invocation. This is less of an issue than it might seem, since more than 90% of what people do in constructor bodies is probably what primary constructors now do for them – copying parameter values into private fields. Even initialization of other fields can now usually take place in initializers, because the constructor parameters are in scope for those. Even so, for the remaining scenarios the user would fall off a cliff: they would have to renounce primary constructors and turn them back into explicit constructors. This is a shame, and it is definitely worth considering what syntax we would use to fix this. A radical idea is to just allow statements directly in the class body. This would be weird for several reasons though. Aside from syntactic issues, there’s the problem of evaluation order relative to fields with initializers: initializers run before the base call, whereas statements in a constructor body usually run after, so interspersing them would have very surprising results. In reality we’d need to place the statements in a block, and the only question is how we “dress up” the block – if at all. Some ideas: ``` c# class C(int x): B(x) { // Repeat the full signature – redundant and error prone C(int x) { Register(this); } // Class name and empty parens – clashes with noarg constructor syntax, // but similar to static constructors C() { Register(this); } // Just the class name – a little unfamiliar C { Register(this); } // Prefix with ‘this’ keyword – again a bit unfamiliar this { Register(this); } // Just the block – succinct but mysterious – and has the eval order issue { Register(this); } } ``` Many of these options are reasonable. We’ll hold off though and see how the feature fares without user specified statements for now. ### Struct issues Structs always have a default constructor. Should we allow explicit constructors in a struct with a primary constructor to delegate to the default constructor instead of the primary constructor? I.e. is the following ok? ``` c# struct S(int x) { public S(bool b) : this() { … } } ``` It seems that the restriction on explicit constructors to always chain to the primary constructor cannot be upheld just by requiring a this(…) initializer on them: we must also require that it isn’t referencing the default (no-arg) constructor. Also, members in structs are allowed to assign to ‘this’. Should we allow that also in constructor bodies when there are primary constructors? We probably cannot reasonably prevent it, and people who do this are already in the ‘advanced’ category. But we have to spec that it causes the primary constructor parameters to be reset to their default value. ### Attribute targets Currently, we allow “method” targets on constructors. We should allow an attribute on a class or a struct with a primary constructor to specify a “method” target in order to be applied to the underlying generated constructor. We should also allow attributes on the individual parameters of the primary constructor to specify a “field” target in order to be applied to the underlying field. In practice there may be optimizations so that the field is not generated when not needed, but the field target should force the field to be generated. ### Accessibility on primary constructor parameters There is an idea known from other languages (e.g. Scala, TypeScript) of allowing accessibility modifiers on class parameters. This would mean that a property is generated on the class with the name of the parameter, and the accessibility specified. This would make some scenarios even more succinct, but it is not a straightforward feature. For instance, parameter names are typically lower case and property names typically upper case. We think this is a feature that can be added later if we feel there is significant further gain. Right now we are willing to bet on the combination of primary constructors and more terse property specification (e.g. with expression bodied properties and initializers for auto-properties) as a sufficiently great advance in the specification of data-like classes and structs. ### Conclusion We continue to bake the details of primary constructors. ## Lightweight Dynamic We took a fresh look at Lightweight Dynamic, based on feedback from Dino, who used to work on the “old” dynamic infrastructure. The purpose of Lightweight Dynamic is to allow the behaviors of operations on an object to be specified programmatically, as part of the implementation of the object’s class. Dino pointed out that most behaviors can already be specified, using operator overloading, user defined conversions and indexers. Really, the “only” things missing are invocation and dotting. So instead of coming up with yet another new infrastructure for programmatic definition of object behaviors, we should just augment the one we’ve had all along with facilities for invocation and member access. Doing more with less is a pretty attractive idea! So we experimented with what that would look like. ### Invokers It seems reasonable to allow specification of what it means to invoke an object. The scenario seems similar to indexers, which are a special kind of instance members that can be abstract and virtual, take arguments, and be overloaded on signature. The main difference would be that parameters are specified in round parentheses, and that the body is simply a method body, rather than a pair of accessors: ``` c# public int this(int x) { return x*x; } // allow instances to be invoked with ints ``` One could imagine using this feature to build alternatives to delegate types, but more interestingly, this could provide semi-typed invocation capabilities for e.g. service calls: ``` c# public object this(params object[] args) { … } // generalized dynamic invoker ``` While it is straightforward to imagine this feature, it is also of relatively limited value: You can always provide an explicit Invoke method that the user can call at little extra cost. ``` c# myOperation.Invoke(5, "Hello"); // instead of myOperation(5, "Hello"); ``` Even so, it is interesting to consider. ### Member access For programmatic member access it seems superficially attractive – or at least powerful – to allow overriding of the dot operator. Maybe in the form of some kind of operator overload? ``` c# public static JsonObject operator .(JsonObject receiver, string name) { … } ``` The problem of course is that dot is such a fundamental operator. If you overload it to hide the native meaning, you can hardly do anything with the underlying object. How would you even implement the dot operator without using the built in dot for accessing state etc. on the object? You could of course have rules like we do for events, where the meaning is different on the inside and the outside. But that is not exactly elegant. The fact of the matter is that overriding dot on a statically typed class is simply too disruptive. Here is where Visual Basic may provide some interesting inspiration. The typical way you’d implement the dot operator would be some sort of dictionary lookup, and in fact you’d probably be fine if dotting did the same as indexing with a string, only with slightly more elegant syntax. Well, VB has an operator for that: The lookup operator, `!`. We could essentially resign ourselves to not using dot for “lightweight dynamic” member access, instead using `!` as a just-as-short substitute. We would then simply define these two expressions to mean the same: ``` c# obj!x obj["x"] ``` If you want to implement dynamic behavior for member access, you just provide an indexer over strings. Your callers will have to use a `!` instead of a `.`, but that is probably a good thing: that way they can still access your “real” members using a normal dot. This was a real issue with the Lightweight dynamic model we’ve explored before, one that we had yet to find a good solution to. ### Conclusion We still need to dig deeper here, but we think that adding the `!` lookup operator as a short hand for indexing with a string may quite possibly be the only work we have to do in the language for lightweight dynamic! That is quite possibly the biggest reduction of work associated with a feature we’ve ever accomplished in a single sitting! ================================================ FILE: meetings/2013/LDM-2013-11-04.md ================================================ # C# Design Notes for Nov 4, 2013 ## Agenda Next up for implementation are the features for auto-properties and function bodies, both of which go especially well with primary constructors. For the purpose of spec’ing those, we nailed down a few remaining details. We also took another round discussing member access in the lightweight dynamic scenario. 1. Initialized and getter-only auto-properties \
2. Expression-bodied function members \
3. Lightweight dynamic \ ## Initialized and getter-only auto-properties We are allowing initializers for auto-properties, and we are allowing getter-only auto-properties, where the underlying field is untouched after the initializer has run. This was last discussed on May 20, and allows declarations like this: ``` c# public bool IsActive { get; } = true; ``` In order to spec these feature additions there are a couple of questions to iron out. ## Getter-only auto-properties without initializers Initializers will be optional on get-set auto-properties. Should we allow them to be left out on getter-only properties? ``` c# public bool IsActive { get; } // No way to get a non-default value in there ``` There’s a symmetry and simplicity argument that we should allow this. However, anyone writing it is probably making a mistake, and even if they really meant it, they (and whoever inherits their code) are probably better off explicitly initializing to the default value. ### Conclusion We’ll disallow this. ### Is the backing field of getter-only auto-properties read-only? There’s an argument for fewer differences with get-set auto-properties. However, the field is really never going to change, and any tool that works on the underlying field (e.g. at the IL level) would benefit from seeing that it is indeed readonly. Readonly does not prevent setting through reflection, so deserialization wouldn’t be affected. ### Conclusion Let’s make them readonly. There’s a discrepancy with VB’s decision here, which should be rationalized. ## Expression-bodied function members This feature would allow the body of methods and of getter-only properties to be expressed very concisely with a single expression. This feature was last discussed on April 15, and would allow code like this: ``` c# public class Point(int x, int y) { public int X => x; public int Y => y; public double Dist => Math.Sqrt(x * x + y * y); public Point Move(int dx, int dy) => new Point(x + dx, y + dy); } ``` Again, there are a few details to iron out. ### Are we happy with the syntax for expression-bodied properties? The syntax is so terse that it may not be obvious to the reader that it is a property at all. It is just one character off from a field with an initializer, and lacks the telltale get and/or set keywords. One could imagine a slightly more verbose syntax: ``` c# public int X { get => x; } ``` This doesn’t read quite as well, and gives less of an advantage in terms of terseness. ### Conclusion We’ll stick with the terse syntax, but be open to feedback if people get confused. ## Which members can have expression bodies? While methods and properties are clearly the most common uses, we can imagine allowing expression bodies on other kinds of function members. For each kind, here are our thoughts. ### Operators: Yes Operators are like static methods. Since their implementations are frequently short and always have a result, it makes perfect sense to allow them to have expression bodies: ``` c# public static Complex operator +(Complex a, Complex b) => a.Add(b); ``` ### User defined conversions: Yes Conversions are just a form of operators: ``` c# public static implicit operator string(Name n) => n.First + " " + n.Last; ``` ### Indexers: Yes Indexers are like properties, except with parameters: ``` c# public Customer this[Id id] => store.LookupCustomer(id); ``` ### Constructors: No Constructors have syntactic elements in the header in the form of this(…) or base(…) initializers which would look strange just before a fat arrow. More importantly, constructors are almost always side-effecting statements, and don’t return a value. ### Events: No Events have add and remove accessors. Both must be specified, and both would be side-effecting, not value returning. Not a good fit. ### Finalizers: No Finalizers are side effecting, not value returning. ### Conclusion To summarize, expression bodies are allowed on methods and user defined operators (including conversions), where they express the value returned from the function, and on properties and indexers where they express the value returned from the getter, and imply the absence of a setter. ### void-returning methods Methods returning void, and async methods returning task, do not have a value-producing return statement. In that respect they are like some of the member kinds we rejected above. Should we allow them all the same? ### Conclusion We will allow these methods to have expression bodies. The rule is similar to expression-bodied lambdas: the body has to be a statement expression; i.e. one that is “certified” to be executed for the side effect. ## Lightweight dynamic member access At the design meeting on Oct 21, we discussed options for implementing “user-defined” member access. There are two main directions: - Allow users to override the meaning of dot - Introduce a “dot-like” syntax for invoking string indexers The former really has too many problems, the main one being what to do with “real” dot. Either we make “.” retain is current meaning and only delegate to the user-supplied meaning when that fails. But that means the new dot does not get a full domain of names. Or we introduce an escape hatch syntax for “real dot”. But then people would start using that defensively everywhere, especially in generated code. It also violates substitutability where o.x suddenly means something else on a subtype than a supertype. We strongly prefer the latter option, providing a “dot-like” syntax that translates into simply invoking a string indexer with the provided identifier as a string. This has the added benefit of working with existing types that have string indexers. The big question of course is what dot-like syntax. Here are some candidates we looked at in order to home in on a design philosophy: ``` c# x!Foo // Has a dot, but not as a separate character x."Foo" // That looks terrible, too much like any value could be supplied x.[Foo] // Doesn’t feel like Foo is a member name x.#Foo // Not bad x.+Foo // Hmmmm ``` Some principles emerge from this exercise: - Using "…" or […] kills the illusion of it being a member name - Having characters after the identifier breaks the flow - Using a real dot feels right, and would help with the tooling experience - What comes after that real dot really matters! ### Conclusion We like the “dot-like” syntax approach to LW dynamic member access, and we think it should be of the form x.\Foo for some value of \. ================================================ FILE: meetings/2013/LDM-2013-12-16.md ================================================ # C# Design Notes for Dec 16, 2013 ## Agenda This being the last design meeting of the year, we focused on firming up some of the features that we’d like to see prototyped first, so that developers can get going on implementing them. 1. Declaration expressions <_reaffirmed scope rules, clarified variable introduction_> 2. Semicolon operator <_reaffirmed enclosing parentheses_> 3. Lightweight dynamic member access <_decided on a syntax_> ## Declaration expressions On Sep 23 we tentatively decided on rules for what the scope is when a variable is introduced by a declaration expression. Roughly speaking, the rules put scope boundaries around “structured statements”. While this mostly makes sense, there is one idiom, the “guard clause” pattern, that falls a little short here: ``` c# if (!int.TryParse(s, out int i)) return false; // or throw, etc. … // code ideally consuming i ``` Since the variable `i` would be scoped to the if statement, using a declaration statement to introduce it inside of the if would not work. We talked again about whether to rethink the scope rules. Should we have a different rule to the effect essentially of the variable declaration being introduced “on the preceding statement”? I.e. the above would be equivalent to ``` c# int i; if (!int.TryParse(s, out i)) return false; // or throw, etc. … // code consuming i ``` This opens its own can of worms. Not every statement _has_ a preceding statement (e.g. when being nested in another statement). Should it bubble up to the enclosing statement recursively? Should it introduce a block around the current statement to hold the generated preceding statement, etc. More damning to this idea is the issue of initialization. If a variable with an initializer introduced in e.g. a while loop body is understood to really be a single variable declared outside of the loop, then that same variable would be re-initialized every time around the loop. That seems quite counterintuitive. This brings us to the related issue about variable lifetimes. When a variable is introduced somewhere in a loop, is it a fresh variable each time around? It would probably seem strange if it weren’t. In fact we took a slight breaking change in C# 5.0 in order to make that the case for the loop variable in `foreach`, because the alternative didn’t make sense to people, and tripped them up when they were capturing the variable in lambdas, etc. ### Conclusion We keep the scope rules described earlier, despite the fact that the guard clause example doesn’t work. We’ll look at feedback on the implementation and see if we need to adjust. On variable lifetimes, each iteration of a loop will introduce its own instance of a variable introduced in the scope of that loop. The only exception is if it occurs in the initializer of a for loop, because that part is evaluated only on entry to the loop. ## Semicolon operator We previously decided that a sequence of expressions separated by semicolons need to be enclosed in parentheses. This is so that we don’t get into situations where e.g. commas and semicolons are interspersed among expressions and it isn’t visually clear what the precedence is. We discussed briefly whether those parentheses are necessary even when there is other bracketing around the semicolon-separated expressions. ### Conclusion It’s not worth having special rules for this. Instead, semicolons are a relatively straightforward addition to parenthesized expressions – something like this: > _parenthesized-expression:_ > * `(` *expression-sequence*\_opt expression_ `)` > _expression-sequence:_ > * *expression-sequence*\_opt _statement-expression_ `;` > * *expression-sequence*\_opt _declaration-expression_ `;` ## Lightweight dynamic member access On Nov 4 we decided that lightweight member access should take the form `x.Foo` for some value of ``. One candidate for the glyph is `#`, so that member access would look like this: ``` c# payload.#People[i].#Name ``` However, `#` plays really badly with preprocessor directives. It seems almost impossible to come up with syntactic rules that reconcile with the deep lexer-based recognition of `#`-based preprocessor directives. After searching the keyboard for a while, we settled on ‘`$`’ as a good candidate. It sort of invokes a “string” aspect in a tip of the hat to classic Basic: ``` c# payload.$People[i].$Name ``` One key aspect of dynamicness that we haven’t captured so far is the ability to declaratively construct an object with “lightweight dynamic members”, i.e. with entries accessible with string keys. A core syntactic insight here is to think of `$Foo` above as a unit – as a “lightweight dynamic member name”. What this enables is to think of object construction in terms of object initializers – where the member names are dynamic: ``` c# var json = new JsonObject { $foo = 1, $bar = new JsonArray { "Hello", "World" }, $baz = new JsonObject { $x = 1, $y = 2 } }; ``` We could also consider allowing a free standing `$Foo` in analogy with a free standing simple name being able to reference an enclosing member in the implicit meaning of `this.$Foo`. This seems a little over the top, so we won’t do that for now. One thing to consider is that objects will sometimes have keys that aren’t identifiers. This is quite common in Json payloads. That’s fine as far as member access is concerned: just fall back to indexing syntax when necessary: ``` c# payload.$People[i].["first name"] ``` It would be nice to also be able to initialize such “members” in object initializers. This leads to the idea of a generalized “dictionary initializer” syntax in object initializers: ``` c# var payload = new JsonObject { ["first name"] = "Donald", ["last name"] = "Duck", $city = "Duckburg" // equivalent to ["city"] = "Duckburg" }; ``` So just as `x.$Foo` is a shorthand for `x["Foo"]` in expressions, `$Foo=value` is shorthand for `["Foo"]=value` in object initializers. That syntax in turn is a generalized dictionary initializer syntax, that lets you index and assign on the newly created object, so that the above is equivalent to ``` c# var __tmp = new JsonObject(); __tmp["first name"] = "Donald"; __tmp["last name"] = "Duck"; __tmp["city"] = "Duckburg"; var payload = __tmp; ``` It could be used equally well with non-string indexers. A remaining nuisance is having to write all the type names during construction. Could some of them be inferred, or a default be chosen? That’s a topic for a later time. ### Conclusion We’ll introduce a notion of dictionary initializers `[index]=value`, which are indices enclosed in `[…]` being assigned to in object initializers. We’ll also introduce a shorthand `x.$Foo` for indexing with strings `x["Foo"]`, and a shorthand `$Foo=value` for a string dictionary initializer `["Foo"]=value`. ================================================ FILE: meetings/2013/README.md ================================================ # C# Language Design Notes for 2013 Overview of meetings and agendas for 2013 ## Oct 7, 2013 [C# Language Design Notes for Oct 7, 2013](LDM-2013-10-07.md) 1. Invariant meaning of names <*scrap the rule*> 2. Type testing expression <*can’t decide on good syntax*> 3. Local functions <*not enough scenarios*> 4. nameof operator <*yes*> [C# Language Design Notes for Oct 21, 2013](LDM-2013-10-21.md) 1. Primary Constructors <*fleshed out a few more details*> 2. Lightweight Dynamic <*we examined a much simpler approach*> ## Nov 4, 2013 [C# Language Design Notes for Nov 4, 2013](LDM-2013-11-04.md) 1. Initialized and getter-only auto-properties <*details decided*> 2. Expression-bodied function members <*details decided*> 3. Lightweight dynamic <*member access model and syntax discussed*> ## Dec 16, 2013 [C# Language Design Notes for Dec 16, 2013](LDM-2013-12-16.md) 1. Declaration expressions <*reaffirmed scope rules, clarified variable introduction*> 2. Semicolon operator <*reaffirmed enclosing parentheses*> 3. Lightweight dynamic member access <*decided on a syntax*> ================================================ FILE: meetings/2014/LDM-2014-01-06.md ================================================ # C# Design Notes for Jan 6, 2014 Notes are archived [here](https://roslyn.codeplex.com/wikipage?title=CSharp%20Language%20Design%20Notes). ## Agenda In this meeting we reiterated on the designs of a couple of features based on issues found during implementation or through feedback from MVPs and others. 1. Syntactic ambiguities with declaration expressions <_a solution adopted_> 2. Scopes for declaration expressions <_more refinement added to rules_> # Syntactic ambiguities with declaration expressions There are a couple of places where declaration expressions are grammatically ambiguous with existing expressions. The knee-jerk reaction would be to prefer the existing expressions for compatibility, but those turn out to nearly always lead to semantic errors later. There are two kinds of full ambiguities (i.e. ones that don’t resolve with more lookahead): ``` c# a * b // multiplication expression or uninitialized declaration of pointer? a < b > c // nested comparison or uninitialized declaration of generic type? ``` The latter one exists also in a method argument version that seems more realistic: ``` c# a ( b < c , d > e ) // two arguments or one declaration expression? ``` However, in all these cases the expression, when interpreted as a declaration expression, is uninitialized. This means that in the vast majority of cases (except for structs with no accessible members) it will be considered unassigned. Which means that it will be a semantic error for it to occur as a value: it has to occur in one of the few places where an unassigned variable is allowed: 1. On the left hand side of an assignment expression 2. As an argument to an out parameter 3. In parentheses in one of the previous positions Those are places where a multiplication or comparison expression cannot currently occur, because they are always values, never variables. We can therefore essentially split the world neatly for the two interpretations of the ambiguous expressions: * If they occur in a place where a variable is required, they are parsed as declaration expressions * Everywhere else, they are parsed as they are today. This is a purely syntactic distinction. Rules of definite assignment etc. aren’t actually used by the compiler to decide, just by us designers to justify that the rule isn’t breaking. There is one potential future conflict we can imagine: If we start allowing ref returning methods and those include user defined overloads of operators, then you could imagine someone defining an overload of “`*`” that returns a ref, and would therefore give meaning to the expression `(a*b) = c`, even when interpreted as multiplication. The rules as proposed here would not allow that; they would try to see `(a*b)` as a parenthesized declaration expression of a variable `b` with pointer type `a*`. ### Conclusion We like the rule that parsing prefers declaration expressions in ambiguous cases _only_ in places where a variable is required: when occurring as an out argument, on the left hand side of an assignment, or nested in any number of parentheses within those. This is non-breaking, and doesn’t seem too harmful to the future design space. ## Scopes for declaration expressions The initial rules for declaration expression scopes are in need of some refinement in at least two scenarios: ``` c# if (…) m(int x = 1); else m(x = 2); // cross pollution between branches? while (…) m(int x = 1; x++); // survival across loop iterations? ``` We want to make sure that declarations are isolated between branches and loop iterations. This means we need to add more levels of scopes. Essentially, whenever an _embedded-expression_ occurs as an embedded expression (as opposed to where any _expression_ can occur), we want to introduce a new nested scope. Additionally, for for-loops we want to nest scopes for each of the clauses in the header: ``` c# for (int i = (int a = 0); i < (int b = 10); // i and a in scope here i += (int c = 1)) // i, a and b in scope here (int d += i); // i, a and b but not c in scope here ``` It’s as if the for loop was rewritten as ``` c# { int i = (int a = 0); while (i < (int b = 10)) { { i += (int c = 1)); } (int d += i); } } ``` ### Conclusion We’ll adopt these extra scope levels which guard against weird spill-over. ================================================ FILE: meetings/2014/LDM-2014-02-03.md ================================================ # C# Language Design Notes for Feb 3, 2014 ## Agenda We iterated on some of the features currently under implementation 1. Capture of primary constructor parameters <_only when explicitly asked for with new syntax_> 1. Grammar around indexed names <_details settled_> 1. Null-propagating operator details <_allow indexing, bail with unconstrained generics_> ## Capture of primary constructor parameters Primary constructors as currently designed and implemented lead to automatic capture of parameters into private, compiler-generated fields of the object whenever those parameters are used after initialization time. It is becoming increasingly clear that this is quite a dangerous design. To illustrate, what’s wrong with this code? ``` c# public class Point(int x, int y) { public int X { get; set; } = x; public int Y { get; set; } = y; public double Dist => Math.Sqrt(x * x + y * y); public void Move(int dx, int dy) { x += dx; y += dy; } } ``` This appears quite benign, but is in fact catastrophically wrong. The use of `x` and `y` in `Dist` and `Move` causes these values to be captured as private fields. The auto-properties `X` and `Y` each cause their own backing fields to be generated, initialized with the `x` and `y` values passed in to the primary constructors. But from then on, `X` and `Y` lead completely distinct lives from `x` and `y`. Assignments to the `X` and `Y` properties will cause them to be observably updated, but the value of `Dist` remains unchanged. Conversely, changes through the `Move` method will reflect in the value of `Dist`, but not affect the value of the properties. The way for the developer to avoid this is to be extremely disciplined about not referencing `x` and `y` except in initialization code. But that is like giving them a gun already pointing at their foot: sooner or later it will go subtly wrong, and they will have hard to find bugs. There are other incarnations of this problem, e.g. where the parameter is passed to the base class and captured multiple times. There are also other problems with implicit capture: we find, especially from MVP feedback, that people quickly want to specify certain things about the generated fields, such as readonly-ness, attributes, etc. We could allow those on the parameters, but they quickly don’t look like parameters anymore. The best way for us to deal with this is to simply disallow automatic capture. The above code would be disallowed, and given the same declarations of `x`, `y`, `X` and `Y`, `Dist` and `Move` would have to written in terms of the properties: ``` c# public double Dist => Math.Sqrt(X * X + Y * Y); public void Move(int dx, int dy) { X += dx; Y += dy; } ``` Now this raises a new problem. What if you want to capture a constructor parameter in a private field and have no intention of exposing it publicly. You can do that explicitly: ``` c# public class Person(string first, string last) { private string _first = first; private string _last = last; public string Name => _first + " " + _last; } ``` The problem is that the “good” lower case names in the class-level declaration space are already taken by the parameters, and the privates are left with (what many would consider) less attractive naming options. We could address this in two ways (that we can think of) in the primary constructor feature: 1. Allow primary constructor parameters and class members to have the same names, with the excuse that their lifetimes are distinct: the former are only around during initialization, where access to the latter through this is not yet allowed. 1. Introduce a syntax for explicitly capturing a parameter. If you ask for it, presumably you thought through the consequences. The former option seems mysterious: two potentially quite different entities get to timeshare on the same name? And then you’d get confusing initialization code like this: ``` c# private string first = first; // WHAT??? private string last = last; ``` It seems that the latter option is the better one. We would allow field-like syntax to occur in a parameter list, which is a little odd, but kind of says what it means. Specifically specifying an accessibility on a parameter (typically private) would be what triggers capture as a field: ``` c# public class Person(private string first, private string last) { public string Name => _first + " " + _last; } ``` Once there’s an accessibility specified, we would also allow other field modifiers on the parameter; readonly probably being the most common. Attributes could be applied to the field in the same manner as with auto-properties: through a field target. Conclusion We like option two. Let’s add syntax for capture and not do it implicitly. Grammar for indexed names For the lightweight dynamic features, we’ve been working with a concept of “pseudo-member” or _indexed name_ for the `$identifier` notation. We will introduce this as a non-terminal in the grammar, so that the concept is reified. However, for the constructs that use it (as well as ordinary identifiers) we will create separate productions, rather than unify indexed names and identifiers under a common grammatical category. For the stand-alone dictionary initializer notation of `[expression]` we will not introduce a non-terminal. ## Null-propagating operator details Nailing down the design of the null-propagating operator we need to decide a few things: ### Which operators does it combine with? The main usage of course is with dot, as in `x?.y` and `x?.m(…)`. It also potentially makes sense for element access `x?[…]` and invocation `x?(…)`. And we also have to consider interaction with indexed names, as in `x?.$y`. We’ll do element access and indexed member access, but not invocation. The former two make sense in the context that lightweight dynamic is addressing. Invocation seems borderline ambiguous from a syntactic standpoint, and for delegates you can always get to it by explicitly calling Invoke, as in `d?.Invoke(…)`. ### Semantics The semantics are like applying the ternary operator to a null equality check, a null literal and a non-question-marked application of the operator, except that the expression is evaluated only once: ``` c# e?.m(…) => ((e == null) ? null : e0.m(…)) e?.x => ((e == null) ? null : e0.x) e?.$x => ((e == null) ? null : e0.$x) e?[…] => ((e == null) ? null : e0[…]) ``` Where `e0` is the same as `e`, except if `e` is of a nullable value type, in which case `e0` is `e.Value`. ### Type The type of the result depends on the type `T` of the right hand side of the underlying operator: * If `T` is (known to be) a reference type, the type of the expression is `T` * If `T` is (known to be) a non-nullable value type, the type of the expression is `T?` * If `T` is (known to be) a nullable value type, the type of the expression is `T` * Otherwise (i.e. if it is not known whether `T` is a reference or value type) the expression is a compile time error. ================================================ FILE: meetings/2014/LDM-2014-02-10.md ================================================ # C# Design Notes for Feb 10, 2014 ## Agenda 1. Design of using static <_design adopted_> 2. Initializers in structs <_allow in certain situations_> 3. Null-propagation and unconstrained generics <_keep current design_> ## Design of using static The “using static” feature was added in some form to the Roslyn codebase years ago, and sat there quietly waiting for us to decide whether to add it to the language. Now it’s coming out, it’s time to ensure it has the right design. ### Syntax Should the feature have different syntax from namespace usings, or should it be just like that, but just specifying a type instead? The downside of keeping the current syntax is that we need to deal with ambiguities between types and namespaces with the same name. That seems relatively rare, though, and sticking with current syntax definitely makes it feel more baked in: ``` c# using System.Console; ``` as opposed to, e.g.: ``` c# using static System.Console; ``` #### Conclusion We’ll stick with the current syntax. ### Ambiguities This leads to the question of how to handle ambiguities when there are both namespaces and types of a given name. We clearly need to prefer namespaces over types for compatibility reasons. The question is whether we make a choice at the point of the specified name, or whether we allow “overlaying” the type and the namespace, disambiguating at the next level down by preferring names that came from the namespace over ones from the type. #### Conclusion We think overlaps are sufficiently rare that we’ll go with the simple rule: A namespace completely shadows a type of the same name, and you can’t import the members of such a type. If this turns out to be a problem we’re free to loosen it up later. ### Which types can you import? Static classes, all classes, enums? It seems it is almost always a mistake to import non-static types: they will have names that are designed to be used with the type name, such as `Create`, `FromArray`, `Empty`, etc., that are likely to appear meaningless on their own, and clash with others. Enums are more of a gray area. Spilling the enum members to top-level would often be bad, and could very easily lead to massive name clashes, but sometimes it’s just what you want. #### Conclusion We’ll disallow both enums and non-static classes for now. ### Nested types Should nested types be imported as top-level names? #### Conclusion Sure, why not? They are often used by the very members that are being “spilled”, so it makes sense that they are spilled also. ### Extension methods Should extension methods be imported as extension methods? As ordinary static methods? When we first introduced extension methods, a lot of people asked for a more granular way of applying them. This could be it: get the extension methods just from a single class instead of the whole namespace. For instance: ``` c# using System.Linq.Enumerable; ``` Would import just the query methods for in-memory collections, not those for `IQueryable`. On the other hand, extension methods are designed to be used as such: you only call them as static methods to disambiguate. So it seems wrong if they are allowed to pollute the top-level namespace as static methods. On the _other_ other hand, this would be the first place in the language where an extension method wouldn’t be treated like a static method. #### Conclusion We will import extension methods as extension methods, but not as static methods. This seems to hit the best usability point. ## Initializers in structs Currently, field initializers aren’t allowed in structs. The reason is that initializers _look_ like they will be executed every time the struct is created, whereas that would not be the case: If the struct wasn’t `new`’ed, or it was `new`’ed with the default constructor, no user defined code would run. People who put initializers on fields might not be aware that they don’t always run, so it’s better to prevent them. It would be nice to have the benefits of primary constructors on structs, but that only really flies if the struct can make use of the parameters in scope through initializers. Also, we now have initializers for auto-properties, making the issue worse. What to do? We can never prevent people from having uninitialized structs, and the struct type authors still need to make sure that an uninitialized struct is meaningful. However, if a struct has user-defined constructors, chances are they know what they’re doing and initializers wouldn’t make anything worse. However, initializers would only run if the user-defined constructors don’t chain to the default constructor with `this()`. ### Conclusion Let’s allow field and property initializers in structs, but only if there is a user-defined constructor (explicit or primary) that does not chain to the default constructor. If people want to initialize with the default constructor first, they should call it from their constructor, rather than chain to it. ``` c# struct S0 { public int x = 5; } // Bad struct S1 { public int x = 5; S1(int i) : this() { x += i; } } // Bad struct S2 { public int x = 5; S2(int i) { this = new S2(); x += i; } } // Good struct S3(int i) { public int x = 5 + i; } // Good ``` ## Unconstrained generics in null-propagating operator We previously looked at a problem with the null-propagating operator, where if the member accessed is of an unconstrained generic type, we don’t know how to generate the result, and what its type should be: ``` c# var result = x?.Y; ``` The answer is different when `Y` is instantiated with reference types, non-nullable value types and nullable value types, so there’s nothing reasonable we can do when we don’t know which. The proposal has been raised to fall back to type `object`, and generate code that boxes (which is a harmless operation for values that are already of reference type). ### Conclusion This seems like a hack. While usable in some cases, it is weirdly different from the mainline semantics of the feature. Let’s prohibit and revisit if it becomes a problem. ================================================ FILE: meetings/2014/LDM-2014-04-21.md ================================================ # C# Language Design Notes for Apr 21, 2014 ## Agenda In this design meeting we looked at some of the most persistent feedback on the language features showcased in the BUILD CTP, and fixed up many of the most glaring issues. 1. Indexed members <_lukewarm response, feature withdrawn_> 2. Initializer scope <_new scope solves all kinds of problems with initialization_> 3. Primary constructor bodies <_added syntax for a primary constructor body_> 4. Assignment to getter-only auto-properties from constructors <_added_> 5. Separate accessibility for type and primary constructor <_not worthy of new syntax_> 6. Separate doc comments for field parameters and fields <_not worthy of new syntax_> 7. Left associative vs short circuiting null propagation <_short circuiting_> ## Indexed members The indexed member feature – `e.$x` and `new C { $x = e }` – has been received less than enthusiastically. People aren’t super happy with the syntax, but most of all they aren’t very excited about the feature. We came to this feature in a roundabout way, where it started out having much more expressiveness. For instance, it was the way you could declaratively create an object with values at given indices. But given the dictionary initializer syntax – `new C { ["x"] = e }` – the `$` syntax is again just thin sugar for using string literals in indexers. Is that worth new syntax? It seems not. ### Conclusion We’ll pull the feature. There’s little love for it, and we shouldn’t litter the language unnecessarily. If this causes an outcry, well that’s different feedback, and we can then act on that. ## Initializer scope There are a couple of things around primary constructors and scopes that are currently annoying: 1. You frequently want a constructor parameter and a (private) field with the same name. In fact we have a feature just for that – the so-called field parameters, where primary constructors annotated with an accessibility modifier cause a field to be also emitted. However, if you try to declare this manually, we give an error because members and primary constructor parameters are in the same declaration space. 2. We have special rules for primary constructor parameters, making it illegal to use them after initialization time, even though they are “in scope”. So in this code: ``` c# public class ConfigurationException(Configuration configuration, string message) : Exception(message) { private Configuration configuration = configuration; public override string ToString() => message + "(" + configuration + ")"; } ``` The declaration of the field `configuration` is currently an error, because it clashes with the parameter of the same name in the same declaration space, but it would be nice if it just worked. The use of `message` in a method body is and should be an error, but it would be preferable if that was a more natural consequence of existing scoping rules, instead of new specific restrictions. An idea to fix this is to introduce what we’ll call the ___initialization scope___. This is a scope and declaration space that is nested within the type declaration’s scope and declaration space, and which includes the parameters and base initializer arguments of a primary constructor (if any) and the expressions in all member initializers of the type. That immediately means that this line becomes legal and meaningful: ``` c# private Configuration configuration = configuration; ``` The _field_ `configuration` no longer clashes with the _parameter_ `configuration`, because they are no longer declared in the same declaration space: the latter’s is nested within the former’s. Moreover the reference to `configuration` in the initializer refers to the parameter, not the field, because while both are in scope, the parameter is nearer. Some would argue that a line like the above is a little confusing. You are using the same name to mean different things. That is a fair point. The best way to think of it is probably the corresponding line in a normal constructor body: ``` c# this.configuration = configuration; ``` Which essentially means the same thing. Just as we’ve gotten used to `this` disambiguating that line, we’ll easily get used to the leading modifier and type of the field declaration disambiguating the field initializer. The initialization scope also means that this line is naturally disallowed: ``` c# public override string ToString() => message + "(" + configuration + ")"; ``` Because the reference to `message` does not appear within the initialization scope, and the parameter is therefore not in scope. If there was a field with that name, the field would get picked up instead; it wouldn’t be shadowed by a parameter which would be illegal to reference. A somewhat strange aspect of the initialization scope is that it is textually discontinuous: it is made up of bits and pieces throughout a type declaration. Hopefully this is not too confusing: conceptually it maps quite clearly to the notion of “initialization time”. Essentially, the scope is made up of “the code that runs when the object is initialized”. There are some further desirable consequences of introducing the initialization scope: ### Field parameters The feature of field parameters is currently the only way to get a primary constructor parameter and a field of the same name: ``` c# public class ConfigurationException(private Configuration configuration, …) ``` With the initialization scope, the feature is no longer special magic, but just thin syntactic sugar over the field declaration above. If for some reason field parameters don’t work for you, you can easily fall back to an explicit field declaration with the same name as the parameter. It raises the question of whether we’d even _need_ field parameters, but they still seem like a nice shorthand. ### The scope of declaration expressions in initializers In the current design, each initializer provides its own isolated scope for declaration expressions: there was no other choice really. With the initialization scope, however, declaration expressions in initializers would naturally use that as their scope, allowing the use of locals to flow values between initializers. This may not be common, but you can certainly imagine situations where that comes in handy: ``` c# public class ConfigurationException(Configuration configuration, string message) : Exception(message) { private Configuration configuration = configuration; public bool IsRemote { get; } = (var settings = configuration.Settings)["remote"]; public bool IsAsync { get; } = settings["async"]; public override string ToString() => Message + "(" + configuration + ")"; } ``` The declaration expression in `IsRemote`’s initializer captures the result of evaluating `configuration.Settings` into the local variable `settings`, so that it can be reused in the initializer for `IsAsync`. We need to be a little careful about partial types. Since the “textual order” between different parts of a partial type is not defined, it does not seem reasonable to share variables from declaration expressions between different parts. Instead we should introduce a scope within each part of a type containing the field and property initializers contained in that part. This scope is then nested within the initializer scope, which itself covers all the parts. A similar issue needs to be addressed around the argument to the base initializer. Textually it occurs _before_ the member initializers, but it is evaluated _after_. To avoid confusion, the argument list needs to be in its own scope, nested inside the scope that contains the field and property initializers (of that part of the type). That way, locals introduced in the argument list will not be in scope in the initializers, and members introduced in the initializers cannot be used in the argument list (because their use would textually precede their declaration). ### Primary constructors in VB Importantly, the notion of the initialization scope would also make it possible to introduce primary constructors in VB. The main impediment to this has been that, because of case insensitivity, the restriction that primary constructor parameters could not coexist with members of the same name became too harsh. If you need both a parameter, a backing field and a property, you quickly run out of names! The initialization scope helps with that, by introducing a separate scope that the parameters can live in, so they no longer clash with other names. Unlike C#, VB today allows initializers to reference previously initialized fields. With the initialization scope this would still be possible, as long as there’s not a primary constructor parameter shadowing that field. And if there is, you probably want the parameter anyway. An if you don’t, you can always get at the field through `Me` (VB’s version of `this`). It is up to the VB design team whether to actually add primary constructors this time around, but it is certainly nice to have a model that will work in both languages. ### Conclusion The initialization scope solves many problems, and leaves the language cleaner and with less magic. This clearly outweighs the slight oddities it comes with. ## Primary constructor bodies By far the most commonly reported reason why people cannot use primary constructors is that they don’t allow for easy argument validation: there is simply no “body” within which to perform checks and throw exceptions. We could certainly change that. The simplest thing, syntactically, is to just let you write a block directly in the type body, and that block then gets executed when the object is constructed: ``` c# public class ConfigurationException(Configuration configuration, string message) : Exception(message) { { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } } private Configuration configuration = configuration; public override string ToString() => Message + "(" + configuration + ")"; } ``` This looks nice, but there is a core question we need to answer: when _exactly_ is that block executed? There seem to be two coherent answers to that, and we need to choose: 1. The block is an ___initializer body___. It runs before the base call, following the same textual order as the surrounding field and property initializers. You could even imagine allowing multiple of them interspersed with field initialization, and they can occur regardless of whether there is a primary constructor. 2. The block is a ___constructor body___. It is the body of the primary constructor and therefore runs after the base call. You can only have one, and only if there is a primary constructor that it can be part of. Both approaches have pros and cons. The initializer body corresponds to a similar feature in Java, and has the advantage that you can weed out bad parameters before you start digging into them or pass them to the base initializer (though arguments passed to the base initializer should probably be validated by the base initializer rather than in the derived class anyway). As an example of this issue, our previous example where an initializer digs into the contents of a primary constructor parameter, wouldn’t work if the validation was done in a constructor body, after initialization (here in a simplified version): ``` c# public bool IsRemote { get; } = configuration.Settings["remote"]; ``` If the passed-in `configuration` is null, this would yield a null reference exception before a constructor body would have a chance to complain (by throwing a better exception). Instead, in a constructor body interpretation, the initialization of `IsRemote` would either have to happen in the constructor body as well, following the check, or it would have to make copious use of the null propagating operator that we’re also adding: ``` c# public bool IsRemote { get; } = configuration?.Settings?["remote"] ?? false; ``` On the other hand, the notion of a constructor body is certainly more familiar, and it is easy to understand that the block is stitched together with the parameter list and the base initializer to produce the constructor declaration underlying the primary constructor. Moreover, a constructor body has access to fields and members, while `this` access during initialization time is prohibited. Therefore, a constructor body can call helper methods etc. on the instance under construction; also a common pattern. ### Conclusion At the end of the day we have to make a choice. Here, familiarity wins. While the initializer body approach has allure, it is also very much a new thing. Constructor bodies on the other hand work the way they work. The downsides have workarounds. So a constructor body it is. In a partial type, the constructor body must be in the same part as the primary constructor. Scope-wise, the constructor body is nested within the scope for the primary constructor’s base arguments, which in turn is nested within the scope for the field and property initializers of that part, which in turn is nested within the initialization scope that contains the primary constructor parameters: ``` c# partial class C(int x1) : B(int x3 = x1 /* x2 in scope but can’t be used */) { public int X0 { get; } = (int x2 = x1); { int x4 = X0 + x1 + x2 + x3; } } ``` Let’s look at the scopes (and corresponding declaration spaces) nested in each other here: * The scope `S4` spans the primary constructor body. It directly contains the local variable `x4`, and is nested within `S3`. * The scope `S3` spans `S4` plus the argument list to the primary constructor’s base initializer. It directly contains the local variable `x3`, and is nested within `S2`. * The scope `S2` spans `S3` plus all field and property initializers in this part of the type declaration. It directly contains the local variable `x2`, and is nested within `S1`. * The scope `S1` spans `S2` plus similar “`S2`’s” from other parts of the type declaration, plus the parameter list of the primary constructor. It directly contains the parameter `x1`, and is nested within `S0`. * The scope `S0` spans all parts of the whole type declaration, including `S1`. It directly contains the property `X0`. On top of this, the usual rule applies for local variables, that they cannot be used in a position that textually precedes their declaration. ## Assignment to getter-only auto-properties There are situations where you cannot use a primary constructor. We have to make sure that you do not fall off too steep of a cliff when you are forced to abandon primary constructor syntax and use an ordinary constructor. One of the main nuisances that has been pointed out is that the only way to initialize a getter-only auto-property is with an initializer. If you want to initialize it from constructor parameters, you therefore need to have a primary constructor, so those parameters can be in scope for initialization. If you cannot have a primary constructor, then the property cannot be a getter-only auto-property: You have to fall back to existing, more lengthy and probably less fitting property syntax. That’s a shame. The best way to level the playing field here is to allow assignment to getter-only auto-properties from within constructors: ``` c# public class ConfigurationException : Exception { private Configuration configuration; public bool IsRemote { get; } public ConfigurationException(Configuration configuration, string message) : base(message) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } this.configuration = configuration; IsRemote = configuration.Settings["remote"]; } } ``` The assignment to `IsRemote` would go directly to the underlying field (since there is no setter to call). Thus, semantics are a little different from assignment to get/set auto-properties, where the setter is called even if you assign from a constructor. The difference is observable if the property is virtual. We could restore symmetry by changing the meaning of assignment to a get/set auto-property to also go directly to the backing field, but that would be a breaking change. ### Conclusion Let’s allow assignment to getter-only auto-properties from constructor bodies. It translates into assignment directly to the underlying field (which is `readonly`). We are ok with the slight difference in semantics from get/set auto-property assignment. ## Separate accessibility on type and primary constructor There are scenarios where you don’t want the constructors of your type to have the same accessibility as the type. A common case is where the type is public, but the constructor is private or protected, object construction being exposed only through factories. Should we invent syntax so that a primary constructor can get a different accessibility than its type? ### Conclusion No. There is no elegant way to address this. This is a fine example of a scenario where developers should just fall back to normal constructor syntax. With the previous decisions above, we’ve done our best to make sure that that cliff isn’t too steep. ## Separate doc comments for field parameters and their fields Doc comments for a primary constructor parameter apply to the parameter. If the parameter is a field parameter, there is no way to add a doc comment that goes on the field itself. Should there be? ### Conclusion No. If the field needs separate doc comments, it should just be declared as a normal field. With the introduction of initialization scopes above, this is now not only possible but easy. ## Null propagating operator associativity What does the following mean? ``` c# var x = a?.b.c; ``` People gravitate to two interpretations, which each side maintains is perfectly intuitive and the only thing that makes sense. One interpretation is that `?.` is an operator much like `.`. It is left associative, and so the meaning of the above is roughly the same as ``` c# var x = ((var tmp = a) == null ? null : tmp.b).c; ``` In other words, we access `b` only if `a` is not null, but `c` is accessed regardless. This is obviously likely to lead to a null reference exception; after all the use of the null propagating operator indicates that there’s a likelihood that `a` is null. So advocates of the “left associative” interpretation would put a diagnostic on the code above, warning that this is probably bad, and pushing people to write, instead: ``` c# var x = a?.b?.c; ``` With a null-check again before accessing `c`. The other interpretation has been called “right associative”, but that isn’t exactly right (no pun intended): better to call it “short circuiting”. It holds that the null propagating operator should short circuit past subsequent member access (and invocation and indexing) when the receiver is null, essentially pulling those subsequent operations into the conditional: ``` c# var x = ((var tmp = a) == null ? null : tmp.b.c); ``` There are long discussions about this, which I will no attempt to repeat here. The “short circuiting” interpretation is slightly more efficient, and probably more useful. On the other hand it is more complicated to fit into the language, because it needs to “suck up” subsequent operations in a way those operations aren’t “used to”: since when would the evaluation of `e.x` not necessarily lead to `x` being accessed on `e`? So we’d need to come up with alternative versions of remote access, indexing and invocation that can represent being part of a short-circuited chain following a null propagating operator. ### Conclusion Despite the extra complexity and some disagreement on the design team, we’ve settled on the “short circuiting” interpretation. ================================================ FILE: meetings/2014/LDM-2014-05-07.md ================================================ # C# Language Design Notes for May 7, 2014 ## Agenda 1. protected and internal <_feature cut – not worth the confusion_> 2. Field parameters in primary constructors <_feature cut – we want to keep the design space open_> 3. Property declarations in primary constructors <_interesting but not now_> 4. Typeswitch <_Not now – more likely as part of a future more general matching feature_> ## protected and internal Protected and internal was never a feature we were super enthusiastic about. The CLR supports it and it seemed reasonable to surface it in the language. However, the syntactic options are not great. For every suggestion there are significant and good reasons why it doesn’t work. The community has been incredibly helpful in its creativity about names, as well as in pointing out their flaws. ### Conclusion We won’t do this feature. Guidance for the scenarios it addresses will be to use `internal`: the most important aspect is to hide the member from external consumers of the assembly. The `protected` aspect is more of a software engineering thing within the team. You could imagine at some point adding the protected aspect as an attribute, either recognized by the compiler or respected by a custom diagnostic. ## Field parameters in primary constructors Now that we’ve added the initialization scope to classes, it is no longer a problem to have primary constructor parameters with the same name as members. This removes most of the motivation for having the field parameters feature, where an explicit accessibility modifier on a parameter would indicate that there should additionally be a field of that name. ### Conclusion As the next topic demonstrates, there are more interesting things to consider using this design space for in the future. Let’s not occupy it now with this relatively unimportant feature. It is fine that people have to declare their fields explicitly. ## Property declarations in primary constructors While declaration of fields in the primary constructor parameter list is of limited value, it is very often the case that a constructor parameter is accompanied by a corresponding property. It might be nice if there was a shorthand for this. You could imagine very terse class declarations completely without bodies in some cases. A hurdle here is the convention that parameters are `camelCase` (start with lower case) and public properties are `PascalCase` (start with upper case). To be general, we’d need for each parameter to give not one but two names – something like this: ``` c# public class Point(int x: X, int y: Y); ``` Which would yield public getter-only properties named `X` and `Y` as well as constructor parameters `x` and `y` with which the properties are initialized. It would expand to this: ``` c# public class Point(int x, int y) { public int X { get; } = x; public int Y { get; } = y; } ``` This syntax looks fairly nice in the above example, but it gets a little unwieldy when the names are longer: ``` c# public class Person(string firstName: FirstName, string lastName: LastName); ``` Maybe we could live with not having separate parameter names. We could reuse the syntax we’ve just dropped for field parameters and use it for property parameters instead: ``` c# public class Person(public string FirstName, public string LastName); ``` This would be shorthand for writing ``` c# public class Person(string FirstName, string LastName) { public string FirstName { get; } = FirstName; public string LastName { get; } = LastName; } ``` Now the parameters would show up as PascalCase. This does not seem like a big deal for new types, but it would mean that most current code couldn’t be moved forward to this syntax without breaking callers who use named arguments. The implied association of parameter and property could certainly be useful in its own right. You could imagine allowing the use of object initializers to initialize these getter-only properties. Instead of translating it into setter calls, the compiler would know the corresponding constructor parameters to pass the values to: ``` c# var p = new Person { LastName = "Pascal", FirstName = "Blaise" }; ``` Would turn into: ``` c# var p = new Person("Blaise", "Pascal"); ``` Also, in the future, if we were to consider pattern matching or deconstruction features, this association could be helpful. ### Conclusion We like the idea of providing a shorthand in the primary constructor parameter list for generating simple corresponding properties. However, we are not ready to go down this route just yet. We need to decide on the upper-case/lower-case issue for one thing. We note that primary constructors already provide quite an improvement over what you have to write in C# 5.0. That’s just going to have to be good enough for now. ## Typeswitch For a long time we’ve had the idea to add a typeswitch feature to C#. In this coming release, VB is seriously looking at expanding its `Select Case` statement to allow matching on types. Syntactically, this seems to fit right in as a natural extension in VB. In C#, maybe not so much: the `switch` statement is quite restrictive and only a little evolved from C’s original jump table oriented design. It doesn’t easily accommodate such a different form of case condition. So if we were to add typeswitching capabilities to C#, we most likely would do it as a new feature with its own syntax. Options range from a switch-like construct with blocks for each match, to a more expression-oriented style reminiscent of pattern matching in functional languages. A major point here is that type switching can be seen as a special case of pattern matching. Would we ever add generalized pattern matching to C#? It certainly seems like a reasonable possibility. If so, then we should think of any typeswitching feature in that light: it needs to have the credible ability to “grow up” into a pattern matching feature in the future. ### Conclusion We’ve looked some at this, trying to imagine what a pattern matching future would look like. We have some great ideas, but we are not confident that we can map them out at this point to an extent where we would trust a current typeswitch design to fit well with it. And we do not have capacity to design and implement the full feature set in the current round. Let’s rather wait with the whole package and see if we can attack it in one go in the future. ================================================ FILE: meetings/2014/LDM-2014-05-21.md ================================================ # C# Language Design Notes for May 21, 2014 ## Agenda 1. Limit the nameof feature? <_keep current design_> 2. Extend params IEnumerable? <_keep current design_> 3. String interpolation <_design nailed down_> ## Limit the nameof feature? The current design of the `nameof(x)` feature allows for the named entity to reference entities that are not uniquely identified by the (potentially dotted) name given: methods overloaded on signature, and types overloaded on generic arity. This was rather the point of the feature: since you are only interested in the name, why insist on binding uniquely to just one symbol? As long as there’s at least one entity with the given name, it seems fine to yield the name without error. This sidesteps all the issues with the mythical `infoof()` feature (that would take an entity and return reflective information for it) of coming up with language syntax to uniquely identify overloads. (Also there’s no need to worry about distinguishing generic instantiations from uninstantiated generics, etc.: they all have the same name). The design, however, does lead to some interesting challenges for the tooling experience: ``` c# public void M(); public void M(int i); public void M(string s); WriteLine(nameof(M)); // writes the string "M" ``` Which definition of `M` should “Go To Definition” on `M` go to? When does renaming an overload cause a rename inside of the `nameof`? Which of the overloads does the occurrence of `nameof(M)` count as a reference to? Etc. The ambiguity is a neat trick at the language level, but a bit of a pain at the tooling level. Should we limit the application of `nameof` to situations where it is unambiguous? ### Conclusion No. Let’s keep the current design. We can come up with reasonable answers for the tooling challenges. Hobbling the feature would hurt real scenarios. ## Extend params IEnumerable? By current design, params is extended to apply to `IEnumerable` parameters. The feature still works by the call site generating a `T[]` with the arguments in it; but that array is of course available inside the method only as an `IEnumerable`. It has been suggested that we might as well make this feature work for other generic interfaces (or even all types) that arrays are implicitly reference convertible to, instead of just `IEnumerable`. It would certainly be straightforward to do, though there are quite a lot of such types. We could even infer an element type for the array from the passed-in arguments for the cases where the collection type does not have an element type of its own. On the other hand, it is usually bad practice for collection-expecting public APIs to take anything more specific than `IEnumerable`. That is especially true if the API is not intending to modify the collection, and no meaningful params method would do so: after all, if your purpose is to cause a side effect on a passed-in collection, why would you give the caller the option not to pass one? ### Conclusion Params only really makes sense on `IEnumerable`. If we were designing the language from scratch today we wouldn’t even have params on arrays, but only on `IEnumerable`. So let’s keep the design as is. ## String interpolation There have been a number of questions around how to add string interpolation to C#, some a matter of ambition versus simplicity, some just a matter of syntax. In the following we settle on these different design aspects. ### Safety Concatenation of strings with contents of variables has a long tradition for leading to bugs or even attack vectors, when the resulting string is subsequently parsed up and used as a command. Presumably if you make string concatenation easier, you are more vulnerable to such issues – or at least, by having a dedicated string interpolation features, you have a natural place in the language to help address such problems. Consequently, string interpolation in the upcoming EcmaScript 6 standard allows the user to indicate a function which will be charged with producing the result, based on compiler-generated lists of string fragments and expression results to be filled in. A given trusted function can prevent SQL injection or ensure the well-formedness of a URI. #### Conclusion We don’t think accommodating custom interpolators in C# is the sweet spot at this point. Most people are probably just looking for simpler and more readable syntax for filling out holes in strings. However, as we settle on syntax we should keep an eye on our ability to extend for this in the future. ### Culture In .NET there’s a choice between rendering values in the current culture or an invariant culture. This determines how common values such as dates and even floating point numbers are shown in text. The default is current culture, which even language-recognized functions such as `ToString()` make use of. Current culture is great if what you’re producing is meant to be read by humans in the same culture as the program is run in. If you get more ambitious than that with human readers, the next step up is to localize in some more elaborate fashion: looking up resources and whatnot. At that point, you are reaching for heavier hammers than the language itself should probably provide. There’s an argument that when a string is produced for machine consumption it is better done in the invariant culture. After all, it is quite disruptive to a comma-separated list of floating point values if those values are rendered with commas instead of dots as the decimal point! Should a string interpolation feature default to current or invariant culture, or maybe provide a choice? #### Conclusion We think this choice has already been made for us, with the language and .NET APIs broadly defaulting to current culture. That is probably the right choice for most quick-and-easy scenarios. If we were to accommodate custom interpolators in the future, there could certainly be one for culture-invariant rendering. ### Syntax The general format is strings with “holes”, the holes containing expressions to be “printed” in that spot. We’d like the syntax to stress the analogy to `String.Format` as much as possible, and we therefore want to use curly braces `{…}` in the delimiting of holes. We’ll return to what exactly goes in the curly braces, but for now there is one central question: how do we know to do string interpolation at all? There are two approaches we can think of: 1. Provide new syntax around the holes 2. Provide new syntax around the string itself To the first approach, we previously settled on escaping the initial curly brace of each hole to mean this was a string interpolation hole, and the contents should be interpreted as expression syntax: ``` c# "Hello, \{name}, you have \{amount} donut{s} left." ``` Here, `name` and `amount` refer to variables in scope, whereas `{s}` is just part of the text. This has a few drawbacks. It doesn’t look that much like a format string, because of the backslash characters in front of the curlies. You also need to visually scan the string to see if it is interpolated. Finally there’d be no natural place for us to indicate a custom interpolation function in the future. An example of the second approach would be to add a prefix to the string to trigger interpolation, e.g.: ``` c# $"Hello, {name}, you have {amount} donut\{s\} left." ``` Now the holes can be expressed with ordinary braces, and just like format strings you have to escape braces to actually get them in the text (though we are eager to use backslash escapes instead of the double braces that format strings use). You can see up front that the string is interpolated, and if we ever add support for custom interpolators, the function can be put immediately before or after the `$`; whichever we decide: ``` c# LOC$"Hello, {name}, you have {amount} donut\{s\} left." SQL$"…" URI$"…" ``` The prefix certainly doesn’t have to be a `$`, but that’s the character we like best for it. We don’t actually have to do it with a prefix. JavaScript is going to use back ticks to surround the string. But prefix certainly seems better than yet another kind of string delimiter. #### Conclusion The prefix approach seems better and more future proof. We are happy to use `$`. It wouldn’t compose with the `@` sign used for verbatim strings; it would be either one or the other. ### Format specifiers Format strings for `String.Format` allow various format specifiers in the placeholders introduced by commas and colons. We could certainly allow similar specifiers in interpolated strings. The semantics would be for the compiler to just turn an interpolated string into a call to `String.Format`, passing along any format specifiers unaltered: ``` c# $"Hello, {name}, you have {amount,-3} donut\{s\} left." ``` This would be translated to ``` c# String.Format("Hello, {0}, you have {1,-3} donut{{s}} left.", name, amount) ``` (Note that formatting of literal curlies needs to change if we want to keep our backslash escape syntax, which, tentatively, we do). The compiler would be free to not call `String.Format`, if it knows how to do things more optimally. This would typically be the case when there are no format specifiers in the string. #### Conclusion Allow all format specifiers that are allowed in the format strings of `String.Format`, and just pass them on. ### Expressions in the holes The final – important – question is which expressions can be put between the curly braces. In principle, we could imagine allowing almost any expression, but it quickly gets weird, both from a readability and from an implementation perspective. What if the expression itself has braces or strings in it? We wouldn’t be able to just lex our way past it (when to stop?), and similarly a reader, even with the help of colorization, would get mightily confused about what gets closed out when exactly. Additionally the choice to allow format specifiers limits the kinds of expressions that can unambiguously precede those. ``` c# $"{a as b ? – c : d}" // ?: or nullable type and format specifier? ``` The other extreme is to allow just a very limited set of expressions. The common case is going to be simple variables anyway, and anything can be expressed by first assigning into variables and then using those in the string. #### Conclusion We want to be quite cautious here, at least to begin with. We can always extend the set of expressions allowed, but for now we want to be close to the restrictive extreme and allow only simple and dotted identifiers. ================================================ FILE: meetings/2014/LDM-2014-07-09.md ================================================ # C# Language Design Notes for July 9, 2014 ## Agenda 1. Detailed design of nameof <_details settled_> 2. Design of #pragma warning extensions <_allow identifiers_> ## Details of nameof The overall design of `nameof` was decided in the design meeting on [Oct 7, 2013](https://roslyn.codeplex.com/discussions/552376). However, a number of issues weren’t addressed at the time. ### Syntactic ambiguity The use of `nameof(…)` as an expression can be ambiguous, as it looks like an invocation. In order to stay compatible, if there’s an invokable `nameof` in scope we’ll treat it as an invocation, regardless of whether that invocation is valid. This means that in those cases there is no way to apply the nameof operator. The recommendation of course will be to get rid of any use of `nameof` as an identifier, and we should think about having diagnostics helping with that. ### Which operands are allowed? The symbols recognized in a nameof expression must represent locals, range variables, parameters, type parameters, members, types or namespaces. Labels and preprocessor symbols are not allowed in a nameof expression. In general, free-standing identifiers are looked up like simple names, and dotted rightmost identifiers are looked up like member access. It is thus an error to reference locals before their declaration, or to reference inaccessible members. However, there are some exceptions: _All members are treated as if they were static members._ This means that instance members are accessed by dotting off the type rather than an instance expression. It also means that the accessibility rules around protected instance members are the simpler rules that apply to static members. _Generic types are recognized by name only._ Normally there needs to be a type parameter list (or at least dimension specifier) to disambiguate, but type parameter lists or dimension specifiers are not needed, and in fact not allowed, on the rightmost identifier in a nameof. _Ambiguities are not an error._ Even if multiple entities with the same name are found, nameof will succeed. For instance, if a property named `M` is inherited through one interface and a method named `M` is inherited through another, the usual ambiguity error will not occur. ### The referenced set Because ambiguities are allowed, a nameof operator can reference a set of different entities at the same time. The precise set of referenced entities in the presence of ambiguity can be loosely defined as “those it would be ambiguous between”. Thus, shadowed members or other entities that wouldn’t normally be found by lookup, e.g. because they are in a base class or an enclosing scope of where an entity is found, will not be part of the referenced set. The notion of referenced set has little importance for the language-level semantics, but is important for the tooling experience, e.g. for refactorings, go-to-definition, etc. Reference to some entities, e.g. obsolete members, `Finalize` or ‘`op_`’ methods, is normally an error. However, it is not an error in `nameof(…)` unless _all_ members of the referenced set would give an error. If all non-error references give warnings, then a warning is given. ### The resulting string C# doesn’t actually have a notion of canonical name. Instead, equality between names is currently defined directly _between_ names that may contain special symbols. For `nameof(… i)` we want the resulting string to be the identifier `I` given, except that formatting characters are omitted, and Unicode escapes are resolved. Also, any leading `@` is removed. In the case of aliases, this means that those are not resolved to their underlying meaning: the identifier is that of the alias itself. As a result, the meaning of the identifier is always only used to check if it is valid, never to decide what the resulting string is. There is no semantic component to determining the result of a nameof operator, only to determining if it is allowed. ## Pragma warning directives Now that custom diagnostics are on their way, we want to allow users to turn these on and off from source code, just as we do with the compiler’s own diagnostics today. To allow this, we need to extend the model of how a diagnostic is identified: today a number is used, but that is not a scalable model when multiple diagnostic providers are involved. Instead the design is that diagnostics are identified by an identifier. For compatibility the C# compiler’s own diagnostics can still be referenced with a number, but can also be referred to with the pattern `CS1234`: ``` c# #pragma warning disable AsyncCoreSet #pragma warning disable CS1234 ``` ================================================ FILE: meetings/2014/LDM-2014-08-27.md ================================================ # C# Design Notes for Aug 27, 2014 ## Agenda The meeting focused on rounding out the feature set around structs. 1. Allowing parameterless constructors in structs <_allow, but some unresolved details_> 2. Definite assignment for imported structs <_revert to Dev12 behavior_> ## Parameterless constructors in structs Unlike classes, struct types cannot declare a parameterless constructor in C# and VB today. The reason is that the syntax `new S()` in C# has historically been reserved for producing a zero-initialized instance of the struct. VB.Net has always had an alternative syntax for that (`Nothing`) and C# 2.0 also added one: `default(T)`. So the `new S()` syntax is no longer necessary for this purpose. It is possible to define parameterless constructors for structs in IL, but neither C#, VB or F# allow you to. All three languages have mostly sane semantics when consuming one, though, _mostly_ having `new S()` call the constructor instead of zero-initializing the struct (except in some corner cases visited below). Not being able to define parameterless constructors in structs has always been a bit of a pain, and now that we’re adding initializers to structs it becomes outright annoying. ### Conclusion We want to add the ability to declare explicit public parameterless constructors in structs, and we also want to think about reducing the number of occurrences of `new S()` that produce a default value. In the following we explore details and additional proposals. ### Accessibility C#, VB and F# will all call an accessible parameterless constructor if they find one. If there is one, but it is not accessible, C# and VB will backfill `default(T)` instead. (F# will complain.) It is problematic to have successful but different behavior of `new S()` depending on where you are in the code. To minimize this issue, we should make it so that explicit parameterless constructors have to be public. That way, if you want to replace the “default behavior” you do it everywhere. #### Conclusion Parameterless constructors will be required to be public. ### Compatibility Non-generic instantiation of structs with (public) parameterless constructors does the right thing in all three languages today. With generics it gets a little more subtle. All structs satisfy the `new()` constraint. When `new T()` is called on a type parameter `T`, the compiler _should_ generate a call to `Activator.CreateInstance` – and in VB and F# it does. However, C# tries to be smart, discovers at runtime that `T` is a struct (if it doesn’t already know from the `struct` constraint), and emits `default(T)` instead! ``` c# public T M() where T: new() { return new T(); } ``` Clearly we should remove this “optimization” and always call `Activator.CreateInstance` in C# as well. This is a bit of a breaking change, in two ways. Imagine the above method is in a library: 1. The more obvious – but also more esoteric – break is if people today call the library with a struct type (written directly in IL) which has a parameterless constructor, yet they depend on the library _not_ calling that parameterless constructor. That seems extremely unlikely, and we can safely ignore this aspect. 2. The more subtle issue is if such a library is not recompiled as we start populating the world with structs with parameterless constructors. The library is going to be wrongly not calling those constructors until someone recompiles it. But if it’s a third party library and they’ve gone out of business, no-one ever will. We believe even the second kind of break is relatively rare. The `new()` constraint isn’t used much. But it would be nice to validate. #### Conclusion Change the codegen for generic `new T()` in C# but try to validate that the pattern is rare in known code. ### Default arguments For no good reason C# allows `new` expressions for value types to occur as default arguments to optional parameters: ``` c# void M(S s = new S()){ … } ``` This is one place where we cannot (and do not) call a parameterless constructor even when there is one. This syntax is plain bad. It suggests one meaning but delivers another. We should do what we can (custom diagnostic?) to move developers over to use `default(S)` with existing types. More importantly we should not allow this syntax at all when `S` has a parameterless constructor. This would be a slight breaking change for the vanishingly rare IL-authored structs that do today, but so be it. #### Conclusion Forbid `new S()` in default arguments when `S` has a parameterless constructor, and consider a custom diagnostic when it doesn’t. People should use `default(S)` instead. ### Helpful diagnostic In general, with this change we are trying to introduce more of a distinction between default values and constructed values for structs. Today it is very blurred by the use of `new S()` for both meanings. Arguably the use of `new S()` to get the default value is fine as long as `S` does not have any explicit constructors. It can be viewed a bit like making use of the default constructor in classes, which gets generated for you if you do not have _any_ explicit constructors. The confusion is when a struct type “intends” to be constructed, by advertising one or more constructors. Provided that none of those is parameterless, `new S()` _still_ creates an unconstructed default value. This may or may not be the intention of the calling code. Oftentimes it would represent a bug where they meant to construct it (and run initializers and so forth), but the lack of complaint from the compiler caused them to think everything was all right. Occasionally a developer really does want to create an uninitialized value even of a struct that has constructors. In those cases, though, their intent would be much clearer if they used the `default(S)` syntax instead. It therefore seems that everyone would be well served by a custom diagnostic that would help “clear up the confusion” as it were, by * Flagging occurrences of `new S()` where `S` has constructors but not a parameterless one * Offering a fix to change to `default(T)`, as well as fixes to call the constructors This would help identify subtle bugs where they exist, and make the developer’s intent clearer when the behavior is intentional. The issue of course is how disruptive such a diagnostic would be to existing code. Would it be so annoying that they would just turn it off? Also, is the above assumption correct, that the occurrence of any constructor means that the library author intended for a constructor to always run? #### Conclusion We are cautiously interested in such a diagnostic, but concerned that it would be too disruptive. We should evaluate its impact on current code. ### Chaining to the default constructor when there’s a parameterless one A struct constructor must ensure that the struct is definitely assigned. It can do so by chaining to another constructor or by assigning to all fields. For structs with auto-properties there is an annoying fact that you cannot assign to the underlying field because its name is hidden, and you cannot assign to the setter, because you are not allowed to invoke a function member until the whole struct is definitely assigned. Catch 22! People usually deal with this today by chaining to the default constructor – which will zero-initialize the entire struct. If there is a user-defined parameterless constructor, however, that will not work. (Especially not if that is the constructor you are trying to implement!) There is a workaround. Instead of writing ``` c# S(int x): this() { this.X = x; } ``` You can make use of the fact that in a struct, `this` is an l-value: ``` c# S(int x) { this = default(S); this.X = x; } ``` It’s not pretty, though. In fact it’s rather magical. We may want to consider adding an alternative syntax for zero-initializing from a constructor; e.g.: ``` c# S(int x): default() { this.X = x; } ``` However, it is also worth noting that auto-properties themselves are evolving. You can now directly initialize their underlying field with an initializer on the auto-property. And for getter-only auto-properties, assignment in the constructor will also directly assign the underlying field. So maybe problem just isn’t there any longer. You can just zero-initialize the auto-properties directly: ``` c# public int X { get; set; } = 0; ``` Now the definite assignment analysis will be happy when you get to running a constructor body. #### Conclusion Do nothing about this right now, but keep an eye on the issue. ### Generated parameterless constructors The current rule is that initializers are only allowed if there are constructors that can run them. This seems reasonable, but look at the following code: ``` c# struct S { string label = ""; bool pending = true; public S(){} … } ``` Do we _really_ want to force people to write that trivial constructor? Had this been a class, they would just have relied on the compiler-generated default constructor. It is probably desirable to at least do what classes do and generate a default constructor when there are no other constructors. Of course we wouldn’t generate one when there are no initializers either: that would be an unnecessary (and probably slightly breaking) change over what we do today, as the generated constructor would do exactly the same as the default `new S()` behavior anyway. A question though is if we should generate a parameterless constructor to run initializers even if there are also parameterful ones. After all, don’t we want to ensure that initializers get run in as many cases as possible? This seems somewhat attractive, though it does mean that a struct with initializers doesn’t get to choose _not_ to have a generated parameterless constructor that runs the initializers. Also, in the case that there’s a primary constructor it becomes uncertain what it would mean for a parameterless constructor to run the initializers: after all they may refer to primary constructor parameters that aren’t available to the parameterless constructor: ``` c# struct Name(string first, string last) { string first = first; string last = last; } ``` How is a generated parameterless constructor supposed to run those initializers? To make this work, we would probably have to make the parameterless constructor chain to the primary constructor (all other constructors must chain to the primary constructor), passing _default values_ as arguments. Alternatively we could require that all structs with primary constructors _also_ provide a parameterless constructor. But that kind of defeats the purpose of primary constructors in the first place: doing the same with less code. In all we seem to have the following options: 1. Don’t generate anything. If you have initializers, you must also provide at least one constructor. The only change from today’s design is that one of those constructors can be parameterless. 2. Only generate a parameterless constructor if there are no other constructors. This most closely mirrors class behavior, but it may be confusing that adding an explicit constructor “silently” changes the meaning of `new S()` back to zero-initialization. (The above diagnostic would help with that, though). 3. Generate a parameterless constructor only when there is not a primary constructor and a. Still fall back to zero-initialization for new S() in this case b. Require a parameterless constructor to be explicitly specified This seems to introduce an arbitrary distinction between primary constructors and other constructors that prevents easy refactoring back and forth between them. 4. Generate a parameterless constructor even when there is a primary constructor a. using default values and/or b. some syntax to provide the arguments as part of the primary constructor This seems overly magical, and again treats primary constructors more differently than was the intent with their design. #### Conclusion This is a hard one, and we didn’t reach agreement. We probably want to do at least option 2, since part of our goal is for structs to become more like classes. But we need to think more about the tradeoffs between that and the more drastic (but also more helpful?) approaches. ## Definite assignments for imported structs Unlike classes, private fields in structs do need to be observed in various ways on the consumer side – they cannot be considered entirely an implementation detail. In particular, in order to know if a struct is definitely assigned we need to know if its fields have all been initialized. For inaccessible fields, there is no sure way to do that piecemeal, so if such inaccessible fields exist, the struct-consuming code must insist that the struct value as a whole has been constructed or otherwise initialized. So the key is to know if the struct has inaccessible fields. The native compiler had a long-running bug that would cause it to check imported structs for inaccessible fields _only_ where those fields were of value type! So if the struct had only a private field of a reference type, the compiler would fail to ensure that it was definitely assigned. In Roslyn we started out implementing the specification, which was of course stricter and turned out to break some code (that was buggy and should probably have been fixed). Instead we then went to the opposite extreme and just stopped ensuring definite assignment of these structs altogether. This lead to its own set of problems, primarily in the form of a new set of bugs that went undetected because of the laxer rules. Ideally we would go back to implementing the spec. This would break old code, but have the best experience for new code. If we had a “quirks” mode approach, we could allow e.g. the lang-version flag to be more lax on older versions. Part of migrating a code base to the new version of the language would involve fixing this kind of issue. ### Conclusion Unfortunately we do not have the notion of a quirks mode. Like a number of issues before, this one alone does not warrant introducing one – after all, it is a new kind of upgrade tax on customers. We should compile a list of things we would do if we had a quirks mode, and evaluate if the combined value would be enough to justify it. Definite assignment for structs should be on that list. In the meantime however, the best we can do is to revert to the behavior of the native compiler, so that’s what we’ll do. ================================================ FILE: meetings/2014/LDM-2014-09-03.md ================================================ # C# Design Notes for Sep 3, 2014 Quote of the day: “It’s a design smell. But it’s a good smell.” ## Agenda The meeting focused on rounding out the design of declaration expressions 1. Removing “spill out” from declaration expressions in simple statements <_yes, remove_> 2. Same name declared in subsequent else-if’s <_condition decls out of scope in else-branch_> 3. Add semicolon expressions <_not in this version_> 4. Make variables in declaration expressions readonly <_no_> ## “Spill out” The scope rules for variables introduced in declaration expressions are reasonably regular: the scope of such a variable extends to the nearest enclosing statement, and like all local variables, it may be used only after it has been defined, textually. We did make a couple of exceptions, though: an expression-statement or a declaration-statement does _not_ serve as a boundary for such a variable – instead it “spills out” to the directly enclosing block – if there is one. Similarly, a declaration expression in one field initializer is in scope for neighboring field initializers (as long as they are in the same part of the type declaration). This was supposed to enable scenarios such as this: ``` c# GetCoordinates(out var x, out var y); … // use x and y; ``` to address the complaint that it is too much of a hassle to use out and ref parameters. But we have a nagging suspicion that this scenario – pick up the value in one statement and use it in the next – is not very common. Instead the typical scenario looks like this: ``` c# if (int.TryParse(s, out int i)) { … i … } ``` Where the introduced local is used in the _same_ statement as it is declared in. Outside of conditions, probably the most common use is the inline common-subexpression refactoring, where the result of an expression is captured into a variable the first time it is used, so the variable can be applied to the remaining ones: ``` c# Console.WriteLine("Result: {0}", (var x = GetValue()) * x); ``` The spill-out is actually a bit of a nuisance for the somewhat common scenario of passing dummies to ref or out parameters that you don’t need (common in COM interop scenarios), because you cannot use the same dummy names in subsequent statements. From a rule regularity perspective, the spilling is quite complicated to explain. It would be a meaningful simplification to get rid of it. While complexity of spec’ing and implementing shouldn’t stand in the way of a good feature, it is often a smell that the design isn’t quite right. ### Conclusion Let’s get rid of the spilling. Every declaration expression is now limited in scope to it nearest enclosing statement. We’ll live with the (hopefully) slight reduction in usage scenarios. ## Else-if’s Declaration expressions lend themselves particularly well to a style of programming where an if/else-if chain goes through various options, each represented by a variable declared in a condition, using those variables in the then-clause: ``` c# if ((var i = o as int?) != null) { … i … } else if ((var s = o as string) != null) { … s … } else if … ``` This particular pattern _looks_ like a chain of subsequent options, and even indents like that, but linguistically the else clauses are nested. For that reason, with our current scope rules the variable `I` introduced in the first condition is in scope in all the rest of the statement – even though it is only meaningful and interesting in the then-branch. In particular, it blocks another variable with the same name from being introduced in a subsequent condition, which is quite annoying. We do want to solve this problem. There is no killer option that we can think of, but there are a couple of plausible approaches: 1. Change the scope rules so that variables declared in the condition of an if are in scope in the then-branch but not in the else-branch 2. Remove the restriction that locals cannot be shadowed by other locals 3. Do something very scenario specific ### Changing the scope rules Changing the scope rules would have the unfortunate consequence of breaking the symmetry of if-statements, so that ``` c# if (b) S1 else S2 ``` No longer means exactly the same as ``` c# if (!b) S2 else S1 ``` It kind of banks on the fact that the majority of folks who would introduce declaration expressions in a condition would do so for use in the then-branch only. That certainly seems to be likely, given the cases we have seen (type tests, uses of the `Try…` pattern, etc.). But it still may be surprising to some, and downright bothersome in certain cases. Worse, there may be tools that rely on this symmetry principle. Refactorings to swap then and else branches (negating the condition) abound. These would no longer always work. Moreover, of course, this breaks with the nice simple scoping principle for declaration expressions that we just established above: that they are bounded (only) by their enclosing statement. ### Removing the shadowing restriction Since C# 1.0, it has been forbidden to shadow a local variable or parameter with another one. This is seen as one of the more successful rules of hygiene in C# - it makes code safe for refactoring in many scenarios, and just generally easier to read. There are existing cases where this rule is annoying: ``` C# task.ContinueWith(task => … task …); // Same task! Why can’t I name it the same? ``` Here it seems the rule even runs counter to refactoring, because you need to change every occurrence of the name when you move code into a lambda. Lifting this restriction would certainly help the else-if scenario. While previous variables would still be in scope, you could now just shadow them with new ones if you choose. If you do not choose to use the same name, however, the fact that those previous variables are in scope may lead to confusion or accidental use. More importantly, are we really ready to part with this rule? It seems to be quite well appreciated as an aid to avoid subtle bugs. ### Special casing the scenario Instead of breaking with general rules, maybe we can do something very localized? Some combination of the two? It would have to work both in then and else branches; otherwise, it would still break the if symmetry, and be as bad as the first option. We could allow only variables introduced in conditions of if-statements to be shadowed only by other variables introduced in conditions of if-statements? This might work, but seems inexcusably ad-hoc, and is almost certain to cause a bug tail in many tools down the line, as well as confusion when refactoring code or trying to experiment with language semantics. ### Conclusion It seems there truly is no great option here. However, we’d rather solve the problem with a wart or two than not address it at all. On balance, option 1, the special scope rule for else clauses, seems the most palatable, so that’s what we’ll do. ## Semicolon expressions We previously proposed a semicolon operator, to be commonly used with declaration expressions, to make “let-like” scenarios a little nicer: ``` c# Console.WriteLine("Result: {0}", (var x = GetValue(); x * x)); ``` Instead of being captured on first use, the value is now captured first, _then_ used multiple times. We are not currently on track to include this feature in the upcoming version of the language. The question is; should we be? There’s an argument that declaration expressions only come to their full use when they can be part of such a let-like construct. Also, there are cases (such as conditional expressions) where you cannot just declare the variable on first use, since the use is in a branch separate from later uses. Nevertheless, it might be rash to say that this is our let story. Is this how we want let to look like in C#? We don’t easily get another shot at addressing the long-standing request for a let-like expression. It probably needs more thought than we have time to devote to it now. ### Conclusion Let’s punt on this feature and reconsider in a later version. ## Should declaration expression variables be mutable? C# is an imperative language, and locals are often used in a way that depends on mutating them sometime after initialization. However, you could argue that this is primarily useful when used across statements, whereas it generally would be a code smell to have a declaration that’s only visible _inside_ one statement rely on mutation. This may or may not be the case, but declaration _expressions_ also benefit from a strong analogy with declaration _statements_. It would be weird that `var s = GetString()` introduces a readonly variable in one setting but not another. (Note: it does in fact introduce a readonly variable in a few situations, like foreach and using statements, but those can be considered special). ### Conclusion Let’s keep declaration expressions similar to declaration statements. It is too weird if a slight refactoring causes the meaning to change. It may be worth looking at adding readonly locals at a later point, but that should be done in an orthogonal way. ================================================ FILE: meetings/2014/LDM-2014-10-01.md ================================================ There were two agenda items... 1. Assignment to readonly autoprops in constructors (we fleshed out details) 2. A new compiler warning to prevent outsiders from implementing your interface? (no, leave this to analyzers) # Assignment to readonly autoprops in constructors ```cs public struct S { public int x {get;} public int y {get; set;} public Z z {get;} public S() { x = 15; y = 23; z.z1 = 1; } } public struct Z { int z1; } ``` _What are the rules under which assignments to autoprops are allowed in constructors?_ __Absolute__ We can't be more permissive in what we allow with readonly autoprops than we are with readonly fields, because this would break PEVerify. (Incidentally, PEVerify doesn't check definite assignment in the constructor of a struct; that's solely a C# language thing). __Overall principle__ When reading/writing to an autoprop, do we go via the accessor (if there is one) or do we bypass it (if there is one) and access the underlying field directly? _Option1:_ language semantics say the accessor is used, and codegen uses it. _Option2:_ in an appropriate constructor, when there is a "final" autoprop (either non-virtual, or virtual in a sealed class), access to an autoprop _means_ an access to the underlying field. This meaning is used for definite assignment, and for codegen. Note that it is semantically visible whether we read from an underlying field vs through an accessor, e.g. in `int c { [CodeSecurity] get;}` _Resolution: Option1_. Under Option2, if you set a breakpoint on the getter of an autoprop, gets of it would not hit the breakpoint if they were called in the constructor which is weird. Also it would be weird that making the class sealed or the autoprop non-virtual would have this subtle change. And things like Postsharper wouldn't be able to inject. All round Option2 is weird and Option1 is clean and expected. __Definite Assignment__. Within an appropriate constructor, what exactly are the rules for definite assignment? Currently if you try to read a property before _all_ fields have been assigned then it says CS0188 'this' cannot be used before all fields are assignment, but reading a field is allowed so long as merely that field has been assigned. More precisely, within an appropriate constructor, for purposes of definite assignment analysis, when does access of the autoprop behave as if it's an access of the backing field? _Option1_: never _Option2_: Only in case of writes to readonly autoprops _Option3_: In the case of writes to all autoprops _Option4_: In the case of reads and writes to all autoprops _Resolution: Option4_. This is the most helpful to developers. You might wonder what happens if it's a virtual autoprop and someone overrides getter or setter in derived types in such a way that would violate the definite assignment assumptions. But for structs there won't be derived types, and for classes the semantics say that all fields are assigned to default(.) so there's no difference. __Piecewise initialization of structs__. In the code above, do we allow `z.z1 = 15` to assign to the _field_ of a readonly struct autoprop? _Option1:_ Yes by threating access to "z" for purposes of definite assignment as an access of the underlying field. _Option2:_ No because in `z.z1` the read of `z` happens via the accessor as per the principle above, and thus returns an rvalue, and hence assignment to `z.z1` can't work. Instead you will have to write `z = new Z(...)`. _Resolution: Option2_. If we went with Option1, then readonly autoprops would end up being more expressive than settable autoprops which would be odd! Note that in VB you can still write `_z.z1 = 15` if you do want piecewise assignment. __Virtual__. What should happen if the readonly autoprop is virtual, and its getter is overridden in a derived class? _Resolution:_ All reads of the autoprop go via its accessor, as is already the case. __Semantic model__. In the line `x = 15` what should the Roslyn semantic model APIs say for the symbol `x` ? _Resolution:_ they refer to the property x. Not the backing field. (Under the hood of the compiler, during lowering, if in an appropriate constructor, for write purposes, it is implicitly transformed into a reference to the backing field). More specifically, for access to an autoprop in the constructor, 1. It should _bind_ to the property, but the property should be treated as a readable+writable (for purposes of what's allowed) in the case of a readonly autoprop. 2. The definite assignment behavior should be as if directly accessing the backing field. 3. It should code gen to the property accessor (if one exists) or a field access (if not). __Out/ref arguments in C#__. Can you pass a readonly autoprop as an out/ref argument in C#? _Resolution: No_. For readonly autoprops passed as _ref_ arguments, that wouldn't obey the principle that access to the prop goes via its accessor. For passing readonly autoprops as _out_ arguments with the hope that it writes to the underlying field, that wouldn't obey the principle that we bind to the property rather than the backing field. For writeonly autoprops, they don't exist because they're not useful. __Static readonly autoprops__ Should everything we've written also work for static readonly autoprops? _Resolution: Yes._ Note there's currently a bug in the native compiler (fixed in Dev14) where the static constructor of a type G\ is able to initialize static readonly fields in specializations of G e.g. `G.x=15;`. The CLR does indeed maintain separate storage locations for each static readonly fields, so `G.g` and `G.g` are different variables. (The native compiler's bug where the static constructor of G could assign to all of them resulted in unverifiable code). __VB rules in initializers as well as constructors__. VB initializers are allowed to refer to other members of a class, and VB initializers are all executed during construction time. Should everything we've said about behavior in C# constructors also apply to behavior in VB initializers? _Resolution: Yes_. __VB copyback for ByRef parameters__. In VB, when you pass an argument to a ByRef parameter, then either it passes it as an lvalue (if the argument was a local variable or field or similar) or it uses "copy-in to a temporary then invoke the method then copy-out from the temporary" (if the argument was a property), or it uses "copy-in to a temporary then invoke the method then ignore the output" (if the argument was an rvalue or a constant). What should happen when you pass a readonly autoprop to a ByRef parameter? _Option1:_ Emit a compile-time error because copyback is mysterious and bites you in mysterious ways, and this new way is even more mysterious than what was there before. _Option2:_ Within the constructor/initializers, copy-in by reading via the accessor, and copy-back by writing to the underlying field. Elsewhere, copy-in with no copy-out. Also, just as happens with readonly fields, emit an error if assignment to a readonly autoprop happens in a lambda in a constructor (see code example below) _Resolution: Option2_. Exactly has happens today for readonly fields. Note incidentally that passing a readonly autoprop to a ByRef parameter will have one behavior in the constructor and initializers (it will do the copy-back), and will silently have different behavior elsewhere (it won't do any copy-back). This too is already the case with readonly fields. On a separate note, developers would like to have feedback in some cases (not constants or COM) where copyback in a ByRef argument isn't done. But that's not a question for the language design meeting. __VB copyin for writeonly autoprops__. VB tentatively has writeonly autoprops for symmetry, even though they're not useful. What should happen when you pass a writeonly autoprop as a ByRef argument? _Resolution: Yuck._ This is a stupid corner case. Notionally the correct thing is to read from the backing field, and write via the setter. But if it's easier to just remove support for writeonly autoprops, then do that. ```vb Class C ReadOnly x As Integer = 15 Public Sub New() f(x) Dim lambda = Sub() f(x) ' error BC36602: 'ReadOnly' variable ' cannot be the target of an assignment in a lambda expression ' inside a constructor. End Sub End Sub Shared Sub f(ByRef x As Integer) x = 23 End Sub End Class ``` We discussed a potential new error message in the compiler. __Scenario:__ Roslyn ships with ISymbol interface. In a future release it wants to add additional members to the interface. But this will break anyone who implemented ISymbol in its current form. Therefore it would be good to have a way to prevent anyone _else_ from implementing ISymbol. That would allow us to add members without breaking people. Is this scenario widespread? Presumably, but we don't have data and haven't heard asks for it. There are a number of workarounds today. Some workarounds provide solid code guarantees. Other workarounds provide "suggestions" or "encouragements" that might be enough for us to feel comfortable breaking people who took dependencies where we told them not to. __Counter-scenario:__ Nevertheless, I want to _MOCK_ types. I want to construct a mock ISymbol myself maybe using MOQ, and pass it to my functions which take in an ISymbol, for testing purposes. I still want to be able to do this. (Note: MOQ will automatically update whenever we add new members to ISymbol, so users of it won't be broken). __Workarounds__ 1. Ignore the problem and just break people. 2. Like COM, solve it by adding new incremental interfaces ISymbol2 with the additional members. As Adam Speight notes below, you can make ISymbol2 inherit from ISymbol. 3. Instead of interfaces, use abstract classes with internal constructors. Or abstract classes but never add abstract methods to it; only virtual methods. 4. Write documentation for the interface, on MSDN or in XML Doc-Comments, that say "Internal class only; do not implement it". We see this for instance on ICorThreadpool. 5. Declare a method on the interface which has an internal type in its signature. The CLR allows this but the language doesn't so it would have to be authored in IL. Every type which implements the interface would have to provide an implementation of that method. 6. Write run-time checks at the public entry points of key Roslyn methods that take in an ISymbol, and throw if the object given was implemented in the wrong assembly. 7. Write a Roslyn analyzer which is deployed by the same NuGet package that contains the definition of ISymbol, and have this analyzer warn if you're trying to implement the interface. This analyzer could be part of Roslyn, or it could be an independent third-party analyzer used by many libraries. __Proposal:__ Have the compiler recognize a new attribute. Given the following code ```cs [System.Runtime.CompilerServices.InternalImplementationOnly] interface I<...> {...} ``` it should be a compile-time warning for a type to implement that interface, directly or indirectly, unless the class is in the same assembly as "I" or is in one of its InternalsVisibleTo friends. It will also be a compile-time error for an interface to inherit from the interface in the same way. Also, we might ask for the .NET Framework team to add this attribute in the same place as System.Runtime.CompilerServices.ExtensionAttribute, and CallerMemberNameAttribute. But doing it isn't necessary since the compiler will recognize any attribute with that exact fully-qualified name and the appropriate (empty) constructor. Note that this rule would not be cast-iron, since it won't have CLR enforcement. It would still be possible to bypass it by writing IL by hand, or by compiling with an older compiler. But we're not looking for cast-iron. We're just looking for discouragement strong enough to allow us to add members to ISymbol in the future. (In the case of ISymbol, it's very likely that people will be using Roslyn to compile code relating to ISymbol, but that doesn't apply to other libraries). __Resolution:__ Workaround #7 is a better option than adding this proposal to the language. ================================================ FILE: meetings/2014/LDM-2014-10-15.md ================================================ # nameof operator: spec v5 The nameof(.) operator has the form nameof(expression). The expression must have a name, and may refer to either a single symbol or a method-group or a property-group. Depending on what the argument refers to, it can include static, instance and extension members. This is v5 of the spec for the "nameof" operator. [[v1](https://roslyn.codeplex.com/discussions/552376), [v2](https://roslyn.codeplex.com/discussions/552377), [v3](https://roslyn.codeplex.com/discussions/570115), [v4](https://roslyn.codeplex.com/discussions/570364)]. The key decisions and rationales are summarized below. _Please let us know what you think!_ # Rationale Question: why do we keep going back-and-forth on this feature? Answer: I think we're converging on a design. It's how you do language design! (1) make a proposal, (2) _spec it out_ to flush out corner cases and make sure you've understood all implications, (3) _implement the spec_ to flush out more corner cases, (4) try it in practice, (5) if what you hear or learn at any stage raises concerns, goto 1. * _v1/2 had the problem "Why can't I write nameof(this.p)? and why is it so hard to write analyzers?"_ * _v3 had the problem "Why can't I write nameof(MyClass1.p)? and why is it so hard to name method-groups?"_ * _v4 had the problem "What name should be returned for types? and how exactly does it relate to member lookup?"_ This particular "nameof v5" proposal came from a combined VB + C# LDM on 2014.10.22. We went through the key decisions: 1. __Allow to dot an instance member off a type? Yes.__ Settled on the answer "yes", based on the evidence that v1/v2 had it and it worked nicely, and v3 lacked it and ended up with unacceptably ugly "default(T)" constructions. 2. __Allow to dot instance members off an instance? Yes.__ Settled on the answer "yes", based on the evidence that v1/v2 lacked it and it didn't work well enough when we used the CTP, primarily for the case "this.p" 3. __Allow to name method-groups? Yes.__ Settled on the answer "yes", based on the evidence that v1/v2 had it and it worked nicely, and v3 lacked it and ended up with unacceptably ugly constructions to select which method overload. 4. __Allow to unambiguously select a single overload? No.__ Settled on the answer "no" based on the evidence that v3 let you do this but it looked too confusing. I know people want it, and it would be a stepping stone to infoof, but at LDM we rejected these (good) reasons as not worth the pain. The pain is that the expressions look like they'll be executed, and it's unclear whether you're getting the nameof the method or the nameof the result of the invocation, and they're cumbersome to write. 5. __Allow to use nameof(other-nonexpression-types)? No.__ Settled on the answer "only nameof(expression)". I know people want other non-expression arguments, and v1/v2 had them, and it would be more elegant to just write nameof(List<>.Length) rather than having to specify a concrete type argument. But at LDM we rejected these (good) reasons as not worth the pain. The pain is that the language rules for member access in expressions are too different from those for member access in the argument to nameof(.), and indeed member access for StrongBox<>.Value.Length doesn't really exist. The effort to unify the two concepts of member access would be way disproportionate to the value of nameof. This principle, of sticking to existing language concepts, also explains why v5 uses the standard language notions of "member lookup", and hence you can't do nameof(x.y) to refer to both a method and a type of name "y" at the same time. 6. __Use source names or metadata names? Source.__ Settled on source names, based on the evidence... v1/v2/v3 were source names because that's how we started; then we heard feedback that people were interested in metadata names and v4 explored metadata names. But I think the experiment was a failure: it ended up looking like _disallowing_ nameof on types was better than picking metadata names. At LDM, after heated debate, we settled on source names. For instance `using foo=X.Y.Z; nameof(foo)` will return "foo". Also `string f() => nameof(T)` will return "T". There are pros and cons to this decision. In its favor, it keeps the rules of nameof very simple and predictable. In the case of nameof(member), it would be a hindrance in most (not all) bread-and-butter cases to give a fully qualified member name. Also it's convention that "System.Type.Name" refers to an unqualified name. Therefore also nameof(type) should be unqualified. If ever you want a fully-qualified type name you can use typeof(). If you want to use nameof on a type but also get generic type parameters or arguments then you can construct them yourself, e.g. nameof(List\) + "`2". Also the languages have no current notion of metadata name, and metadata name can change with obfuscation. 7. __Allow arbitrary expressions or just a subset?__ We want to try out the proposal "just a subset" because we're uneasy with full expressions. That's what v5 does. We haven't previously explored this avenue, and it deserves a try. 8. __Allow generic type arguments?__ Presumably 'yes' when naming a type since that's how expression binding already works. And presumably 'no' when naming a method-group since type arguments are used/inferred during overload resolution, and it would be confusing to also have to deal with that in nameof. [this item 8 was added after the initial v5 spec] I should say, we're not looking for unanimous consensus -- neither amongst the language design team nor amongst the codeplex OSS community! We hear and respect that some people would like something closer to infoof, or would make different tradeoffs, or have different use-cases. On the language design team we're stewards of VB/C#: we have a responsibility to listen to and understand _every opinion_, and then use our own judgment to weigh up the tradeoffs and use-cases. I'm glad we get to do language design in the open like this. We've all been reading the comments on codeplex and elsewhere, our opinions have been swayed, we've learned about new scenarios and issues, and we'll end up with a better language design thanks to the transparency and openness. I'm actually posting these notes on codeplex as my staging ground for sharing them with the rest of the team, so the codeplex audience really does see everything "in the raw". # C# Syntax ``` expression: ... | nameof-expression name-of-expression: nameof ( expression ) ``` In addition to the syntax indicated by the grammar, there are some additional syntactic constraints: (1) the argument expression can only be made up out of simple-name, member-access, base-access, or "this", and (2) cannot be simply "this" or "base" on its own. These constraints ensure that the argument looks like it has a name, and doesn't look like it will be evaluated or have side effects. I found it easier to write the constraints in prose than in the grammar. [clarification update] Note that member-access has three forms, `E.I` and `predefined-type.I` and `qualified-alias-member.I`. All three forms are allowed, although the first case `E` must only be made out of allowed forms of expression. If the argument to nameof at its top level has an unacceptable form of expression, then it gives the error "This expression does not have a name". If the argument contains an unacceptable form of expression deeper within itself, then it gives the error "This sub-expression cannot be used as an argument to nameof". It is helpful to list some things not allowed as the nameof argument: ``` invocation-expression e(args) assignment x += 15 query-expression from y in z select y lambda-expression () => e conditional-expression a ? b : c null-coalescing-expression a?? b binary-expression ||, &&, |, ^, &, ==, !=, <, >, <=, >=, is, as, <<, >>, +, -, *, /, % prefix-expression +, -, !, ~, ++, --, *, &, (T)e postfix-expression ++, -- array-creation-expression new C[…] object-creation-expression new C(…) delegate-creation-expression new Action(…) anonymous-object-creation-expression new {…} typeof-expression typeof(int) checked-expression checked(…) unchecked-expression unchecked(…) default-value-expression default(…) anonymous-method-expression delegate {…} pointer-member-access e->x sizeof-expression sizeof(int) literal "hello", 15 parenthesized-expression (x) element-access e[i] base-access-indexed base[i] await-expression await e nameof-expression nameof(e) vb-dictionary-lookup e!foo ``` Note that there are some types which are not counted as expressions by the C# grammar. These are not allowed as nameof arguments (since the nameof syntax only allows expressions for its argument). There's no need to spell out that the following things are not valid expressions, since that's already said by the language syntax, but here's a selection of some of the types that are not expressions: ``` predefined-type int, bool, float, object, dynamic, string nullable-type Customer? array-type Customer[,] pointer-type Buffer*, void* qualified-alias-member A::B void void unbound-type-name Dictionary<,> ``` # Semantics The nameof expression is a constant. In all cases, nameof(...) is evaluated at compile-time to produce a string. Its argument is not evaluated at runtime, and is considered unreachable code (however it does not emit an "unreachable code" warning). _Definite assignment._ The same rules of definite assignment apply to nameof arguments as they do to all other unreachable expressions. _Name lookup_. In the following sections we will be discussing member lookup. This is discussed in $7.4 of the C# spec, and is left unspecified in VB. To understand nameof it is useful to know that the existing member lookup rules in both languages either return a single type, or a single instance/static field, or a single instance/static event, or a property-group consisting of overloaded instance/static properties of the same name (VB), or a method-group consisting of overloaded instance/static/extension methods of the same name. Or, lookup might fail either because no symbol was found or because ambiguous conflicting symbols were found that didn't fall within the above list of possibilities. _Argument binding_. The nameof argument refers to one or more symbols as follows. __nameof(simple-name)__, of the form I or I The normal rules of simple name lookup $7.6.2 are used but with one difference... * The third bullet talks about member lookup of I in T with K type arguments. Its third sub-bullet says _"Otherwise, the result is the same as a member access of the form T.I or T.I. In this case, it is a binding-time error for the simple-name to refer to an instance member."_ For the sake of nameof(simple-name), this case does not constitute a binding-time error. __nameof(member-access)__, of the form E.I or E.I The normal rules of expression binding are used to evaluate "E", with _no changes_. After E has been evaluated, then E.I is evaluated as per the normal rules of member access $7.6.4 but with some differences... * The third bullet talks about member lookup of I in E. Its sub-bullets have rules for binding when I refers to static properties, fields and events. For the sake of nameof(member-access), each sub-bullet applies to instance properties, fields and events as well. * The fourth bullet talks about member lookup of I in T. Its sub-bullets have rules for binding when I refers to instance properties, fields and events. For the sake of nameof(member-access), each sub-bullet applies to static properties, fields and events as well. __nameof(base-access-named)__, of the form base.I or base.I This is treated as nameof(B.I) or nameof(B.I where B is the base class of the class or struct in which the construct occurs. _Result of nameof_. The result of nameof is the identifier "I" with the _standard identifier transformations_. Note that, at the top level, every possible argument of nameof has "I". [update that was added after the initial v5 spec] If "I" binds to a method-group and the argument of nameof has generic type arguments at the top level, then it produces an error "Do not use generic type arguments to specify the name of methods". Likewise for property-groups. The standard identifier transformations in C# are detailed in $2.4.2 of the C# spec: first any leading @ is removed, then Unicode escape sequences are transformed, and then any formatting-characters are removed. This of course still happens at compile-time. In VB, any surrounding [] is removed # Implementation In C#, nameof is stored in a normal InvocationExpressionSyntax node with a single argument. That is because in C# 'nameof' is a contextual keyword, which will only become the "nameof" operator if it doesn't already bind to a programmatic symbol named "nameof". TO BE DECIDED: what does its "Symbol" bind to? In VB, NameOf is a reserved keyword. It therefore has its own node: ```vb Class NameOfExpressionSyntax : Inherits ExpressionSyntax Public ReadOnly Property Argument As ExpressionSyntax End Class ``` TO BE DECIDED: Maybe VB should just be the same as C#. Or maybe C# should do the same as VB. What is the return value from `var r = semanticModel.GetSymbolInfo(argument)`? In all cases, r.Candidates is the list of symbol. If there is only one symbol then it is in r.Symbol; otherwise r.Symbol is null and the reason is "ambiguity". Analyzers and the IDE will just have to deal with this case. # IDE behavior ```cs class C { [3 references] static void f(int i) {...nameof(f)...} [3 references] void f(string s) {...nameof(this.f)...} [3 references] void f(object o) {...nameof(C.f)...} } static class E { [2 references] public static void f(this C c, double d) {} } ``` __Highlight symbol from argument__: When you set your cursor on an argument to nameof, it highlights all symbols that the argument bound to. In the above examples, the simple name "nameof(f)" binds to the three members inside C. The two member access "nameof(this.f)" and "nameof(C.f)" both bind to extension members as well. __Highlight symbol from declaration__: When you set your cursor on any declaration of f, it highlights all nameof arguments that bind to that declaration. Setting your cursor on the extension declaration will highlight only "this.f" and "C.f". Setting your cursor on any member of C will highlight both all three nameof arguments. __Goto Definition__: When you right-click on an argument to nameof in the above code and do GoToDef, it pops up a FindAllReferences dialog to let you chose which declaration. (If the nameof argument bound to only one symbol then it would go straight to that without the FAR dialog.) __Rename declaration__: If you do a rename-refactor on one of the declarations of f in the above code, the rename will only rename this declaration (and will not rename any of the nameof arguments); the rename dialog will show informational text warning you about this. If you do a rename-refactor on the _last remaining_ declaration of f, then the rename will also rename nameof arguments. Note that if you turn on the "Rename All Overloads" checkbox of rename-refactor, then it will end up renaming all arguments. __Rename argument__: If you do a rename-refactor on one of the nameof arguments in the above code, the rename dialog will by default check the "Rename All Overloads" button. __Expand-reduce__: The IDE is free to rename "nameof(p)" to "nameof(this.p)" if it needs to do so to remove ambiguity during a rename. This might make nameof now bind to more things than it used to... __Codelens__: We've articulated the rules about what the argument of nameof binds to. The CodeLens reference counts above are a straightforward consequence of this. ## Bread and butter cases ```cs // Validate parameters void f(string s) { if (s == null) throw new ArgumentNullException(nameof(s)); } ``` ```cs // MVC Action links <%= Html.ActionLink("Sign up", @typeof(UserController), @nameof(UserController.SignUp)) %> ``` ```cs // INotifyPropertyChanged int p { get { return this._p; } set { this._p = value; PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.p)); } } // also allowed: just nameof(p) ``` ```cs // XAML dependency property public static DependencyProperty AgeProperty = DependencyProperty.Register(nameof(Age), typeof(int), typeof(C)); ``` ```cs // Logging void f(int i) { Log(nameof(f), "method entry"); } ``` ```cs // Attributes [DebuggerDisplay("={" + nameof(getString) + "()}")] class C { string getString() { ... } } ``` # Examples ```cs void f(int x) { nameof(x) } // result "x": Parameter (simple name lookup) ``` ```cs int x=2; nameof(x) // result "x": Local (simple name lookup) ``` ```cs const x=2; nameof(x) // result "x": Constant (simple name lookup) ``` ```cs class C { int x; ... nameof(x) } // result "x": Member (simple name lookup) ``` ```cs class C { void f() {} nameof(f) } // result "f": Member (simple name lookup) ``` ```cs class C { void f() {} nameof(f()) } // result error "This expression does not have a name" ``` ```cs class C { void f(){} void f(int i){} nameof(f) } // result "f": Method-group (simple name lookup) ``` ```cs Customer c; ... nameof(c.Age) // result "Age": Property (member access) ``` ```cs Customer c; ... nameof(c._Age) // result error "_Age is inaccessible due to its protection level: member access ``` ```cs nameof(Tuple.Create) // result "Create": method-group (member access) ``` ```cs nameof(System.Tuple) // result "Tuple": Type (member access). This binds to the non-generic Tuple class; not to all of the Tuple classes. ``` ```cs nameof(System.Exception) // result "Exception": Type (member access) ``` ```cs nameof(List) // result "List": Type (simple name lookup) ``` ```cs nameof(List<>) // result error "type expected": Unbound types are not valid expressions ``` ```cs nameof(List.Length) // result "Length": Member (Member access) ``` ```cs nameof(default(List)) // result error "This expression doesn't have a name": Not one of the allowed forms of nameof ``` ```cs nameof(default(List).Length) // result error "This expression cannot be used for nameof": default isn't one of the allowed forms ``` ```cs nameof(int) // result error "Invalid expression term 'int'": Not an expression. Note that 'int' is a keyword, not a name. ``` ```cs nameof(System.Int32) // result "Int32": Type (member access) ``` ```cs using foo=System.Int32; nameof(foo) // result "foo": Type (simple name lookup) ``` ```cs nameof(System.Globalization) // result "Globalization": Namespace (member access) ``` ```cs nameof(x[2]) nameof("hello") nameof(1+2) // error "This expression does not have a name": Not one of the allowed forms of nameof ``` ```vb NameOf(a!Foo) ' error "This expression does not have a name": VB-specific. Not one of the allowed forms of NameOf. ``` ```vb NameOf(dict("Foo")) ' error "This expression does not have a name": VB-specific. This is a default property access, which is not one of the allowed forms. ``` ```vb NameOf(dict.Item("Foo")) ' error "This expression does not have a name": VB-specific. This is an index of a property, which is not one of the allowed forms. ``` ```vb NameOf(arr(2)) ' error "This expression does not have a name": VB-specific. This is an array element index, which is not one of the allowed forms. ``` ```vb Dim x = Nothing NameOf(x.ToString(2)) ' error "This expression does not have a name": VB-specific. This resolves to .ToString()(2), which is not one of the allowed forms. ``` ```vb Dim o = Nothing NameOf(o.Equals) ' result "Equals". Method-group. Warning "Access of static member of instance; instance will not be evaluated": VB-specific. VB allows access to static members off instances, but emits a warning. ``` ```cs [Foo(nameof(C))] class C {} // result "C": Nameof works fine in attributes, using the normal name lookup rules. ``` ```cs [Foo(nameof(D))] class C { class D {} } // result "D": Members of a class are in scope for attributes on that class ``` ```cs [Foo(nameof(f))] class C { void f() {} } // result "f": Members of a class are in scope for attributes on that class ``` ```cs [Foo(nameof(T))] class C {} // result error "T is not defined": A class type parameter is not in scope in an attribute on that class ``` ```cs [Foo(nameof(T))] void f { } // result error "T not defined": A method type parameter is not in scope in an attribute on that method ``` ```cs void f([Attr(nameof(x))] int x) {} // result error "x is not defined": A parameter is not in scope in an attribute on that parameter, or any parameter in the method ``` ```vb Function f() nameof(f) End Function ' result "f": VB-specific. This is resolved as an expression which binds to the implicit function return variable ``` ```vb NameOf(New) ' result error "this expression does not have a name": VB-specific. Not one of the allowed forms of nameof. Note that New is not a name; it is a keyword used for construction. ``` ```vb Class C Dim x As Integer Dim s As String = NameOf(x) End Class ' result "x": Field (simple name lookup) ``` ```cs class C { int x; string s = nameof(x); } // result "x". Field (simple name lookup) ``` ```cs class C { static int x; string s = nameof(x); } // result "x". Field (simple name lookup) ``` ```cs class C { int x; string s = nameof(C.x); } // result "x". Member (member access) ``` ```cs class C { int x; string s = nameof(default(C).x); } // result error "This expression isn't allowed in a nameof argument" - default. ``` ```cs struct S { int x; S() {var s = nameof(x); ...} } // result "x": Field access (simple name lookup). Nameof argument is considered unreachable, and so this doesn't violate definite assignment. ``` ```cs int x; ... nameof(x); x=1; // result "x": Local access (simple name lookup). Nameof argument is unreachable, and so this doesn't violate definite assignment. ``` ```cs int x; nameof(f(ref x)); // result error "this expression does not have a name". ``` ```cs var @int=5; nameof(@int) // result "int": C#-specific. Local (simple name lookup). The leading @ is removed. ``` ```cs nameof(m\u200c\u0065) // result "me": C#-specific. The Unicode escapes are first resolved, and the formatting character \u200c is removed. ``` ```vb Dim [Sub]=5 : NameOf([Sub]) ' result "Sub": VB-specific. Local (simple name lookup). The surrounding [.] is removed. ``` ```cs class C { class D {} class D {} nameof(C.D) } // result "D" and binds to the non-generic form: member access only finds the type with the matching arity. ``` ```cs class C where T:Exception { ... nameof(C) } // result error: the type 'string' doesn't satisfy the constraints ``` # [String Interpolation for C#](http://1drv.ms/1tFUvbq) # An *interpolated string* is a way to construct a value of type `String` (or `IFormattable`) by writing the text of the string along with expressions that will fill in "holes" in the string. The compiler constructs a format string and a sequence of fill-in values from the interpolated string. When it is treated as a value of type `String`, it is a shorthand for an invocation of ```cs String.Format(string format, params object args[]) ``` When it is converted to the type `IFormattable`, the result of the string interpolation is an object that stores a compiler-constructed *format string* along with an array storing the evaluated expressions. The object's implementation of ```cs IFormattable.ToString(string format, IFormatProvider formatProvider) ``` is an invocation of ```cs String.Format(IFormatProviders provider, String format, params object args[]) ``` By taking advantage of the conversion from an interpolated string expression to `IFormattable`, the user can cause the formatting to take place later in a selected locale. See the section `System.Runtime.CompilerServices.FormattedString` for details. Note: the converted interpolated string may have more "holes" in the format string than there were interpolated expression holes in the interpolated string. That is because some characters (such as `"\{"` `"}"`) may be translated into a hole and a corresponding compiler-generated fill-in. ## Lexical Grammar ## An interpolated string is treated initially as a token with the following lexical grammar: ``` interpolated-string: $ " " $ " interpolated-string-literal-characters " interpolated-string-literal-characters: interpolated-string-literal-part interpolated-string-literal-parts interpolated-string-literal-part interpolated-string-literal-part: single-interpolated-string-literal-character simple-escape-sequence hexadecimal-escape-sequence unicode-escape-sequence interpolation simple-escape-sequence: one of \' \" \\ \0 \a \b \f \n \r \t \v \{ \} single-interpolated-string-literal-character: Any character except " (U+0022), \ (U+005C), { (U+007B) and new-line-character interpolation: { interpolation-contents } interpolation-contents: balanced-text balanced-text : interpolation-format balanced-text: balanced-text-part balanced-text-part balanced-text balanced-text-part Any character except ", (, [, {, /, \ and new-line-character ( balanced-text ) { balanced-text } [ balanced-text ] regular-string-literal delimited-comment unicode-escape-sequence / after-slash after-slash Any character except ", (, [, {, /, \, * and new-line-character ( balanced-text ) { balanced-text } [ balanced-text ] regular-string-literal * delimited-comment-text[opt] asterisks / unicode-escape-sequence interpolation-format: regular-string-literal literal-interpolation-format literal-interpolation-format: interpolation-format-part interpolation-format-part literal-interpolation-format interpolation-format-part Any character except ", :, \, } and new-line-character ``` With the additional restriction that a *delimited-comment-text* that is a *balanced-text-part* may not contain a *new-line-character*. This lexical grammar is ambiguous in that it allows a colon appearing in *interpolation-contents* to be considered part of the *balanced-text*, or as the separator between the *balanced-text* and the *interpolation-format*. This ambiguity is resolved by considering it to be a separator between the *balanced-text* and *interpolation-format*. ## Syntactic Grammar ## An *interpolated-string* token is reclassified, and portions of it are reprocessed lexically and syntactically, during syntactic analysis as follows: - If the *interpolated-string* contains no *interpolation*, then it is reclassified as a *regular-string-literal*. - Otherwise - the portion of the *interpolated-string* before the first *interpolation* is reclassified as an *interpolated-string-start* terminal; - the portion of the *interpolated-string* after the last *interpolation* is reclassified as an *interpolated-string-end* terminal; - the portion of the *interpolated-string* between one *interpolation* and another *interpolation* is reclassified as an *interpolated-string-mid* terminal; - the *balanced-text* of each *interpolation-contents* is reprocessed according to the language's lexical grammar, yielding a sequence of terminals; - the colon in each *interpolation-contents* that contains an *interpolation-format* is classified as a colon terminal; - each *interpolation-format* is reclassified as a *regular-string-literal* terminal; and - the resulting sequence of terminals undergoes syntactic analysis as an *interpolated-string-expression*. ``` expression: interpolated-string-expression interpolated-string-expression: interpolated-string-start interpolations interpolated-string-end interpolations: single-interpolation single-interpolation interpolated-string-mid interpolations single-interpolation: interpolation-start interpolation-start : regular-string-literal interpolation-start: expression expression , expression ``` ## Semantics ## An *interpolated-string-expression* has type `string`, but there is an implicit *conversion from expression* from an *interpolated-string-expression* to the type `System.IFormattable`. By the existing rules of the language (7.5.3.3 Better conversion from expression), the conversion to `string` is a better conversion from expression. An *interpolated-string-expression* is translated into an intermediate *format string* and *object array* which capture the contents of the interpolated string using the semantics of [Composite Formatting](http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx "Composite Formatting"). If treated as a value of type `string`, the formatting is performed using `string.Format(string format, params object[] args)` or equivalent code. If it is converted to `System.IFormattable`, an object of type [`System.Runtime.CompilerServices.FormattedString`](#FormattedString) is constructed using the format string and argument array, and that object is the value of the *interpolated-string-expression*. The format string is constructed of the literal portions of the *interpolated-string-start*, *interpolated-string-mid*, and *interpolated-string-end* portions of the expression, with special treatment for `{` and `}` characters (see [Notes](#Notes)). **The evaluation order needs to be specified**. **The definite assignment rules need to be specified**. ## single-interpolation Semantics ## **This section should describe in detail the construction of a [*format item*](http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx) from a single-interpolation, and the corresponding element of the object array**. If an *interpolation-start* has a comma and a second expression, the second expression must evaluate to a compile-time constant of type `int`, which is used as the [*alignment* of a *format item*](http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx). If a *single-interpolation* has a colon and a *regular-string-literal*, then the string literal is used as the [*formatString* of a *format item*](http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx). ## Notes ## The compiler is free to translate an interpolated string into a format string and object array where the number of objects in the object array is not the same as the number of interpolations in the *interpolated-string-expression*. In particular, the compiler may translate `{` and `}` characters into a fill-in in the format string and a corresponding string literal containing the character. For example, the interpolated string `$"\{ {n} \}"` may be translated to `String.Format("{0} {1} {2}", "{", n, "}")`. The compiler is free to use any overload of `String.Format` in the translated code, as long as doing so preserves the semantics of calling `string.Format(string format, params object[] args)`. ## Examples ## The interpolated string ``` $"{hello}, {world}!" ``` is translated to ``` String.Format("{0}, {1}!", hello, world) ``` The interpolated string ``` $"Name = {myName}, hours = {DateTime.Now:hh}" ``` is translated to ``` String.Format("Name = {0}, hours = {1:hh}", myName, DateTime.Now) ``` The interpolated string ``` $"\{{6234:D}\}" ``` is translated to ``` String.Format("{0}{1:D}{2}", "{", 6234, "}") ``` For example, if you want to format something in the invariant locale, you can do so using this helper method ```cs public static string INV(IFormattable formattable) { return formattable.ToString(null, System.Globalization.CultureInfo.InvariantCulture); } ``` and writing your interpolated strings this way ```cs string coordinates = INV("longitude={longitude}; latitude={latitude}"); ``` ## System.Runtime.CompilerServices.FormattedString ## The following platform class is used to translate an interpolated string to the type `System.IFormattable`. ```cs namespace System.Runtime.CompilerServices { public class FormattedString : System.IFormattable { private readonly String format; private readonly object[] args; public FormattedString(String format, params object[] args) { this.format = format; this.args = args; } string IFormattable.ToString(string ignored, IFormatProvider formatProvider) { return String.Format(formatProvider, format, args); } } } ``` ## Issues ## 1. As specified, an interpolated string with no interpolations cannot be converted to `IFormattable` because it is a string literal. It should have such a conversion. ================================================ FILE: meetings/2014/README.md ================================================ # C# Language Design Notes for 2014 Overview of meetings and agendas for 2014 ## Jan 6, 2014 [C# Language Design Notes for Jan 6, 2014](LDM-2014-01-06.md) 1. Syntactic ambiguities with declaration expressions <_a solution adopted_> 2. Scopes for declaration expressions <_more refinement added to rules_> ## Feb 3, 2014 [C# Language Design Notes for Feb 3, 2014](LDM-2014-02-03.md) 1. Capture of primary constructor parameters <_only when explicitly asked for with new syntax_> 2. Grammar around indexed names <_details settled_> 3. Null-propagating operator details <_allow indexing, bail with unconstrained generics_> ## Feb 10, 2014 [C# Language Design Notes for Feb 10, 2014](LDM-2014-02-10.md) 1. Design of using static <_design adopted_> 2. Initializers in structs <_allow in certain situations_> 3. Null-propagation and unconstrained generics <_keep current design_> ## Apr 21, 2014 [C# Language Design Notes for Apr 21, 2014](LDM-2014-04-21.md) 1. Indexed members <_lukewarm response, feature withdrawn_> 2. Initializer scope <_new scope solves all kinds of problems with initialization_> 3. Primary constructor bodies <_added syntax for a primary constructor body_> 4. Assignment to getter-only auto-properties from constructors <_added_> 5. Separate accessibility for type and primary constructor <_not worthy of new syntax_> 6. Separate doc comments for field parameters and fields <_not worthy of new syntax_> 7. Left associative vs short circuiting null propagation <_short circuiting_> ## May 7, 2014 [C# Language Design Notes for May 7, 2014](LDM-2014-05-07.md) 1. protected and internal <_feature cut – not worth the confusion_> 2. Field parameters in primary constructors <_feature cut – we want to keep the design space open_> 3. Property declarations in primary constructors <_interesting but not now_> 4. Typeswitch <_Not now – more likely as part of a future more general matching feature_> ## May 21, 2014 [C# Language Design Notes for May 21, 2014](LDM-2014-05-21.md) 1. Limit the nameof feature? <_keep current design_> 2. Extend params IEnumerable? <_keep current design_> 3. String interpolation <_design nailed down_> ## Jul 9, 2014 [C# Language Design Notes for Jul 9, 2014](LDM-2014-07-09.md) 1. Detailed design of nameof <_details settled_> 2. Design of #pragma warning extensions <_allow identifiers_> ## Aug 27, 2014 [C# Language Design Notes for Aug 27, 2014](LDM-2014-08-27.md) 1. Allowing parameterless constructors in structs <_allow, but some unresolved details_> 2. Definite assignment for imported structs <_revert to Dev12 behavior_> ## Sep 3, 2014 [C# Language Design Notes for Sep 3, 2014](LDM-2014-09-03.md) 1. Removing “spill out” from declaration expressions in simple statements <_yes, remove_> 2. Same name declared in subsequent else-if’s <_condition decls out of scope in else-branch_> 3. Add semicolon expressions <_not in this version_> 4. Make variables in declaration expressions readonly <_no_> ## Oct 1, 2014 [C# Language Design Notes for Oct 1, 2014](LDM-2014-10-01.md) 1. Assignment to readonly autoprops in constructors (we fleshed out details) 2. A new compiler warning to prevent outsiders from implementing your interface? (no, leave this to analyzers) ## Oct 15, 2014 [C# Language Design Notes for Oct 15, 2014](LDM-2014-10-15.md) 1. nameof operator: spec v5 2. [String Interpolation for C#](http://1drv.ms/1tFUvbq) ================================================ FILE: meetings/2015/LDM-2015-01-21.md ================================================ C# Design Meeting Notes for Jan 21, 2015 ======================================== Discussion thread on these notes can be found at https://github.com/dotnet/roslyn/issues/98. Quotes of the day: > Live broadcast of design meetings: we could call it C#-SPAN > > We've made it three hours without slippery slopes coming up! Agenda ------ This is the first design meeting for the version of C# coming after C# 6. We shall colloquially refer to it as C# 7. The meeting focused on setting the stage for the design process and homing in on major themes and features. 1. Design process 2. Themes 3. Features See also [Language features currently under consideration by the language design group](https://github.com/dotnet/roslyn/issues?q=is%3Aopen+label%3A%22Area-Language+Design%22+label%3A%221+-+Planning%22+ "Language Features Under Consideration"). 1\. Design process ================== We have had great success sharing design notes publicly on CodePlex for the last year of C# 6 design. The ability of the community to see and respond to our thinking in real time has been much appreciated. This time we want to increase the openness further: - we involve the community from the beginning of the design cycle (as per these notes!) - in addition to design notes (now issues on GitHub) we will maintain feature proposals (as checked-in Markdown documents) to reflect the current design of the feature - we will consider publishing recordings of the design meetings themselves, or even live streaming - we will consider adding non-Microsoft members to the design team. Design team ----------- The C# 7 design team currently consists of - [Anders Hejlsberg](https://github.com/ahejlsberg) - [Mads Torgersen](https://github.com/MadsTorgersen) - [Lucian Wischik](https://github.com/ljw1004) - [Matt Warren](https://github.com/mattwar) - [Neal Gafter](https://github.com/gafter) - [Anthony D. Green](https://github.com/AnthonyDGreen) - [Stephen Toub](https://github.com/stephentoub) - [Kevin Pilch-Bisson](https://github.com/Pilchie) - [Vance Morrison](https://github.com/vancem) - [Immo Landwerth](https://github.com/terrajobst) Anders, as the chief language architect, has ultimate say, should that ever become necessary. Mads, as the language PM for C#, pulls together the agenda, runs the meetings and takes the notes. (Oooh, the power!) To begin with, we meet 4 hours a week as we decide on the overall focus areas. There will not be a separate Visual Basic design meeting during this initial period, as many of the overall decisions are likely to apply to both and need to happen in concert. Feature ideas ------------- Anyone can put a feature idea up as an *issue* on GitHub. We'll keep an eye on those, and use them as input to language design. A way to gauge interest in a feature is to put it up on UserVoice, where there's a voting system. This is important, because the set of people who hang out in our GitHub repo are not necessarily representative of our developer base at large. Design notes ------------ Design notes are point-in-time documents, so we will put them up as *issues* on GitHub. For a period of time, folks can comment on them and the reactions will feed into subsequent meetings. Owners and proposals -------------------- If the design team decides to move on with a feature idea, we'll nominate an *owner* for it, typically among the design team members, who will drive the activities related to the design of that feature: gathering feedback, making progress between meetings, etc. Most importantly, the owner will be responsible for maintaining a *proposal* document that describes the current state of that feature, cross-linking with the design notes where it was discussed. Since the proposals will evolve over time, they should be documents in the repo, with history tracked. When the proposal is first put up, and if there are major revisions, we will probably put up an issue too, as a place to gather comments. There can also be pull requests to the proposals. We'll play with this process and find a balance. Other ways of increasing openness --------------------------------- We are very interested in other ideas, such as publishing recordings (or even live streaming?) of the design meeting themselves, and inviting non-Microsoft luminaries, e.g., from major players in the industry, onto the design team itself. We are certainly open to have "guests" (physical or virtual) when someone has insights that we want to leverage. However, these are things we can get to over time. We are not going to do them right out of the gate. Decisions --------- It's important to note that the C# design team is still in charge of the language. This is not a democratic process. We derive immense value from comments and UserVoice votes, but in the end the governance model for C# is benevolent dictatorship. We think design in a small close-knit group where membership is long-term is the right model for ensuring that C# remains tasteful, consistent, not too big and generally not "designed by committee". If we don't agree within the design team, that is typically a sign that there are offline activities that can lead to more insight. Usually, at the end of the day, we don't need to vote or have the Language Allfather make a final call. Prototypes ---------- Ideally we should prototype every feature we discuss, so as to get a good feel fro the feature and allow the best possible feedback from the community. That may note be realistic, but once we have a good candidate feature, we should try to fly it. The cost of the prototyping is an issue. This may be feature dependent: Sometimes you want a quick throwaway prototype, sometimes it's more the first version of an actual implementation. Could be done by a member of the design team, the product team or the community. Agenda ------ It's usually up to Mads to decide what's ready to discuss. Generally, if a design team member wants something on the agenda, they get it. There's no guarantee that we end up following the plan in the meeting; the published notes will just show the agenda as a summary of what was *actually* discussed. 2\. Themes ========== If a feature is great, we'll want to add it whether it fits in a theme or not. However, it's useful to have a number of categories that we can rally around, and that can help select features that work well together. We discussed a number of likely themes to investigate for C# 7. Working with data ----------------- Today’s programs are connected and trade in rich, structured data: it’s what’s on the wire, it’s what apps and services produce, manipulate and consume. Traditional object-oriented modeling is good for many things, but in many ways it deals rather poorly with this setup: it bunches functionality strongly with the data (through encapsulation), and often relies heavily on mutation of that state. It is "behavior-centric" instead of "data-centric". Functional programming languages are often better set up for this: data is immutable (representing *information*, not *state*), and is manipulated from the outside, using a freely growable and context-dependent set of functions, rather than a fixed set of built-in virtual methods. Let’s continue being inspired by functional languages, and in particular other languages – F#, Scala, Swift – that aim to mix functional and object-oriented concepts as smoothly as possible. Here are some possible C# features that belong under this theme: - pattern matching - tuples - "denotable" anonymous types - "records" - compact ways of describing shapes - working with common data structures (List/Dictionary) - extension members - slicing - immutability - structural typing/shapes? A number of these features focus on the interplay between "kinds of types" and the ways they are used. It is worth thinking of this as a matrix, that lets you think about language support for e.g. denoting the types (*type expressions*), creating values of them (*literals*) and consuming them with matching (*patterns*) : | Type | Denote | Create | Match | |------------|-------------------------|------------------------------|--------------------------| | General | `T` | `new T()`, `new T { x = e }` | `T x`, `var x`, `*` | | Primitive | `int`, `double`, `bool` | `5`, `.234`, `false` | `5`, `.234`, `false` | | String | `string` | `"Hello"` | `"Hello"` | | Tuple | `(T1, T2)` | `(e1, e2)` | `(P1, P2)` | | Record | `{ T1 x1, T2 x2 }` | `new { x1 = e1, x2 = e2 }` | `{ x1 is P1, x2 is P2 }` | | Array | `T[]` | `new T[e]`, `{ e1, e2 }` | `{ P1, P2 }`, `P1 :: P2` | | List | ? | ? | ? | | Dictionary | ? | ? | ? | | ... | | | | A lot of the matrix above is filled in with speculative syntax, just to give an idea of how it could be used. We expect to give many of the features on the list above a lot of attention over the coming months: they have a lot of potential for synergy if they are designed together. Performance and reliability (and interop) ----------------------------------------- C# and .NET has a heritage where it sometimes plays a bit fast and loose with both performance and reliability. While (unlike, say, Java) it has structs and reified generics, there are still places where it is hard to get good performance. A top issue, for instance is the frequent need to copy, rather than reference. When devices are small and cloud compute cycles come with a cost, performance certainly starts to matter more than it used to. On the reliability side, while (unlike, say, C and C++) C# is generally memory safe, there are certainly places where it is hard to control or trust exactly what is going on (e.g., destruction/finalization). Many of these issues tend to show up in particular on the boundary to unmanaged code - i.e. when doing interop. Having coarse-grained interop isn't always an option, so the less it costs and the less risky it is to cross the boundary, the better. Internally at Microsoft there have been research projects to investigate options here. Some of the outcomes are now ripe to feed into the design of C# itself, while others can affect the .NET Framework, result in useful Roslyn analyzers, etc. Over the coming months we will take several of these problems and ideas and see if we can find great ways of putting them in the hands of C# developers. Componentization ---------------- The once set-in-stone issue of how .NET programs are factored and combined is now under rapid evolution. With generalized extension members as an exception, most work here may not fall in the language scope, but is more tooling-oriented: - generating reference assemblies - static linking instead of IL merge - determinism - NuGet support - versioning and adaptive light-up This is a theme that shouldn't be driven primarily from the languages, but we should be open to support at the language level. Distribution ------------ There may be interesting things we can do specifically to help with the distributed nature of modern computing. - Async sequences: We introduced single-value asynchrony in C# 5, but do not yet have a satisfactory approach to asynchronous sequences or streams - Serialization: we may no longer be into directly providing built-in serialization, but we need to make sure we make it reasonable to custom-serialize data - even when it's immutable, and without requiring costly reflection. Also, await in catch and finally probably didn't make it into VB 14. We should add those the next time around. Metaprogramming --------------- Metaprogramming has been around as a theme on the radar for a long time, and arguably Roslyn is a big metaprogramming project aimed at writing programs about programs. However, at the language level we continue not to have a particularly good handle on metaprogramming. Extension methods and partial classes both feel like features that could grow into allowing *generated* parts of source code to merge smoothly with *hand-written* parts. But if generated parts are themselves the result of language syntax - e.g. attributes in source code, then things quickly get messy from a tooling perspective. A keystroke in file A may cause different code to be generated into file B by some custom program, which in turn may change the meaning of A. Not a feedback loop we're eager to have to handle in real time at 20 ms keystroke speed! Oftentimes the eagerness to generate source comes from it being too hard to express your concept beautifully as a library or an abstraction. Increasing the power of abstraction mechanisms in the language itself, or just the syntax for applying them, might remove a lot of the motivation for generated boilerplate code. Features that may reduce the need for boilerplate and codegen: - Virtual extension methods/default interface implementations - Improvements to generic constraints, e.g.: - generic constructor constraints - delegate and enum constraints - operators or object shapes as constraints (or interfaces), e.g. similar to C++ concepts - mixins or traits - delegation Null ---- With null-conditional operators such as `x?.y` C# 6 starts down a path of more null-tolerant operations. You could certainly imagine taking that further to allow e.g. awaiting or foreach'ing null, etc. On top of that, there's a long-standing request for non-nullable reference types, where the type system helps you ensure that a value can't be null, and therefore is safe to access. Importantly such a feature might go along well with proper safe *nullable* reference types, where you simply cannot access the members until you've checked for null. This would go great with pattern matching! Of course that'd be a lot of new expressiveness, and we'd have to reconcile a lot of things to keep it compatible. In his [blog](http://blog.coverity.com/2013/11/20/c-non-nullable-reference-types), Eric Lippert mentions a number of reasons why non-nullable reference types would be next to impossible to fully guarantee. To be fully supported, they would also have to be known to the runtime; they couldn't just be handled by the compiler. Of course we could try to settle for a less ambitious approach. Finding the right balance here is crucial. Themeless in Seattle -------------------- *Type providers*: This is a whole different kind of language feature, currently known only from F#. We wouldn't be able to just grab F#'s model though; there'd be a whole lot of design work to get this one right! *Better better betterness*: In C# we made some simplifications and generalizations to overload resolution, affectionately known as "better betterness". We could think of more ways to improve overload resolution; e.g. tie breaking on staticness or whether constraints match, instead of giving compiler errors when other candidates would work. *Scripting*: The scripting dialect of C# includes features not currently allowed in C# "proper": statements and member declarations at the top level. We could consider adopting some of them. *params IEnumerable*. *Binary literals and digit separators*. 3\. Features ============ The Matrix above represents a feature set that's strongly connected, and should probably be talked about together: we can add kinds of types (e.g. tuples, records), we can add syntax for representing those types or creating instances of them, and we can add ways to match them as part of a greater pattern matching scheme. Pattern matching ---------------- Core then is to have a pattern matching framework in the language: A way of asking if a piece of data has a particular shape, and if so, extracting pieces of it. ``` c# if (o is Point(var x, 5)) ... ``` There are probably at least two ways you want to use "patterns": 1. As part of an expression, where the result is a bool signaling whether the pattern matched a given value, and where variables in the pattern are in scope throughout the statement in which the pattern occurs. 2. As a case in a switch statement, where the case is picked if the pattern matches, and the variables in the pattern are in scope throughout the statements of that case. A strong candidate syntax for the expression syntax is a generalization of the `is` expression: we consider the type in an `is` expression just a special case, and start allowing any pattern on the right hand side. Thus, the following would be valid `is` expressions: ``` c# if (o is Point(*, 5) p) Console.WriteLine(o.x); if (o is Point p) Console.WriteLine(p.x); if (p is (var x, 5) ... ``` Variable declarations in an expression would have the same scope questions as declaration expressions did. A strong candidate for the switch syntax is to simply generalize current switch statements so that - the switch expression can be any type - the case labels can contain patterns, not just constants - the cases are checked in order of appearance, since they can now overlap ``` c# switch (o) { case string s: Console.WriteLine(s); break; case int i: Console.WriteLine($"Number {i}"); break; case Point(int x, int y): Console.WriteLine("({x},{y})"); break; case null: Console.WriteLine("); break } ``` Other syntaxes you can think of: *Expression-based switch*: An expression form where you can have multiple cases, each producing a result value of the same type. *Unconditional deconstruction*: It might be useful to separate the deconstruction functionality out from the checking, and be able to unconditionally extract parts from a value that you know the type of: ``` c# (var x, var y) = getPoint(); ``` There is a potential issue here where the value could be null, and there's no check for it. It's probably ok to have a null reference exception in this case. It would be a design goal to have symmetry between construction and deconstruction syntaxes. Patterns *at least* have type testing, value comparison and deconstruction aspects to them. There may be ways for a type to specify its deconstruction syntax. In addition it is worth considering something along the lines of "active patterns", where a type can specify logic to determine whether a pattern applies to it or not. Imagine positional deconstruction or active patterns could be expressed with certain methods: ``` c# class Point { public Point(int x, int y) {...} void Deconstruct(out int x, out int y) { ... } static bool Match(Point p, out int x, out int y) ... static bool Match(JObject json, out int x, out int y) ... } ``` We could imagine separate syntax for specifying this. One pattern that does not put new requirements on the type is matching against properties/fields: ``` c# if (o is Point { X is var x, Y is 0 }) ... ``` Open question: are the variables from patterns mutable? This has a strong similarity to declaration expressions, and they could coexist, with shared scope rules. Records ------- Let's not go deep on records now, but we are aware that we need to reconcile them with primary constructors, as well as with pattern matching. Array Slices ------------ One feature that could lead to a lot of efficiency would be the ability to have "windows" into arrays - or even onto unmanaged swaths of memory passed along through interop. The amount of copying that could be avoided in some scenarios is probably very significant. Array slices represent an interesting design dilemma between performance and usability. There is nothing about an array slice that is functionally different from an array: You can get its length and access its elements. For all intents and purposes they are indistinguishable. So the best user experience would certainly be that slices just *are* arrays - that they share the same type. That way, all the existing code that operates on arrays can work on slices too, without modification. Of course this would require quite a change to the runtime. The performance consequences of that could be negative even on the existing kind of arrays. As importantly, slices themselves would be more efficiently represented by a struct type, and for high-perf scenarios, having to allocate a heap object for them might be prohibitive. One intermediate approach might be to have slices be a struct type Slice\, but to let it implicitly convert to T[] in such a way that the underlying storage is still shared. That way you can use Slice\ for high performance slice manipulation (e.g. in recursive algorithms where you keep subdividing), but still make use of existing array-based APIs at the cost of a boxing-like conversion allocating a small object. ref locals and ref returns -------------------------- Just like the language today has ref parameters, we could allow locals and even return values to be by `ref`. This would be particularly useful for interop scenarios, but could in general help avoid copying. Essentially you could return a "safe pointer" e.g. to a slot in an array. The runtime already fully allows this, so it would just be a matter of surfacing it in the language syntax. It may come with a significant conceptual burden, however. If a method call can return a *variable* as opposed to a *value*, does that mean you can now assign to it?: ``` c# m(x, y) = 5; ``` You can now imagine getter-only properties or indexers returning refs that can be assigned to. Would this be quite confusing? There would probably need to be some pretty restrictive guidelines about how and why this is used. readonly parameters and locals ------------------------------ Parameters and locals can be captured by lambdas and thereby accessed concurrently, but there's no way to protect them from shared-mutual-state issues: they can't be readonly. In general, most parameters and many locals are never intended to be assigned to after they get their initial value. Allowing `readonly` on them would express that intent clearly. One problem is that this feature might be an "attractive nuisance". Whereas the "right thing" to do would nearly always be to make parameters and locals readonly, it would clutter the code significantly to do so. An idea to partly alleviate this is to allow the combination `readonly var` on a local variable to be contracted to `val` or something short like that. More generally we could try to simply think of a shorter keyword than the established `readonly` to express the readonly-ness. Lambda capture lists -------------------- Lambda expressions can refer to enclosing variables: ``` c# var name = GetName(); var query = customers.Where(c => c.Name == name); ``` This has a number of consequences, all transparent to the developer: - the local variable is lifted to a field in a heap-allocated object - concurrent runs of the lambda may access and even modify the field at the same time - because of implementation tradeoffs the content of the variable may be kept live by the GC, sometimes even after lambdas directly using them cease to exist. For these reasons, the recently introduced lambdas in C++ offer the possibility for a lambda to explicitly specify what can be captured (and how). We could consider a similar feature, e.g.: ``` c# var name = GetName(); var query = customers.Where([name]c => c.Name == name); ``` This ensures that the lambda only captures `name` and no other variable. In a way the most useful annotation would be the empty `[]`, making sure that the lambda is never accidentally modified to capture *anything*. One problem is that it frankly looks horrible. There are probably other syntaxes we could consider. Indeed we need to think about the possibility that we would ever add nested functions or class declarations: whatever capture specification syntax we come up with would have to also work for them. C# always captures "by reference": the lambda can observe and effect changes to the original variable. An option with capture lists would be to allow other modes of capture, notable "by value", where the variable is copied rather than lifted: ``` c# var name = GetName(); var query = customers.Where([val name]c => c.Name == name); ``` This might not be *too* useful, as it has the same effect as introducing another local initialized to the value of the original one, and then capture *that* instead. If we don't want capture list as a full-blown feature, we could consider allowing attributes on lambdas and then having a Roslyn analyzer check that the capture is as specified. Method contracts ---------------- .NET already has a contract system, that allows annotation of methods with pre- and post-conditions. It grew out of the Spec# research project, and requires post-compile IL rewriting to take full effect. Because it has no language syntax, specifying the contracts can get pretty ugly. It has often been proposed that we should add specific contract syntax: ``` c# public void Remove(string item) requires item != null ensures Count >= 0 { ... } ``` One radical idea is for these contracts to be purely runtime enforced: they would simply turn into checks throwing exceptions (or FailFast'ing - an approach that would need further discussion, but seems very attractive). When you think about how much code is currently occupied with arguments and result checking, this certainly seems like an attractive way to reduce code bloat and improve readability. Furthermore, the contracts can produce metadata that can be picked up and displayed by tools. You could imagine dedicated syntax for common cases - notably null checks. Maybe that is the way we get some non-nullability into the system? ================================================ FILE: meetings/2015/LDM-2015-01-28.md ================================================ C# Design Meeting Notes for Jan 28, 2015 ======================================== Discussion thread for these notes can be found at https://github.com/dotnet/roslyn/issues/180. Quote of the day: > It's not turtles all the way down, it's frogs. :-) Agenda ------ 1. Immutable types 2. Safe fixed-size buffers 3. Pattern matching 4. Records See also [Language features currently under consideration by the language design group](https://github.com/dotnet/roslyn/issues?q=is%3Aopen+label%3A%22Area-Language+Design%22+label%3A%221+-+Planning%22+ "Language Features Under Consideration"). 1\. Immutable types =================== In research prototypes we've experimented with an `immutable` modifier on types, indicating that objects of the type are *deeply* immutable - they recursively do not point to mutable fields. Issue #159 describes the proposal in more detail. How do we construct types which once fully constructed can't be changed? - all fields are readonly, and recursively have immutable types - can only inherit from other immutable types (or `object`) - the constructor can't use "this" other than to access fields `unsafe` or some other notation could be used to escape scrutiny, in order to create "observable immutability" while cheating under the hood (typically for performance reasons). You could factor such unsafeness into a few types, e.g. `Lazy`. The feature is designed to work with generics. There would be a new constraint `where T: immutable`. However don't want to bifurcate on `Tuple` vs `ImmutableTuple` just based on whether the content type is constrained to `immutable`. Instead an immutable `Tuple` would instantiate to immutable types *only* if type arguments are all immutable. So `Tuple` would be immutable, `Tuple` wouldn't be. Immutable generic types would allow type parameters in fields, because that still maintains the recursive guarantee. Immutable interfaces are also part of the proposal. Somewhat strangely an immutable interface can only be implemented by types that pass all their non-immutable type parameters to the interface! What's the value? It's mostly a tool for the compiler to help you ensure you are following your intent of writing deeply immutable types. Why is that a valuable property to ensure in objects? - it would allow safe parallel operations over them (modulo holes, see below) - it would allow burning an object graph into the DLL by compile time evaluation of static initializers - they can be passed to others without defensive copying Immutable delegates are ones that can only bind to methods on immutable types. At the language level, that means closures would need to be generated as immutable when possible - which it won't often be, unless we adopt readonly parameters and locals (#98, #115). As for choice of keyword: `readonly` indicates "shallow", that's why `immutable` may be a better word. Given the restrictions, you'd expect that any method call on an immutable type would have side effects only on data that was passed in to the method - so a parameterless method (or one taking only immutable parameters) would essentially be pure. Unfortunately that is not quite true. This expectation can be undermined by two things (other than the built-in facility for cheating): mutable static fields and reflection. We can probably live with reflection, that's already a way to undermine so many other language level expectations! However, mutable statics are unfortunate, not just for this scenario but in general. It would be a breaking change to start disallowing them, of course, bu they could be prevented with a Roslyn analyzer. Even then, while not having side effects, calling such a method twice with the same arguments might not yield the same result: even returning a new object isn't idempotent. Given the holes and gotchas, the question is whether it is still valuable enough to have this feature? If it's not a full guarantee but mostly a help to not make mistakes, maybe we should do this through attributes and analyzers? The problem with analyzers is that you can't rely on other folks to run them on their code that you depend on. It wouldn't e.g. prevent defensive copies. In our research project, this turned out to be very valuable in detecting bugs and missed optimization opportunities. The object freezing could be done without the feature just by carefully analyzing static fields. But the feature might better help people structure things to be ripe for it. IDE tooling benefits: Extract method would not need to grab all structs by ref. We would have to consider making it impossible to implement an immutable interface even via the old compiler. Otherwise there's a hole in the system. Something with "modrec"? If we added a bunch of features that introduce readonly objects to C# 7 (like records, tuples, ...) and then add this feature later, would we end up being in trouble? Only if we violated the rules we would end up applying. Marking an existing unsealed type as immutable would be a breaking change. If we introduce such classes in C# 7, it would be breaking to make them immutable later. As a case study, has Roslyn suffered from the lack of this feature? There have been race conditions, but those would still have happened. Is Roslyn not a great example? Probably not. Roslyn is *not* immutable. It's presenting an immutable *view*, but is mutable inside. Would that be the common case, though? Some of the "cheating" in Roslyn (caching, free lists, etc) is for performance, some is for representing cycles. Insofar as the immutable types feature is *also* for performance, it seems that there's a tension between using it or not. In summary, we are unsure of the value. Let's talk more. 2\. Safe Fixed-Size buffers =========================== Why are fixed-size buffers unsafe? We could generate safe code for them - at least when not in unsafe regions. Proposal described at #126. It might generate a lot of code. You could do it with different syntax, or switch on how you generate. This is a very constrained scenario. It wouldn't be harmful, and a few people would celebrate, but is it worth our effort? It would allow arbitrary types, not just primitive like today. That may be the bigger value. Not a high-pri, not one of the first we should commit to. 3\. Pattern matching ==================== A view on pattern matching: A pattern is not an expression, but a separate construct. It can be recursive. It's idempotent, doesn't affect the state of the program or "compute" something. Sktech of possible pattern syntax: > *pattern:*   `*`   *literal*   `var` *identifier*   *type* *identifier*opt   *type* `{` *member* `is` *pattern* ... `}` *identifier*opt > *expression:*   *expression* `is` *pattern* > *switch-label:*   `case` *pattern* `:` Actually we'd separate into simple and complex patterns and only allow some at the top level. We'd have to think carefully about semantics to make it fit into existing is expressions and switch statements. Alternatively we'd come up with a new kind of switch statement. The syntax of switch is already among the least appealing parts of C# - maybe time for a revamp anyway? Additionally we could imagine a switch *expression*, e.g. of the form: ``` c# match (e) { pattern => expression; ... ; default => expression } ``` This would result in a value, so it would have to be complete - exactly one branch would have to be taken. Maybe an expression would be allowed to throw. In fact, `throw` could be made an expression. Expanded `is` operator ---------------------- Here's an example scenario from the Roslyn framework. This should not be taken to say that this is a feature specific to building compilers, though! First, without pattern matching: ``` c# var e = s as ExpressionStatement; if (e != null) { var a = e.Expr as AssignmentExpressionSyntax; if (a != null) { var l = a.Left as IdentifierName; var r = a.Right as IdentifierName; if (l != null && r != null & l.Name.name == r.Name.name) ... ``` Ugh! With just the `e is T x` non-recursive pattern match we can do a lot better, handling everything in a single if condition: ``` c# if (s is ExpressionStatement e && e.Expr is AssignmentExpressionSyntax a && a.Left is IdentifierName l && a.Right is IdentifierName r && l.Name.name == r.Name.name) ... ``` Much more readable! The explicit null checks and the nesting are gone, and everything still has sensible names. The test digs pretty deep inside the structure. Here's what if would look like with recursive `T { ... }` patterns: ``` c# if (s is ExpressionStatement { Expr is AssignmentExpressionSyntax { Left is IdentifierName { Name is val l }, Right is IdentifierName { Name is val r } } } && l.name = r.name) ... ``` Here the pattern match sort of matches the structure of the object itself, nesting patterns for nested objects. It is not immediately obvious which is more readable, though - at least we disagree enthusiastically on the design team about that. It is possible that the nesting approach has more things going for it, though: - patterns might not just be type tests - we could embrace "active patterns" that are user defined - we could do optimized code gen here, since evaluation order might not necessarily be left to right. - the code becomes more readable because the structure of the code matches the shape of the object. - the approach of coming up with more top-level variable names may run out of steam the deeper you go. The `T { ... }` patterns are a little clunky, but would apply to any object. For more conciseness, we imagine that types could specify positional matching, which would be more compactly matched through a `T (...)` pattern.That would significantly shrink the recursive example: ``` c# if (s is ExpressionStatement( AssignmentExpressionSyntax(IdentifierName l, IdentifierName r)) && l.name = r.name) ... ``` This is more concise, but it relies on you knowing what the positions stand for since you are not giving them a name at the consumption site. So, in summary, it's interesting that the `e is T x` form in itself gives most of the value. The incremental improvement of having recursive patterns may not be all that significant. Either way, the built-in null check is likely to help with writing null-safe code. On the tooling side, what would the stepping behavior be? One answer is that there is no stepping behavior. That's already the case inside most expressions. Or we could think it through and come up with something better if the need is there. The variables are definitely assigned only when true. There is a scoping issue with else if. It's similar to the discussions around declaration expressions in C# 6: ``` c# if (o is string s) { ... s ... } else if (o is short s) { ... s ... } // error: redeclaration of s else ... ``` All else equal, though not definitely assigned there, the `string s` introduced in the first if condition would also be in scope in the else branch, and the nested if clause would therefore not be allowed to introduce another variable with the same name `s`. With declaration expressions we discussed having special rules around this, e.g. making variables introduced in an if condition *not* be in scope in the else clause. But this gets weird if you try to negate the condition and swap the branches: all of a sudden the variables would be in scope only where they are *not* definitely assigned. Expanded `switch` statement --------------------------- The above examples are in the context of if(...is...), but for switch statements you can't just put `&&...` after the initial pattern. Maybe we would allow a `where ...` (or `when`) in the case labels to add additional filtering: ``` c# switch (o) { case ExpressionStatement( AssignmentExpressionSyntax(IdentifierName l, IdentifierName r) where (l.name == r.name): ... } ``` If we add pattern matching to switch statements, one side effect is that we generalize the type of thing you can switch on to anything classified as a value. ``` c# object o = ... switch(o) { case 1: case 2: case 3: case Color.Red: case string s: case *: // no case var x: // would have to be last and there'd have to not be a default: default: } ``` We would probably disallow `*`, the wildcard, at the top level. It's only useful in recursive positional notation. Evaluation order would now be important. For back compat, we would allow default everywhere, and evaluate it last. We would diagnose situations where an earlier pattern hides a later one: ``` c# case Point(*, 2): case Point(1, 2): // error/warning ``` We could allow case guards, which would make the case not subsume other cases: ``` c# case Point(var x, 2) where (x > 2): case Point(1, 2): // fine ``` User-defined `is` operator -------------------------- We've sneaked uses of a positional pattern match above. For that to work, a type would have to somehow specify which positional order to match it in. One very general approach to this would be to allow declaration of an `is` operator on types: ``` c# public class Point { public Point(int x, int y) { this.X = x; this.Y = y; } public int X { get; } public int Y { get; } overrides public int GetHashCode() ... overrides public bool Equals(...)... public static bool operator is(Point self out int x, out int y) {...} } ``` The operator `is` is a particular way of specifying custom matching logic, similar to F# active patterns. We could imagine less ambitious ways, in particular if we just want to specify deconstruction and not additional logic. That's something to dive into later. 4\. Records =========== A value-semantics class like the above would be automatically generated by a "record" feature, e.g. from something like: ``` c# 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 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) { public int X { get; set; } = X; } ``` You could have syntax to create separate parameter names from member names: ``` c# class Point(int x:X, int y:Y) { public int X { get; set; } = x; } ``` Whether in this form or otherwise, we definitely want to pursue more concise type declarations that facilitate value semantics and deconstruction. We want to pursue the connection with anonymous types and we want to pursue tuples that mesh well with the story too. ================================================ FILE: meetings/2015/LDM-2015-02-04.md ================================================ C# Design Meeting Notes for Feb 4, 2015 ======================================== Discussion thread on these notes can be found at https://github.com/dotnet/roslyn/issues/396. Agenda ------ 1. Internal Implementation Only (C# 6 investigation) 2. Tuples, records and deconstruction 3. Classes with value semantics 1\. Internal implementation only ================================ We have a versioning problem in the Roslyn APIs that we suspect is somewhat common - that of inherited hierarchies. The essence of the problem is that we want to describe an inheritance *hierarchy* of types in the abstract, as well as several concrete "implementations" of the hierarchy. In Roslyn, the main example is that we have a language agnostic hierarchy of symbols, as well as a C# and VB specific implementation of that. This leads to a need for multiple inheritance: "C#-specific local symbol" needs to both inherit "C#-specific symbol" and "abstract local symbol". The only way to represent this is to express the abstract hierarchy with interfaces: ``` c# public interface ISymbol { ... } public interface ILocalSymbol : ISymbol { ... } public abstract class CSharpSymbol : ISymbol { ... } public class CSharpLocalSymbol : CSharpSymbol, ILocalSymbol { ... } // Etc... ``` So far, so good. However, even though `ISymbol` and friends are interfaces, we don't actually want anyone else to implement them. They are not intended as extension points, we merely made them interfaces to allow multiple base types. On the contrary we would like to be able to add members to these types in the future, as the Roslyn API evolves. Had these types been abstract classes, we could have prevented implementation from other assemblies by having only internal constructors. That would have protected the types for future extensions. However, for interfaces no such trick exists. As a result, if we ever want to evolve these interfaces by adding more members, but someone implemented them *despite our intent*, we will break those people. Our options, current and future, seem to include: 1. Tell people really loudly in comments on the interfaces that they shouldn't implement them, because we will break them 2. Add an attribute, `ImplicitImplementationOnlyAttribute`, to the interfaces 3. Write an analyzer that enforces the attribute, make it be installed and on by default and hope that catches enough cases 4. Make the compiler enforce the attribute 5. Add a mechanism to the language to safely evolve interfaces * default implementations of interface methods (like in Java) - will require CLR support 6. Add an alternative multiple-inheritance mechanism to the language * mixins or traits Options 1, 2 and 3 are open to us today. However, the concern is whether we can consider them strong enough discouragement that our future selves will feel good about evolving the interfaces. If we do 1, 2 and 3, will we then be ready to later break people who disregard the discouragement? We have never been 100% on compat. Reliance on reflection, codegen patterns etc, can cause code to be breakable today. The BCL has a clear set of guidelines of which things they allow themselves to change even if they can break existing code: adding private fields, etc. Their rule on interfaces is that we cannot add members to an interface. In inherited-hierarchies situations such as the one in Roslyn that constraint is hard to live with. Analyzers --------- Analyzers can easily detect when an interface with the attribute is being implemented outside of its assembly. The problem here is that it is easy to land in a situation where you do not have the analyzer, or it is not turned on. Analyzers are optional by design: they help you use an API, but the API author can't rely on them being enforced. Compiler enforcement -------------------- Moving this check into the compiler would make the "hole" smaller, but, perhaps surprisingly, wouldn't completely deal with it: a pre C# 6 compiler would still happily compile an implementation of an interface with the attribute on. Now, upgrading to C# 6 and the Roslyn-based compiler would be a breaking change! Also, this may play badly with mocking. Though many of the automated mocking frameworks already have a way to work around internal requirements, explicit or manual mocking would still suffer. There are probably some design details around how this works in conjunction with `InternalsVisibleTo`: Assembly A: ``` c# [assembly: InternalVisibleTo("B")] [InternalImplementationOnly] public interface IA {} ``` Assembly B: ``` c# public abstract class B : IA {} public interface IB : IA {} // implicitly inherits restriction? ``` Assembly C: ``` c# public class C : B {} // OK public class D : B, IA {} // not OK public class E : IB {} // not OK ``` Conclusion ---------- We need to talk to the BCL team to decide whether we would consider either of these approaches sufficient to protect the evolvability of interfaces. 2\. Tuples, records, deconstruction =================================== We are eager to look at language support for tuples. They should facilitate multiple return values from methods, including async ones, and a consumption experience that includes deconstruction. We'll have a proposal ready to discuss in the next meeting (It is now here: #347). We are also interested in making it much easier to write something akin to algebraic datatypes (discriminated unions), whose shape and contents are easily pattern-matched on and, probably, deconstructed. While we have a proposal for records that caters to that (#206), there's a sense that this might be more closely connected with the tuple feature than the current proposals suggest, and we should churn on these together to produce a unified, or at least rationalized, design. Are tuples just a specific kind of record? Are records just tuples with a name? Further exploration is needed! The next point is one such exploration. 3\. Classes with values ======================= We explored a possible pattern for immutable classes. It may or may not be the right thing to do, but it generated a lot of ideas for features that would be possible with it, and that it would be interesting to pursue regardless of the pattern. The core idea is: if you want value semantics for your object, maybe your object should literally have a `Value`: ``` c# public class Person { public readonly PersonValue Value; public Person(PersonValue value) { Value = value; } public string Name => Value.Name; public int Age => Value.Age; … public struct PersonValue { public string Name; public int Age; } } ``` The core idea is that there is a *mutable* value type representing the actual state. The object wraps a `readonly` field `Value` of that value type and exposes properties that just delegate to the `Value`. This starts out with the obvious downside that there are two types instead of one. We'll get back to that later. Value semantics --------------- Value semantics mean a) being immutable and b) having value-based equality (and hash code). Value types *already* have value-based equality and hash code, and the `Value` field is `readonly`. So instead of having to negotiate computation of hashcodes, a developer using this pattern can just forward these questions to the `Value`: ``` c# public override bool Equals(object other) => (other as Person)?.Value.Equals(Value) ?? false; // or similar public override int GetHashCode() => Value.GetHashCode; ``` No need to write per-member code. All is taken care of by the `Value`. So for bigger types this definitely leads to less code bloat. You could implement `IEquatable` for better performance, but this illustrates the point. Builder pattern --------------- This approach is essentially a version of the Builder pattern: `PersonValue` acts as a builder for `Person` in that it can be manipulated, and eventually passed to the constructor (which implements the Parameter Object pattern by taking all its parameters as one object). This allows for the use of object initializers in creating new instances: ``` c# var person = new Person(new PersonValue { Name = "John Doe" }); ``` It also lets you create new objects from old once using mutation to incrementally change it: ``` c# var builder = Person.Value; builder.Name = "John Deere"; person = new Person(builder); ``` This again scales to much larger objects, and illustrates one of the points why the builder pattern is popular. Another popular approach is to use "Withers", methods for returning a new instance of an immutable type that's incrementally changed in one way from an old one: ``` c# person.WithName("John Deere"); ``` Withers are more elegant to use, at least when you are only changing one property. But when changing multiple ones, you need to chain them, creating intermediate objects along the way. What's worse, you need to declare a `WithFoo` method for every single property, which is a lot of boilerplate. Maybe we can make the Builder pattern more elegant to use - more like the Wither pattern? Object initializers ------------------- One idea is to allow two new uses of object initializers: * when creating new immutable objects that implement the builder pattern * when non-destructively "modifying" immutable objects by creating new ones with deltas First, let's allow this (similar to what's proposed in #229): ``` c# var person = new Person { Name = "John Doe" }; ``` It would simply rewrite to this: ``` c# var person = new Person(new PersonValue { Name = "John Doe" }); ``` Which is what we had above. Second, let's allow object initializers on *existing* values instead of just new expressions. This is a much requested feature already, because it would apply to factories etc. It would allow us to do a lot better on the incremental modification: ``` c# person = new Person(person.Value { Name = "John Deere" }); ``` If we are uncomfortable just letting object initializers occur directly on any expression, we could consider adding a keyword, like `with`. Now we can get the builder, modify it and create a new Person from it, all in one expression. But of course the kicker is to combine the two ideas, allowing you to write: ``` c# person = person { Name = "John Deere" }; ``` to the same effect: * Since `person` doesn't have a writable member `Name`, get its builder * modify the builder based on the object initializer * create a new `Person` from the builder So in summary, adding the builder patter in *some* compiler-recognized form allows us to tinker with the object initializer feature, making it work as well for immutable objects as it does for mutable ones, and essentially serving as built-in "wither" support. Using tuples as the values -------------------------- Tuples are far from a done deal, but assuming a design like #347, where tuples are mutable structs, they could actually serve as the type of the `Value` field, thus obliterating the need for a second type declaration: ``` c# public class Person { public readonly (string Name, int Age) Value; // a tuple public Person((string Name, int Age) value) { Value = value; } … } ``` This opens up one more opportunity. Assuming you want your immutable type to be deconstructable the same way as a tuple - by position. How do you indicate that? Well maybe having a tuple as your `Value` is how: deconstruction is simply yet another aspect that is delegated to the `Value`: ``` c# (var n, var a) = GetPerson(); // deconstructed as per the `Value` tuple ``` Records ------- If we have a pattern that the compiler supports in various ways for immutable types (whether this pattern or another), it makes sense that that pattern should also underlie any record syntax that we add. Assuming a record syntax like in #206, the record definition: ``` c# class Person(string Name, int Age); ``` Would then generate the class ``` c# class Person { public readonly (string Name, int Age) Value; public Person((string Name, int Age) value) { Value = value; } public string Name => Value.Name; public int Age => Value.Age; public override bool Equals(object other) => (other as Person)?.Value.Equals(Value) ?? false; // or similar public override int GetHashCode() => Value.GetHashCode; } ``` And would therefore support object initializers and positional deconstruction. Conclusion ---------- We are not at all sure if this is the right general direction, let alone the right *specific* pattern if it is. But builders are a common pattern, and for a reason. It would be interesting if support for them was somehow included in what we do for immutable types in C# 7. ================================================ FILE: meetings/2015/LDM-2015-02-11.md ================================================ C# Design Meeting Notes for Feb 11, 2015 ========================================= Discussion on these notes can be found at https://github.com/dotnet/roslyn/issues/1207. Agenda ------ 1. Destructible types <*we recognize the problem but do not think this is quite the right solution for C#*> 2. Tuples <*we like the proposal, but there are several things to iron out*> 1\. Destructible types ====================== Issue #161 is a detailed proposal to add destructible types to C#. The problem area is deterministic disposal of resources. Finalizers are notoriously non-deterministic (you can never know when or even whether they run), and rampant usage is quite a hit on performance. `using` statements are the language's current attempt at providing for deterministic disposal. The problem with `using` statements is that no-one is forced to use them. An `IDisposable` resource is disposed of only if people remember to wrap it in a `using`. FxCop rules and now Roslyn diagnostics can be employed to help spot places where things aren't disposed that should be, but these have notoriously run into false positives and negatives, to the extent that they are a real nuisance and often get turned off. At least one reason for this is the lack of their ability to track ownership. Whose responsibility is it to ultimately dispose the resource, and how is that responsibility transferred? Destructible types offer a sweeping approach to deal with this, that is a complete departure from `using` and `IDisposable` and instead leans more closely on C++-like RAII features. The core idea is that destructible types are a language concept (they have to be declared as such): ``` c# destructible struct Handle { IntPtr ptr; ~Handle() { if (ptr != IntPtr.Zero) Free(ptr); } } ``` Destructible types can only be fields in other destructible types. Variables of destructible type have their contents automatically disposed when they go out of scope. To prevent multiple disposal, such variables can't just be assigned to others. Instead, ownership must be explicitly transferred (with a `move` keyword): ``` c# void M() { Handle h = CreateHandle(); Handle h2 = h; // Not allowed! Moves are explicit Handle h3 = move h; // Explicit owner transfer M2(new Handle()); // ok, no owner M2(h3); // Not allowed! M2(move h3); // ok, explicit owner transfer } void M2(Handle h) ... ``` At the end of M, all locals with destructible types are destructed, in opposite order of their introduction. Additionally variables can be "borrowed" by being passed by `ref` to a method. There are a lot of limitations to the feature, and it doesn't replace `IDisposable`, because you cannot use destructible types as type arguments, in arrays, and as fields of non-destructible types. Also, they cannot implement interfaces. As a release valve one can have a `Box` that "magically" transfers the ownership of a destructible type to the finalizer. The finalizer will crash the process if the destructible hasn't been properly destructed. There has been some negative feedback around the destruction being implicit, even as the ownership transfer was explicit. When assignment overwrites a value, that value has to be destructed. But what about cases like this? ``` c# { D x; if (e) x = new x(); x = new x(); } ``` How to know whether the second assignment overwrites a value, that should therefore be destructed? We'd have to keep track at runtime. Or prevent this situation through definitely assigned or unassigned rules, or the like. Conclusion ---------- Overall, we see how this adds value, but not enough that having these concepts in the face of all C# developers. It doesn't earn back it's -100 points. Could we take a step back and have analyzers encourage practices that are less error prone? Yes. You just cannot have analyzers enforce correctness. And you cannot have them improve perf by affecting codegen. And they cannot do the heavy lifting for you. An analyzer can help you with where to put your `using` statements. FxCop tries to do that today, but has too many false positives (use vs ownership). Even if you could get rid of those, there's a lot of code you'd need to write. People do struggle with `IDisposable` today. They don't know when things are `IDisposable`, whether they are the ones who should dispose things. We would like to solve the problem in C#, and pick over this proposal for ideas, but we'd need to noodle with it to make it a better fit. It would need to integrate better with current `IDisposable`. 2\. Tuples ========== Issue #347 is a proposal for adding tuples to C#. The main guiding principle for the proposal is to enable multiple return values, and wherever possible design them in analogy with parameter lists. As such, a tuple type is a parenthesized list of types and names, just like a parameter list: ``` c# public (int sum, int count) Tally(IEnumerable values) { ... } var t = Tally(myValues); Console.WriteLine($"Sum: {t.sum}, count: {t.count}"); ``` Along the same lines, tuple values can be constructed using a syntax similar to argument lists. By position: ``` c# public (int sum, int count) Tally(IEnumerable values) { var sum = 0; var count = 0; foreach (var value in values) { sum += value; count++; } return (sum, count); // target typed to the return type } ``` Or by name: ``` c# public (int sum, int count) Tally(IEnumerable values) { var res = (sum: 0, count: 0); // infer tuple type from names and values foreach (var value in values) { res.sum += value; res.count++; } return res; } ``` Finally, there'd be syntax to deconstruct tuples into variables for the individual members: ``` c# (var sum, var count) = Tally(myValues); // deconstruct result Console.WriteLine($"Sum: {sum}, count: {count}"); ``` Tuples should be thought of as temporary, ephemeral constellations of values without inherent conceptual connection: they just "happen to be traveling together". We are generally eager to pursue this design. There are a number of open issues, that we explore in the following. Type equivalence ---------------- Intuitively you'd expect that every time you use the same tuple type expression, it denotes the same type - that there is *structural equivalence*. However, depending on how we implement tuple types on top of the CLR, that may not always be easy to achieve - especially when unifying across different assemblies. To take it to an extreme, what if they didn't even unify *within* an assembly? Even then they'd probably have significant value. They would be good for the "ephemeral" core scenario of things traveling together. It wouldn't let them serve as e.g. builders; they would really only work for the multiple return values scenario. Even then there'd be issues with virtual method overrides, etc: When you override and restate the return type, it doesn't really work if that declares a *different* return type. ``` c# class C { public abstract (int x, int y) GetCoordinates(); } class D : C { public override (int x, int y) GetCoordinates() { ... } // Error! different return type!!?! } ``` A middle ground is for these methods to unify *within* but not *across* assemblies. This is what we do for anonymous types today. However, there we are careful never to let the types occur in member signatures, whereas that would be the whole point of tuples. This makes a little more sense than no unification at all, since at least all mentions of a tuple type within the same member body and even type body refer to the same type. However there'd still be weird behavior involving overrides across assembly boundaries, equivalence of generic types constructed with tuple types, etc. We could try to be smart about reusing types from other assemblies when possible, etc, but it would be quite a game of whack-a-mole to get all the edge cases behaving sensibly. The only thing that really makes sense at the language level is true structural equivalence. Unfortunately, the CLR doesn't really provide for unification of types with similar member names across assemblies. Ideally we could *add* that to the CLR, but adding new functionality to the CLR is not something to be done lightly, as it breaks all downlevel targeting. A way to get structural equivalence working at compile time would be to encode tuples with framework types similar to the current `Tuple<...>` types. Member access would be compiled into access of members called `Item1` etc. The language level member names would be encoded in attributes for cross-assembly purposes: ``` c# struct VTuple2 { ... } struct VTuple3 { ... } /* Source: */ (x: int, y: int) f() { ... } /* Generates: */ [TupleNames("x", "y")] VTuple2 f() {...} ``` Unfortunately this leaves problems at runtime: now there's *too much* type equivalence. If I want to do a runtime test to see if an object is an `(int x, int y)`, I would get false positives for an `(int a, int b)`, since the names wouldn't be part of the runtime type: ``` c# object o = (a: 7, b: 9); if (o is (int x, int y) t) WriteLine(t.x); // false positive ``` Also the member names would be lost to `dynamic`: ``` c# dynamic d = (a: 7, b: 9); WriteLine(t.a); // Error! Huh ??!? ``` Of course we could come up with tricks so that a tuple would carry some kind of member name info around at runtime, but not without cost. Or we could decide that the names are ephemeral, compile time only entities, kind of like parameter names, and losing them when boxing is actually a good thing. Also, this approach means that languages that don't know about tuples would see the actual `Item1` member names when referencing member signatures containing tuples. Such languages include previous versions of C#! So for compatibility with those versions of C#, even in the new, tuple-aware, version, access through `Item1` etc would have to be still legal (though probably hidden from IntelliSense etc.). A nice thing about this scheme is that it leads to very little code bloat: it only relies on framework types, and not on compiler generated types in the assembly. This remains an issue for further debate. Conversions ----------- There are several kinds of conversions you can imagine allowing between tuple types: ``` c# (string name, int age) t = ("Johnny", 29); /* Covariance */ (object name, int age) t1 = t; /* Truncation */ (object name) t2 = t; /* Reordering */ (int age, string name) t3 = t; /* Renaming */ (string n, int a) t4 = t; ``` Note that you cannot have both reordering and renaming in the language; we would have to choose. However, we are more inclined not to allow *any* of these conversions. They don't seem particularly helpful, and are likely to mask something you did wrong. People can always deconstruct and reconstruct if they want to get to a different tuple type, so our default position is to be super rigid here and only match exactly the same names and types in exactly the same order. Deconstruction -------------- There are a couple of issues with the proposed deconstruction syntax. First of all, since the recipient variables are on the left, there is no equivalent to parameter help guiding you as to what to name them. In that sense it would be better to have a syntax that receives the values on the right. But that seems an odd place to declare new variables that are intended to be in scope throughout the current block: ``` c# Tally(myValues) ~> (var sum, var count); // strawman right side alternative Console.WriteLine($"Sum: {sum}, count: {count}"); ``` That's not an actual proposed syntax, but just intended to show the point! A related issue is that you'd sometimes want to deconstruct into *existing* variables, not declare new ones: ``` c# (sum, count) = Tally(myValues); ``` Should this be allowed? Is deconstruction then a declaration statement, an assignment statement or something different? Finally, once we have a deconstruction syntax, we'd probably want to enable it for other things than tuples. Maybe types can declare how they are to be deconstructed, in a way that the language understands. So deconstruction syntax should be considered in this broader context. For instance, if deconstructable types can be reference types, can deconstruction throw a null reference exception? LINQ ---- You could imagine tuples becoming quite popular in LINQ queries, to the point of competing with anonymous types: ``` c# from c in customers select (name: c.Name, age: c.Age) into p where p.name == "Kevin" select p; ``` To this end it would be useful for tuple "literals" to support *projection*, i.e. inference of member names, like anonymous types do: ``` c# from c in customers select (c.Name, c.Age) into p // infers (string Name, int Age) where p.Name == "Kevin" select p; ``` Out parameters -------------- F# allows out parameters to be seen as additional return values. We could consider something similar: ``` c# bool TryGet(out int value){ ... } /* current style */ int value; bool b = TryGet(out value); /* New style */ (int value, bool b) = TryGet(); ``` Conclusion ---------- We'd very much like to support tuples! There are a number of open questions, as well as interactions with other potential features. We'll want to keep debating and experimenting for a while before we lock down a design, to make sure we have the best overall story. ================================================ FILE: meetings/2015/LDM-2015-03-04.md ================================================ C# Design Meeting Notes for Mar 4, 2015 ======================================== Discussion on these notes can be found at https://github.com/dotnet/roslyn/issues/1303. Agenda ------ 1. `InternalImplementationOnly` attribute <*no*> 2. Should `var x = nameof(x)` work? <*no*> 3. Record types with serialization, data binding, etc. <*keep thinking*> 4. "If I had a [billion dollars](http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare)...": nullability <*daunting but worth pursuing*> 1\. InternalImplementationOnly ============================== This was a last-minute proposal for C# 6 and VB 14 to recognize and enforce an attribute to the effect of disallowing non-internal implementation of an interface. It would be useful to represent "abstract hierarchies", while reserving the right to add new members at a later stage. Conclusion ---------- Let's not do this. We cannot enforce it well enough since old compilers don't know about it. 2\. Should `var x = nameof(x)` work? ==================================== This was raised as issue #766. ``` c# var POST = nameof(POST); ``` Conclusion ---------- This works the same as with any other construct, i.e.: not. This is not a special case for `nameof`, and it doesn't seem worth special casing to allow it. 3\. Records =========== There's general excitement around proposals such as #206 to add easier syntax for declaring "records", i.e. simple data shapes. This discussion is about the degree to which such a feature should accommodate interaction with databases, serialization and UI data binding. The problem has compounded in recent times. In a mobile app, pretty much everything has to be serialized one way or another. There's a big difference between e.g. JSON and binary serialization. In one you want readable property names (in a certain format), in the other you want compactness. ORMs are also essentially serialization. POCO was invented in that same context, but only gets you part of the way there. Clearly, tying the language feature to specific such technologies is not a sustainable idea. There will not be built-in JSON literals, or `INotifyPropertyChanged` implementation or anything like that. That said, It is worth thinking about what can be done *in general* to support such features better. One problem is that each specific serialization and data binding technology tends to have certain requirements of the shape of the data objects. Serialization frameworks may expect the fields to be mutable, or to have certain attributes on them. UIs may expect properties to notify on change. Such requirements make it hard to have a single language-sanctioned mechanism for specifying the data shapes. A separate problem may be the degree to which reflection is used by these technologies. It is commonly used to get at metadata such as member names, but is also sometimes used to set or get the actual values. It ends up being the case that both serialization and UI use the objects only in a "meta" fashion; the only part of the program that actually makes use of the data in a strongly typed way ends up being the business logic in between. So in effect, data objects need to both facilitate strongly typed business logic based on their property names and shapes (and often in hierarchies), *and* satisfy requirements that are specific to the serialization and UI environments they are used in. Cross-cutting aspects, one might say, that clash in the implementation of the data objects. There are several possible approaches, not necessarily mutually exclusive: **Write the data types manually**: Often what ends up happening today. Just grit your teeth and churn out those INotifyPropertyChanged implementations. **Separate objects**: Often the "best practice" is to have separate objects for serialization, logic, and presentation. But having to write the glue code between these is a big pain, and people often have to do at least some of it manually. **Non-reflective meta-level access model**: E.g. implement dictionary-like behavior. Prevents need for reflection, but is it actually better or faster? **"Fake" types**: Maybe at runtime the data objects shouldn't have strongly typed properties at all. Maybe the business logic is written up against a new kind of types that are only there at compile time, and which either simply erase like in TypeScript, or cause weakly typed but smart access code to be generated like with F# type providers. Then, from the perspective of serialization and UI, all the objects have the same type. **Compile-time metaprogramming**: Generate the types (either into source or IL) from code describing the data shapes, adding whatever serialization or presentation functionality is required. A particularly common requirement of these frameworks is for the data properties to be mutable. That might clash spectacularly with a records feature that tries to encourage immutability by default. Conclusion ---------- We'll keep thinking about this situation. A next step in particular is to reach out to teams that own the serialization and presentation technologies that we'd like to work well with. 4\. Nullability and reference types =================================== The horrifying thing about null being allowed as a member of reference types is that it does not obey the contract of those types. Reference types can be null *and* reference types can be dereferenced, But dereferencing null is a runtime error! No wonder Sir Tony famously calls null pointers his "[billion dollar mistake](http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare)". There are type systems that prevent null reference errors, and it is easy enough to do. It takes different shapes in different languages, but the core of it is that there is a non-nullable kind of reference type that can be dereferenced but cannot be null, and a nullable "wrapper" of that, which *can* be null, but cannot be dereferenced. The way you go from nullable to nonnullable is through a guard to test that the nullable value wasn't in fact null. Something like: ``` c# string? n; nullable string string! s; non-nullable string n = null; // Sure; it's nullable s = null; // Error! WriteLine(s.Length); // Sure; it can't be null so no harm WriteLine(n.Length); // Error! if (n is string! ns) WriteLine(ns.Length); // Sure; you checked and dug out the value ``` Of course, if such a type system were around from day one, you wouldn't need the `!` annotation; `string` would just *mean* non-nullable. It's a very common request (in fact [the top C# request on UserVoice](http://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c)) for us to do something about null reference exceptions along those lines. There are numerous obstacles, however: **Backward compatibility**: The cat is already very much out of the bag in that existing code needs to continue to compile without error - no matter how null-unsafe it is. The best we can do about *existing* code is to offer optional analyzers to point out dangerous places. **A default value for all types**: It is deeply ingrained in .NET and the CLR that every type has a default value. When you create an array with `new T[10]`, the array elements are initialized to the default value of `T`. There's even syntax to get the default value of a type, `default(T)`, and it is allowed even on unconstrained type parameters. But what is the default value of a non-nullable reference type? **Definite assignment of non-nullable fields is unenforceable**. Eric Lippert explains it well [on his blog](http://blog.coverity.com/2013/11/20/c-non-nullable-reference-types/). **Evolving libraries is breaking**: Say we added non-null annotations to the language. You'd then want to use them on your existing libraries to provide further guidance to your clients. Adding them on parameters would of course be breaking: ``` c# public void Foo(string! name) { ... } // '!' newly added Foo(GetString()); // Unless GetString also annotated, this is now broken ``` More subtly, adding the extra guarantee of non-nullability to a *return* type would also be breaking: ``` c# public string! Foo() { ... } // '!' newly added var s = Foo(); // Now infers 'string!' instead of 'string' for s ... s = GetString(); // So this assignment is now broken ``` Type inference and overload resolution generally make strengthening of return types a potentially breaking change, and adding non-nullability is just a special case of that. Conclusion ---------- Moving C# to a place of strong guarantees around nullability seems out of the question. But that is not to say that we cannot come up with an approach that meaningfully reduces the number of null reference exceptions. We want to keep digging here to see what can be done. The perfect is definitely the enemy of the good with this one. ================================================ FILE: meetings/2015/LDM-2015-03-10-17.md ================================================ C# Design Meeting Notes for Mar 10 and 17, 2015 =============================================== Discussion thread for these notes can be found at https://github.com/dotnet/roslyn/issues/1648. Agenda ------ These two meetings looked exclusively at nullable/non-nullable reference types. I've written them up together to add more of the clarity of insight we had when the meetings were over, rather than represent the circuitous path we took to get there. 1. Nullable and non-nullable reference types 2. Opt-in diagnostics 3. Representation 4. Potentially useful rules 5. Safely dereferencing nullable reference types 6. Generating null checks 1\. Nullable and non-nullable reference types ============================================= The core features on the table are nullable and non-nullable reference types, as in `string?` and `string!` respectively. We might do one or both (or neither of course). The value of these annotations would be to allow a developer to express intent, and to get errors or warnings when working against that intent. 2\. Opt-in diagnostics ====================== However, depending on various design and implementation choices, some of these diagnostics would be a breaking change to add. In order to get the full value of the new feature but retain backward compatibility, we therefore probably need to allow the enforcement of some or most of these diagnostics to be *opt-in*. That is certainly an uncomfortable concept, and adding switches to the language changing its meaning is not something we have much of an appetite for. However, there are other ways of making diagnostics opt-in. We now have an infrastructure for custom analyzers (built on the Roslyn infrastructure). In principle, some or all of the diagnostics gained from using the nullability annotations could be custom diagnostics that you'd have to switch on. The downside of opt-in diagnostics is that we can forget any pretense to guarantees around nullability. The feature would help you find more errors, and maybe guide you in VS, but you wouldn't be able to automatically trust a `string!` to not be null. There's an important upside though, in that it would allow you to gradually strengthen your code to nullability checks, one project at a time. 3\. Representation ================== The representation of the annotations in metadata is a key decision point, because it affects the number of diagnostics that can be added to the language itself without it being a breaking change. There are essentially four options: 1. Attributes: We'd have `string?` be represented as `string` plus an attribute saying it's nullable. This is similar to how we represent `dynamic` today, and for generic types etc. we'd use similar tricks to what we do for `dynamic` today. 2. Wrapper structs: There'd be struct types `NullableRef` and `NonNullableRef` or something like that. The structs would have a single field containing the actual reference. 3. Modreq's: These are annotations in metadata that cause an error from compilers that don't know about them. 4. New expressiveness in IL: Something specific to denote these that only a new compiler can even read. We can probably dispense with 3 and 4. We've never used modreq's before, and who knows how existing compilers (of all .NET languages!) will react to them. Besides, they cannot be used on type arguments, so they don't have the right expressiveness. A truly new metadata annotation has similar problems with existing compilers, and also seems like overkill. Options 1 and 2 are interesting because they both have meaning to existing compilers. Say a library written in C# 7 offers this method: ``` c# public class C { string? M(string! s) { ... } } ``` With option 1, this would compile down to something like this: ``` c# public class C { [Nullable] string M([NonNullable] string s) { ... } } ``` A consuming program in C# 6 would not be constrained by those attributes, because the C# 6 compiler does not know about them. So this would be totally fine: ``` c# var l = C.M(null).Length; ``` Unfortunately, if something is fine in C# 6 it has to also be fine in C# 7. So C# 7 cannot have rules to prevent passing null to a nonnullable reference type, or prevent dereferencing a nullable reference type! That's obviously a pretty toothless - and hence useless - version of the nullability feature in and of itself, given that the value was supposed to be in getting diagnostics to prevent null reference exceptions! This is where the opt-in possibility comes in. Essentially, if we use an attribute encoding, we need all the diagnostics that make nullability annotations useful be opt-in, e.g. as custom diagnostics. With option 2, the library would compile down to this: ``` c# public class C { NullableRef M(NonNullableRef s) { ... } } ``` Now the C# 6 program above would not compile. The C# 6 compiler would see structs that can't be null and don't have a Length. Whatever members those structs *do* have, though, would be accessible, so C# 7 would still have to accept using them as structs. (We could mitigate this by not giving the structs any public members). For the most part, this approach would make the C# 6 program able to do so little with the API that C# 7, instead of adding *restrictions*, can allow *more* things than C# 6. There are exceptions, though. For instance, casting any returned such struct to `object` would box it in C# 6, whereas presumably the desired behavior in C# 7 would be to unwrap it. This is exactly where the CLR today has special behavior, boxing nullable value types by first unwrapping to the underlying type if possible. Also, having these single-field structs everywhere is likely going to have an impact on runtime performance, even if the JIT can optimize many of them away. Probably the most damning objection to the wrapper structs is probably the degree to which they would hamper interoperation between the different variations of a type. For instance, the conversion from `string!` to `string` and on to `string?` wouldn't be a reference conversion at runtime. Hence, `IEnumerable` wouldn't convert to `IEnumerable`, despite covariance. We are currently leaning strongly in the direction of an attribute-based representation, which means that there needs to be an opt-in mechanism for enforcement of the useful rules to kick in. 4\. Potentially useful rules to enforce ======================================= **Don't dereference `C?`**: you must check for null or assert that the value is not null. **Don't pass `null`, `C` or `C?` to `C!`:** you must check for null or assert that the value is not null. **Don't leave `C!` fields unassigned:** require definite assignment at the end of the constructor. (Doesn't prevent observing null during initialization) **Avoid `default(C!)`:** it would be null! **Don't instantiate `C![]`:** it's elements would be null. This seems like a draconian restriction - as long as you only ever read fields from the array that were previously written, no-one would observe the default value. Many data structures wrapping arrays observe this discipline. **Don't instantiate `G`:** this is because the above rules aren't currently enforced on even unconstrained type parameters, so they could be circumvented in generic types and methods. Again, this restriction seems draconian. No existing generic types could be used on nonnullable reference types. Maybe the generic types could opt in? **Don't null-check `C!`:** oftentimes using e.g. `?.` on something that's already non-nullable is redundant. However, since non-nullable reference types *can* be null, maybe flagging such checks is not always so helpful? We very much understand that these rules can't be perfect. The trade-off needs to be between adding value and allowing continuity with existing code. 5\. Safely dereferencing nullable reference types ================================================= For nullable reference types, the main useful error would come from dereferencing the value without checking for null. That would often be in the shape of the null-conditional operator: ``` c# string? s = ...; var l = s?.Length; var c = s?[3]; ``` However, just as often you'd want the null test to guard a block of code, wherein dereferencing is safe. An obvious candidate is to use pattern matching: ``` c# string? ns = ...; if (ns is string! s) // introduces non-null variable s { var l = s.Length; var c = s[3]; } ``` It is somewhat annoying to have to introduce a new variable name. However, in real code the expression being tested (`ns` in the above example) is more likely to be a more complex expression, not just a local variable. Or rather, the `is` expression is how you'd get a local variable for it in the first place. More annoying is having to state the type again in `ns is string! s`. We should think of some shorthand, like `ns is ! s` or `ns is var s` or something else. Whatever syntax we come up with here would be equally useful to nullable *value* types. 6\. Generating null checks for parameters ========================================= There'd be no guarantees that a `string!` parameter actually isn't going to be null. Most public API's would probably still want to check arguments for null at runtime. Should we help with that by automatically generating null checks for `C!` parameters? Every generated null check is performance overhead and IL bloat. So this may be a bad idea to do on every parameter with a non-nullable reference type. But we could have the user more compactly indicate the desire to do so. As a complete strawman syntax: ``` c# public void M(string!! s) { ... } ``` Where the double `!!` means the type is non-nullable *and* a runtime check should be generated. If we choose to also do contracts (#119), it would be natural for this feature to simply be a shorthand for a null-checking `requires` contract. ================================================ FILE: meetings/2015/LDM-2015-03-18.md ================================================ C# Design Meeting Notes for Mar 18, 2015 ======================================== Discussion thread for these notes can be found at https://github.com/dotnet/roslyn/issues/1677 Agenda ------ In this meeting we looked over the top [C# language feature requests on UserVoice](http://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c) to see which ones are reasonable to push on further in C# 7. 1. Non-nullable reference types (*already working on them*) 2. Non-nullary constructor constraints (*require CLR support*) 3. Support for INotifyPropertyChanged (*too specific; metaprogramming?*) 4. GPU and DirectX support (*mostly library work; numeric constraints?*) 5. Extension properties and static members (*certainly interesting*) 6. More code analysis (*this is what Roslyn analyzers are for*) 7. Extension methods in instance members (*fair request, small*) 8. XML comments (*Not a language request*) 9. Unmanaged constraint (*requires CLR support*) 10. Compilable strings (*this is what nameof is for*) 11. Multiple returns (*working on it, via tuples*) 12. ISupportInitialize (*too specific; hooks on object initializers?*) 13. ToNullable (*potentially part of nullability support*) 14. Statement lambdas in expression trees (*fair request, big feature!*) 15. Language support for Lists, Dictionaries and Tuples (*Fair; already working on tuples*) A number of these are already on the table. 1\. Non-nullable reference types ================================ [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2320188-add-non-nullable-reference-types-in-c](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2320188-add-non-nullable-reference-types-in-c) We're already working on this; see e.g. #1648. 2\. Non-nullary constructor constraints ======================================= [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2122427-expand-generic-constraints-for-constructors](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2122427-expand-generic-constraints-for-constructors) It is odd that we only support the `new()` constraint for empty parameter lists. In order to generalize this, however, we'd need CLR support to express it - see #420. 3\. Support for INotifyPropertyChanged ====================================== [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2255378-inotifypropertychanged](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2255378-inotifypropertychanged) This is too specific an interface to bake in special knowledge for in the language. However, we recognize the pain of having to repeat the boilerplate around this, even as we've improved the situation here a bit with C# 6. We think that this may be better addressed with metaprogramming. While we don't have a clear story for how to support this better, in the language or compiler tool chain, we think that Roslyn in and of itself helps here. We'll keep watching the space and the specific scenario. 4\. GPU and DirectX support =========================== [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2730068-greatly-increase-support-for-gpu-programming-in-c](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2730068-greatly-increase-support-for-gpu-programming-in-c) [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3646222-enable-hlsl-directx-and-graphics-development-tool](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3646222-enable-hlsl-directx-and-graphics-development-tool) These are mostly library-level requests, independent of the language. One feature that could potentially improve such libraries would be the ability to specify generic constraints that somehow express the presence of numeric operators. Being able to write generic methods, say, over anything that has a `+` operator, allowing `+` to be used directly in that method body, would certainly improve the experience of writing such code, and would prevent a lot of repetition. Unfortunately, like other new constraints, such a numeric constraint facility would require new support from the CLR. 5\. Generalized extension members ================================= [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2242236-allow-extension-properties](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2242236-allow-extension-properties) [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2060313-c-support-static-extension-methods-like-f](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2060313-c-support-static-extension-methods-like-f) The requests for extension properties and for static extension methods are essentially special cases of a general desire to be able to more generally specify extension member versions of all kinds of function members: properties, indexers, constructors, static members - why not? This is a reasonable request. The main problem we have is that the current scheme for extension methods doesn't easily generalize to other kinds of members. We'd need to make some very clever syntactic tricks to do this without it feeling like a complete replacement of the current syntax. This is certainly one that we will look at further for C# 7. 6\. More code analysis ====================== [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4428274-improve-code-analysis](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4428274-improve-code-analysis) This feels like it is best addressed via Roslyn-based analyzers. 7\. Extension methods in non-static classes =========================================== [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3359397-allow-extension-methods-to-be-defined-in-instance](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3359397-allow-extension-methods-to-be-defined-in-instance) We were very cautious when we first introduced extension methods, and surrounded them with a lot of restrictions. This is a well argued scenario where allowing them inside instantiable classes would enable a fluent style for private helper methods. This seems fair enough, and we could loosen this restriction, though it's probably a relatively low priority work item. 8\. XML comments ================ [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2709987-xml-comments-schema-customization-in-c](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2709987-xml-comments-schema-customization-in-c) This is not a language suggestion. 9\. Unmanaged constraint ======================== [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4716089-unmanaged-generic-type-constraint-generic-pointe](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4716089-unmanaged-generic-type-constraint-generic-pointe) This would be great in order to enable pointers over type parameters. However, it requires CLR support. 10\. Compilable strings ======================= [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/5592955-compliable-strings](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/5592955-compliable-strings) This is mostly addressed by `nameof` in C# 6; it's unlikely there is basis for more language level functionality here. 11\. Multiple returns ===================== [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2083753-return-multiple-values-from-functions-effortlessly](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2083753-return-multiple-values-from-functions-effortlessly) We're already looking at addressing this through tuples. 12\. ISupportInitialize ======================= [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2094881-add-support-for-isupportinitialize-on-object-initi](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2094881-add-support-for-isupportinitialize-on-object-initi) This suggestion addresses the need to perform validation upon initialization. While depending on the ISupportInitialize interface is probably too specific, it is interesting to ponder if there is a way to e.g. hook in after an object initializer has run. 13\. ToNullable =============== [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2531917-structure-all-nullable-values](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2531917-structure-all-nullable-values) This suggestion would add a new operator to make type parameters nullable only if they are not already so. You could certainly imagine something like this in conjunction with at least some variations of the nullability proposals we have been discussing lately. 14\. Statement lambdas in expression trees ========================================== [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4255391-let-lambdas-with-a-statement-body-be-converted-to](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4255391-let-lambdas-with-a-statement-body-be-converted-to) The language today doesn't allow statement lambdas to be converted to expression trees, despite there being expression tree classes for most features. Moreover, in fact, it disallows several expression forms, including `await` expressions. This would be a lovely gap to fill, especially since it would come with no conceptual overhead - more code would just work as expected. However, it is also an enormous feature to take on. We are not sure we have the weight of scenarios necessary to justify taking on the full magnitude of this. It is also possible that we'd address a subset. 15\. Language support for Lists, Dictionaries and Tuples ======================================================== [http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2405699-build-list-dictionary-and-tuple-into-the-language](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2405699-build-list-dictionary-and-tuple-into-the-language) We are already looking at tuples, and we do have it on our radar to consider language syntax for lists and dictionaries in some form. Many shapes of this feature would require a strong commitment to specific BCL types. However, you could also imagine having anonymous expression forms that target type to types that follow a given pattern. ================================================ FILE: meetings/2015/LDM-2015-03-24.md ================================================ C# Design Meeting Notes for Mar 24, 2015 ======================================== Discussion thread on these notes is at https://github.com/dotnet/roslyn/issues/1898. *Quote of the Day:* If we have slicing we also need dicing! Agenda ------ In this meeting we went through a number of the performance and reliability features we have discussed, to get a better reading on which ones have legs. They end up falling roughly into three categories: * Green: interesting - let's keep looking * Yellow: there's something there but this is not it * Red: probably not As follows: 1. ref returns and locals <*green*> (#118) 2. readonly locals and parameters <*green*> (#115) 3. Method contracts <*green*> (#119) 4. Does not return <*green*> (#1226) 5. Slicing <*green*> (#120) 6. Lambda capture lists <*yellow - maybe attributes on lambdas*> (#117) 7. Immutable types <*yellow in current form, but warrants more discussion*> (#159) 8. Destructible types <*yellow - fixing deterministic disposal is interesting*> (#161) 9. Move <*red*> (#160) 10. Exception contracts <*red*> 11. Static delegates <*red*> 12. Safe fixed-size buffers in structs <*red*> (#126) Some of these were discussed again (see below), some we just reiterated our position. 1\. Ref returns and locals ========================== At the implementation level these would require a verifier relaxation, which would cause problems when down targeting in sandboxing scenarios. This may be fine. At the language level, ref returns would have to be allowed on properties and indexers only if they do not have a setter. Setters and ref would be two alternative ways of allowing assignment through properties and indexers. For databinding scenarios we would need to check whether reflection would facilitate such assignment through a ref. A danger of ref returns in public APIs: say you return a ref into the underlying array of e.g. a list, and the list is grown by switching out the underlying array. Now someone can get a ref, modify the collection, follow the ref and get to the wrong place. So maybe ref returns are not a good thing on public API boundary. There's complexity around "safe to return": You should only return refs that you received as parameters, or got from the heap. This leads to complexity around allowing reassignment of ref locals: how do you track whether the ref they are pointing to is "safe to return" or not? We'd have to either * add syntax to declare which kind the local points to (complex) * allow only one of the kinds to be assigned to locals (restrictive) * track it as best we can through flow analysis (magic) There's complexity in how refs relate to readonly. You either can't take a ref to a readonly, or you need to be able to express a readonly ref through which assignments is illegal. The latter would need explicit representation in metadata, and ideally the verifier would enforce the difference. This can't very well be a C# only feature, at least if it shows up in public APIs. VB and F# would need to at least know about it. This feature would be a decent performance win for structs, but there aren't a lot of structs in the .NET ecosystem today. This is a chicken-and-egg thing: because structs need to be copied, they are often too expensive to use. So this feature could lower the cost of using structs, making them more attractive for their other benefits. Even so, we are still a bit concerned that the scenario is somewhat narrow for the complexity the feature adds. The proof will have to be in the use cases, and we're not entirely convinced about those. It would be wonderful to hear more from the community. This is also a great candidate for a prototype implementation, to allow folks to experiment with usability and performance. 2\. Readonly locals and parameters ================================== At the core this is a nice and useful feature. The only beef we have with it is that you sort of want to use `readonly` to keep your code safe, and you sort of don't because you're cluttering your code. The `readonly` keyword simply feels a bit too long, and it would be nice to have abbreviations at least in some places. For instance `readonly var` could be abbreviated to `val` or `let`. Probably `val` reads better than `let` in many places, e.g. declaration expressions. We could also allow `val` as an abbreviation for `readonly` even in non-var situations. In Swift they use `let` but it reads strange in some contexts. In Swift it's optional in parameter positions, which helps, but we couldn't have that for back compat reasons. This is promising and we want to keep looking at it. 4\. Does Not Return =================== It would be useful to be able to indicate that a method will never return successfully. It can throw or loop. The proposal is to do it as an attribute, but there would be more value if it was part of the type system. Essentially it replaces the return type, since nothing of that type is ever returned. We could call it `never`. The `never` type converts to pretty much anything. This would allow us to add throw *expressions* in the language - their type would be `never`. Having it in the type system allows e.g. returning `Task`, so that you can indicate an async method that will only ever produce an exception, if anything. Because of the Task example you do want to allow `never` in generics, but that means you could have generic types that unwittingly operate on never values, which is deeply strange. This needs to be thought about more. If through nasty tricks you get to a point in the code that according to never types should not be reachable, the code should probably throw. A common usage would be helper methods to throw exceptions. But throw as an expression is the most useful thing out of this. 6\. Attributes on lambdas ========================= Why? Guiding an analyzer, e.g. to prevent variable capture. Syntactically it might collide with XML literals in VB. We could probably hack it in. The attribute would be emitted onto the generated method. ================================================ FILE: meetings/2015/LDM-2015-03-25-Design-Review.md ================================================ C# Language Design Review, Mar 25, 2015 ======================================= Discussion thread for these notes can be found at https://github.com/dotnet/roslyn/issues/1921. We've recently changed gears a little on the C# design team. In order to keep a high design velocity, part of the design team meets one or two times each week to do detailed design work. Roughly monthly the full design team gets together to review and discuss the direction. This was the first such review. Agenda ------ 1. Overall direction 2. Nullability features 3. Performance and reliability features 4. Tuples 5. Records 6. Pattern matching 1\. Overall direction ===================== In these first two months of design on C# 7 we've adopted a mix of deep dive and breadth scouring. There's agreement that we should be ambitious and try to solve hard problems, being willing to throw the result away if it's not up to snuff. We should keep an open mind for a while still, and not lock down too soon on a specific feature set or specific designs. 2\. Nullability features ======================== Non-nullable types are the number one request on UserVoice. We take it that the *underlying problem* is trying to avoid null reference exceptions. Non-nullable types are at best only part of the solution to this. We'd also need to help prevent access when something is *nullable*. We've looked at this over a couple of design meetings (#1303, #1648). Ideally we could introduce non-null types, such as `string!` that are guaranteed never to be null. However, the problems around initialization of fields and arrays, etc., simply run too deep. We can never get to full guarantees. We've been mostly looking at implementation approaches that use type erasure, and that seems like a promising approach. However, the thing we need to focus on more is this: when you get a nullability warning from this new feature, how do you satisfy the compiler? If you need to use unfamiliar new language features or significant extra syntax to do so, it probably detracts from the feature. Instead we should at least consider an flow-based approach, where the "null-state" of a variable is tracked based on tests, assignments etc. ``` c# if (x == null) return; // not null here ``` It's an open question how far we would go. Would we track only locals and parameters, or would we also keep track of fields? ``` c# if (foo.x == null) ... ``` This is more problematic, not just because of the risk of other threads changing the field, but also because other code may have side effects on the field or property. TypeScript uses information about type guards to track union types in if branches, but it's not full flow analysis, and works only for local variables. Google Closure is more heuristics based, and is happy to track e.g. `foo.bar.baz` style patterns. A core nuisance with nullability checking is that it raises a wealth of compat questions that limit the design in different ways. There may need to be some sort of opt-in to at least some of the diagnostics you'd get, since you wouldn't want them if you were just recompiling old code that used to "work". 3\. Performance and reliability =============================== The list produced at a recent design meeting (#1898) looks sensible. val / readonly -------------- We should cross check with Scala on their syntax. ref return / locals ------------------- Lots of compexitity - we question whether it is worth the cost? Never type ---------- The type system approach is interesting, and allows throw expressions. Method contracts ---------------- `requires` / `ensures`, show up in docs, etc. This looks great. The biggest question is what happens on failure: exceptions? fail fast? Slices ------ We got strong feedback that array slices are only interesting if we unify them with arrays. Otherwise there's yet another bifurcation of the world. There's *some* value to have a `Slice` struct type just show up in the Framework. But it really doesn't seem worth it unless it's a runtime feature. That unification is really hard to achieve, and would require CLR support. It's valuable enough to try to pursue even with high likelihood of failure. Slicing as a language syntax could also be "overloadable" - on IEnumerables for instance. In Go, if you treat a slice as an object, it gets boxed. Lambda capture lists -------------------- Not interesting as a feature, but the idea of allowing attributes on lambdas might fly. Immutable types --------------- General concern that this doesn't go far enough, is lying to folks, etc. It tries to have strong guarantees that we can't make. But a lot of people would appreciate *something* here, so the scenario of immutability should continue to motivate us. Destructible types ------------------ The scenario is good, not the current proposal. 4\. Tuple types =============== There's agreement on wanting the feature and on the syntax (#347, #1207). We probably prefer a value type version of Tuple\. Of course those would be subject to tearing, like all structs. We're willing to be swayed. There are performance trade offs around allocation vs copying, and also around generic instantiation. We could do some experiments in F# source code, which already has tuples. 5\. Records =========== See #180, #206, #396, #1303, #1572. In the current proposal, we should just give up on the ability to name constructor parameters and members differently. The motivation was to be able to upgrade where parameter names start with lower case and member names upper case, but it's not worth the complexity. Should it have `==` and `!=` that are value based? Clashes a little with the ability to make them mutable. If I introduce extra state, then I have to write my own GetHashCode. That seems unfortunate. All the gunk today is part of why Roslyn uses XML to generate its data structure. A test of success would be for the Roslyn syntax trees to be concise to write in source code. A big issue here is incremental non-destructive modification. Roslyn follows the pattern of "Withers", a method for each property that takes a new value for that property and returns a new object that's a copy of the old one except for that property. Withers are painfully verbose to declare, and ideally this feature would offer a solution to that. Serialization has to work *somehow*, even though many of the members will be generated. We should not be *too* concerned about the ability to grow up to represent all kinds of things. Start from it being the POD feature, and work from there. 6\. Pattern matching ==================== See #180, #206, #1572. Whether introduced variables are mutable or not is not a key question: we can go with language uniformity or scenario expectation. Integral value matching is an opportunity to generalize. The pattern 3 may match the value 3 of all integral types rather than just the int 3. Named matching against all objects and positional against ones that define a positional match. Are recursive patterns necessary? No, but probably convenient and there's no reason not to have them. Pattern matching could be shoe-horned into current `switch` statements as well as `is` expressions. And we could have a switching *expression* syntax as well: ``` c# var x = match(e) { p => e, p => e, * => e } ``` An expression version would need to be checked by the compiler for completeness. A little clunky, but much more concise than using a switch. Similar to a current pattern in the Roslyn code base: ``` c# Match(Func f1, Func f2) { ... } var x = Match((string s) => e, (int i) => e); ``` Maybe the fat arrow is not right. We need to decide on syntax. Another option is to use the case keyword instead. ================================================ FILE: meetings/2015/LDM-2015-03-25-Notes.md ================================================ C# Design Meeting 2015-03-25 ============================ Discussion thread for these notes can be found at https://github.com/dotnet/roslyn/issues/1572. These are notes that were part of a presentation on 2015-03-25 of a snapshot of our design discussions for C# 7 and VB 15. The features under discussion are described in detail in #206 and elsewhere. [Records](https://github.com/dotnet/roslyn/issues/206) ======= Changes since Semih Okur's work: - Remove `record` modifier - `with` expressions (#5172) - Working on serialization Working proposal resembles Scala case classes with active patterns. ------ ```cs class Point(int X, int Y); ``` - defines a class (struct) and common members - constructor - `readonly` properties - GetHashCode, Equals, ToString - operator== and operator!= (?) - Pattern-matching decomposition operator - an association between ctor parameters and properties - Any of these can be replaced by hand-written code - Ctor-parameter and property can have distinct names ------ ### Use Cases - Simplifies common scenarios - Simple immutable user-defined data types - Roslyn Syntax Trees, bound nodes - Over-the-wire data - Multiple return values ------ ### With expressions Illustrates an example of the value of having parameter-property association. Given ```cs struct Point(int X, int Y); Point p = ... ``` the expression ```cs p with { Y = 4 } ``` is translated to ```cs new Point(p.X, 4) ``` ------ ### Open issues - Closed hierarchy (and tag fields) - Readonly - Parameter names - Can you opt out of part of the machinery? - Serialization (in all of its forms) - Construction and decomposition syntax ------ [Pattern Matching](https://github.com/dotnet/roslyn/issues/206) ================ ### Sources of Inspiration - Scala - F# - Swift - Rust - Erlang - Nemerle ------ ### A pattern-matching operation - Matches a *value* with a *pattern* - Either *succeeds* or *fails* - Extracts selected values into *variables* ```cs object x = ...; if (x is 3) ... if (x is string s) ... if (x is Point { X is 3, Y is int y }) ... if (x is Point(3, int y)) ... ``` ------ ### Other aspects - Patterns defined recursively - "select" from among a set of pattern forms - Typically an expression form - *active patterns* support interop and user-defined types ```cs switch (o) { case 3: ... break; case string s: M(s); break; case Point(3, int y): M(y); break; case Point(int x, 4): M(x); break; } ``` We think we want an expression form too (no proposed syntax yet, but for inspiration): ```cs M(match(e) { 3 => x, string s => s.foo, Point(3, int y) => y, * => null }) ``` ------ ### Benefits - Condenses long sequences of complex logic with a test that resembles the shape of the thing tested ### Use Cases - Simplifies common scenarios - Language manipulation code - Roslyn - Analyzers - Protocol across a wire ------ ### Open questions - How much of the pattern-matching experience do we want (if any)? - Matching may be compiler-checked for completeness - May be implemented using tags instead of type tests - Which types have syntactic support? - Primitives and string - Records - Nullable\ - objects with properties - anonymous types? - arrays? - List? Dictionary? - Tuple<...>? - IEnumerable\? ================================================ FILE: meetings/2015/LDM-2015-04-01-08.md ================================================ C# Design Meeting Notes for Apr 1 and Apr 8, 2015 ================================================= Discussion thread for these notes is at https://github.com/dotnet/roslyn/issues/2119. Agenda ------ Matt Warren wrote a Roslyn analyzer as a low cost way to experiment with nullability semantics. In these two meetings we looked at evolving versions of this analyzer, and what they imply for language design. The analyzer is here: https://github.com/mattwar/nullaby. Flow-based nullability checking =============================== At the design review on Mar 25 (#1921), there was strong support for adding nullability support to the language, but also the advice to make it easy to transition into using nullability checking by recognizing current patterns for null checking. This suggests a flow-based approach, where the "null state" of variables is tracked by the compiler, and may be different in different blocks of code. Comparisons of the variable, and assignments to the variable, would all change its null state within the scope of effect of those operations. An inherent danger with flow-based checks like that is that a variable may change in an untracked way. The risk of that for parameters and local variables is limited: the variable would have to be captured and modified by a lambda, and that lambda would have to be executed elsewhere *during* the running of the current function. Given that any null-checking machinery we build would have to be somewhat approximate anyway, we can probably live with this risk. It gets gradually worse if we try to track fields of `this` or other objects, or even properties or array elements. In all likelihood, tracking just parameters and local variables would deliver the bulk of the value, but at least "dotted chains" would certainly also be useful. Attributes versus syntax ======================== The analyzer makes use of attributes to denote when a variable is "nullable" (`[CouldBeNull]`) or "non-nullable" (`[ShouldNotBeNull]`). Compared to a built-in syntax, this has several disadvantages: * It's less syntactic convenient of course * The attribute cannot be applied to local variables * The attribute cannot be applied to a type argument or an array element type These limitations are inherent to the nature of the experiment, but we know how to counter them if we add language syntax, even if that syntax is encoded with the use of attributes. (We learned all the tricks when we introduced `dynamic`.) Analyzers versus built-in rules =============================== Providing nullability diagnostics by means of an analyzer comes with a number of pros and cons compared to having those diagnostics built in to the language. * Language rules need to be clearly specified and reasonable to explain. An analyzer can employ more heuristics. * Language-based diagnostics need to consider back compat for the language, whereas analyzers can introduce warnings on currently valid code. * For the same reason, analyzers can evolve over time * With an analyzer, individual rules can be turned on and off. Some may want a harsher medicine than others. With the language it has to be one size fits all. On the other hand: * Rules can probably be implemented more efficiently in the compiler itself (though we might be able to come up with tricks to deal with that for analyzers too) * The language has an opportunity to standardize what exactly is allowed * The rules would still apply in contexts where analyzers aren't run * It would be odd to add `!` and `?` syntax to the language, without adding the accompanying semantics * We could avoid many issues with back compat if we adopt the notion of "warning waves" (#1580), where later versions of the language can add new warnings. The analyzer has several shortcomings due to the lack of a public flow analysis API in Roslyn. That would be a great addition, regardless of what we do for nullability checking. With a cruder/simpler analysis built-in to the language, you can imagine folks building enhancement analyzers on top of it. Those may not just add new diagnostics, but might want to *turn off* compiler warnings where more heuristics can determine that they are in fact not warranted. The analyzer infrastructure doesn't currently support this. Taking the analyzer for a spin ============================== Given the following declaration ``` c# void Foo([ShouldNotBeNull] string s) { } ``` The following statements would yield warnings because `s` is declared to be nullable, but is used in a way that requires it not to be: ``` c# void Bad([CouldBeNull] string s) { Foo(s); // Warning! var l = s.Length; // Warning! } ``` However, all the following methods are ok, because the flow analysis can determine that `s` is not null at the point where it is used: ``` c# void Ok1([CouldBeNull] string s) { s = "Not null"; Foo(s); // Ok } void Ok2([CouldBeNull] string s) { if (s != null) { Foo(s); // Ok } } void Ok3([CouldBeNull] string s) { if (s == null) { throw new ArgumentNullException(); } Foo(s); // Ok } void Ok4([CouldBeNull] string s) { if (s == null) { s = "NotNull"; } Foo(s); // Ok } void Ok5([CouldBeNull] string s) { if (s != null && s.Length > 0) // Ok { } } void Ok6([CouldBeNull] string s) { if (s == null || s.Length > 0) // Ok { } } ``` This seems hugely useful, because current code will just continue to work in the vast majority of cases. It is a change of thinking from where nullability is strongly part of the *type* of a variable, and is established at declaration time. In that paradigm, establishing that a given variable is not null doesn't help you; you have to capture its value in a new, more strongly typed variable, which is more cumbersome, and which would require existing code to be extensively rewritten to match new patterns. Conclusion ========== Matt's experiment is great, and we are very interested in the nullability tracking approach, because it has the potential to make the bulk of existing code work in the face of new annotations. ================================================ FILE: meetings/2015/LDM-2015-04-14.md ================================================ C# Design Meeting Notes for Apr 14, 2015 ======================================== Discussion thread for these notes can be found at https://github.com/dotnet/roslyn/issues/2134. Bart De Smet visited from the Bing team to discuss their use of Expression Trees, and in particular the consequences of their current shortcomings. The Expression Tree API today is not able to represent all language features, and the language supports lambda conversions even for a smaller subset than that. Background ========== Certain Bing services, such as parts of Cortana, rely on shipping queries between different machines, both servers in the cloud and client devices. In a typical scenario a lambda expression tree is produced in one place, ideally via the conversion from lambda expressions to expression trees that exists in C#. The expression tree is then serialized using a custom serialization format, and sent over the wire. At the receiving end it will often be stitched into a larger expression tree, which is then compiled with the `Compile` method and executed. Along the way, several transformations are often made on the trees, for efficiency reasons etc. For instance, rather than invoke a lambda its body can often be inlined in the enclosing tree that the lambda gets stitched into. The serialization format is able to carry very specific type information along with the code, but can also represent the code loosely. A looser coupling makes for code that is more resilient to "schema" differences between the nodes, and also allows for use of types and functions that aren't present where the lambda is concocted. However, it also makes it harder to stitch things back up right on the other side. Shortcomings in the Expression Tree API ======================================= The expression tree API was introduced with Linq and C# 3.0, and was extended to support the implementation infrastructure of `dynamic` along with C# 4.0. However, it has not been kept up to date with newer language features. This is a list of ones that are confounding to the Bing team. Dynamic ------- Even though the API was extended for the benefit of the dynamic feature, the feature itself ironically is not well represented in the API. This is not a trivial undertaking: in Expression Trees today, all invoked members are represented using reflection structures such as `MemberInfo`s etc. Dynamic invocations would need a completely different representation, since they are by definition not bound by the time the expression tree is produced. In the Bing scenario, dynamic would probably help a lot with representing the loosely coupled invocations that the serialization format is able to represent. Alternatively, if the C# language added something along the "lightweight dynamic" lines that were discussed (but ultimately abandoned) for C# 6, the representation of that in Expression Trees would probably yield similar benefit with a fraction of the infrastructure. Await ----- Representing await would probably be easy in the Expression Tree API. However, the implementation of it in `Expression.Compile` would be just as complex as it is in the C# and VB compilers today. So again, this is a significant investment, though one with much less public surface area complexity than `dynamic`. Needless to say, distributed scenarios such as that of Bing services have a lot of use for `await`, and it can be pretty painful to get along without it. Null conditional operators and string interpolation --------------------------------------------------- These should also be added as Expression Tree nodes, but could probably be represented as reducible nodes. Reducible nodes are a mechanism for describing the semantics of a node in terms of reduction to another expression tree, so that the `Compile` method or other consumers don't have to know about it directly. Higher level statements ----------------------- While expression trees have support for statements, those tend to be pretty low level. Though there is a `loop` node there are no `for` or `foreach` nodes. If added, those again could be reducible nodes. Shortcomings in the languages ============================= C# and VB are even more limited in which lambdas can be converted to expression trees. While statements are part of the Expression Tree API, the languages will not convert them. Also, assignment operators will not be converted. This is a remnant of the first wave of Linq, which focused on allowing lambdas for simple, declarative queries, that could be translated to SQL. There has traditionally been an argument against adding more support to the languages based on the pressure this would put on existing Linq providers to support the new nodes that would start coming from Linq queries in C# and VB. This may or may not ever have been a very good argument. However, with Roslyn analyzers now in the world, it pretty much evaporates. Any Linq provider that wants to limit at design time the kinds of lambdas allowed, can write a diagnostic analyzer to check this, and ship it with their library. None of this support would require new syntax in the language, obviously. It is merely a matter of giving fewer errors when lambdas are converted to expression trees, and of mapping the additional features to the corresponding nodes in what is probably a straightforward fashion. Conclusion ========== There is no remaining design reason we can think of why we shouldn't a) bring the Expression Tree API up to date with the current state of the languages, and b) extend the language support accordingly. The main issue here is simply that it is likely to be a huge undertaking. We are not sure that the sum of the scenarios will warrant it, when you think about the opportunity cost for other language evolution. Bing is a very important user, but also quite probably an atypical one. So in summary, as a language design team, we certainly support completing the picture here. But with respect to priorities, we'd need to see a broader call for these improvements before putting them ahead of other things. ================================================ FILE: meetings/2015/LDM-2015-04-15.md ================================================ C# Design Meeting Notes for Apr 15, 2015 ======================================== Discussion thread for these notes is at https://github.com/dotnet/roslyn/issues/2133. Agenda ------ In this meeting we looked at nullability and generics. So far we have more challenges than solutions, and while we visited some of them, we don't have an overall approach worked out yet. 1. Unconstrained generics 2. Overriding annotations 3. FirstOrDefault 4. TryGet Unconstrained generics ====================== Fitting generics and nullability together is a delicate business. Let's look at unconstrained type parameters as they are today. One the one hand they allow access to members that are on all objects - `ToString()` and so on - which is bad for nullable type arguments. On the other hand they allow `default(T)` which is bad for non-nullable type arguments. Nevertheless it seems draconian to not allow instantiations of unconstrained type parameters with, say, `string?` and `string!`. We suspect that most generic types and methods probably behave pretty nicely in practice. For instance, `List`, `Dictionary` etc. would certainly have internal data structures - arrays - that would have the default value in several places. However, their logic would be written so that no array element that hadn't explicitly been assigned a value from the user would ever expose its default value later. So if we look at a `List` we wouldn't actually ever see nulls come out as a result of the internal array having leftover nulls from its initial allocation. No nulls would come in, and therefore no nulls would come out. We want to allow this. Overriding annotations ====================== When type parameters are known to be reference types it may make sense to allow overriding of the nullability of the type parameter: `T?` would mean `sting?` regardless of whether the type argument was `string!`, `string` or `string?`. This would probably help in some scenarios, but with most generics the type parameter isn't actually known to be a reference type. FirstOrDefault ============== This pattern explicitly returns a default value if the operation fails to find an actual value to return. Obviously that is unfortunate if the element type is non-nullable - you'd still get a null out! In practice, such methods tend to be so glaringly named (precisely because their behavior is a little funky) that most callers would probably already be wary of the danger. However, we might be able to do a little better. What if there was some annotation you could put on `T` to get the nullable version *if* `T` should happen to be a reference type. Maybe the situation is rare enough for this to just be an attribute, say `[NullableIfReference]`: ``` c# public [return:NullableIfReference] T FirstOrDefault(this IEnumerable src) { if ... else return default(T); } ``` Applied to `List` (or `List`) this would return a `string?`. But applied to `List` it would return an `int` not an `int?`. This seems perfectly doable, but may not be worth the added complexity. TryGet ====== This pattern has a bool return value signaling whether a value was there, and an out parameter for the result, which is explicitly supposed to be default(T) when there was no value to get: ``` c# public bool TryGet(out T result) { ... } ``` Of course no-one is expected to ever look at the out parameter if the method returns false, but even so it might be nice to do something about it. This is not a situation where we want to apply the `[NullableIfReference]` attribute from above. The consumer wants to be able to access the result without checking for null if they have already checked the returned bool! We could imagine another attribute, `[NullableIfFalse]` that would tell the compiler at the consuming site to track nullability based on what was returned from the method, just as if there had been a null check directly in the code. Again, this might not be worth the trouble but is probably doable. ================================================ FILE: meetings/2015/LDM-2015-04-22-Design-Review.md ================================================ # C# Language Design Review, Apr 22, 2015 Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/3910. ## Agenda See #1921 for an explanation of design reviews and how they differ from design meetings. 1. Expression tree extension 2. Nullable reference types 3. Facilitating wire formats 4. Bucketing # Expression Trees Expression trees are currently lagging behind the languages in terms of expressiveness. A full scale upgrade seems like an incredibly big investment, and doesn't seem worth the effort. For instance, implementing `dynamic` and `async` faithfully in expression trees would be daunting. However, supporting `?.` and string interpolation seems doable even without introducing new kinds of nodes in the expression tree library. We should consider making this work. # Nullable reference types A big question facing us is the "two-type" versus the "three-type" approach: We want you to guard member access etc. behind null checks when values are meant to be null, and to prevent you from sticking or leaving null in variables that are not meant to be null. In the "three-type" approach, both "meant to be null" and "not meant to be null" are expressed as new type annotations (`T?` and `T!` respectively) and the existing syntax (`T`) takes on a legacy "unsafe" status. This is great for compatibility, but means that the existing syntax is unhelpful, and you'd only get full benefit of the nullability checking by completely rooting out its use and putting annotations everywhere. The "two-type" approach still adds "meant to be null" annotations (`T?`), but holds that since you can now express when things *are* meant to be null, you should only use the existing syntax (`T`) when things are *not* meant to be null. This certainly leads to a simpler end result, and also means that you get full benefit of the feature immediately in the form of warnings on all existing unsafe null behavior! Therein of course also lies the problem with the "two-type" approach: in its simplest form it changes the meaning of unannotated `T` in a massively breaking way. We think that the "three-type" approach is not very helpful, leads to massively rewritten over-adorned code, and is essentially not viable. The "two-type" approach seems desirable if there is an explicit step to opt in to the enforcement of "not meant to be null" on ordinary reference types. You can continue to use C# as it is, and you can even start to add `?` to types to force null checks. Then when you feel ready you can switch on the additional checks to prevent null from making it into reference types without '?'. This may lead to warnings that you can then either fix by adding further `?`s or by putting non-null values into the given variable, depending on your intent. There are additional compatibility questions around evolution of libraries, but those are somewhat orthogonal: Maybe a library carries an assembly-level attribute saying it has "opted in", and that its unannotated types should be considered non-null. There are still open design questions around generics and library compat. # Wire formats We should focus attention on making it easier to work with wire formats such as JSON, and in particular on how to support strongly typed logic over them without forcing them to be deserialized to strongly typed objects at runtime. Such deserialization is brittle, lossy and clunky as formats evolve out of sync, and extra members e.g. aren't kept and reserialized on the other end. There's a range of directions we could take here. Assuming there are dictionary-style objects representing the JSON (or other wire data) in a weakly typed way, options include: * Somehow supporting runtime conversions from such dictionaries to interfaces (and back) * Compile-time only "types" a la TypeScript, which translate member access etc. to a well-known dictionary pattern * Compile-time type providers a la F#, that allow custom specification not only of the compile-time types but also the code generated for access. We'd need to think about construction, not just consumption. ``` c# var thing = new Thing { name = "...", price = 123.45 } ``` Maybe `Thing` is an interface with an attribute on it: ``` c# [Json] interface { string name; double price; } ``` Or maybe it is something else. This warrants further exploration; the right feature design here could be an extremely valuable tool for developers talking to wire formats - and who isn't? # Bucketing We affirmed that the bucketing in issue #2136 reflects our priorities. ================================================ FILE: meetings/2015/LDM-2015-05-20.md ================================================ C# Design Meeting Notes for May 20, 2015 ======================================== Discussion for this issue can be found at https://github.com/dotnet/roslyn/issues/3911. _Quote of the day:_ The slippery slope you are talking about is that if we satisfy our customers they'll want us to satisfy them some more. Agenda ------ Today we discussed whether and how to add local functions to C#, with the aim of prototyping the feature in the near future. Proposal at issue #259. Some details are discussed in issue #2930. 1. Local functions Local Functions =============== We agree that the scenario is useful. You want a helper function. You are only using it from within a single function, and it likely uses variables and type parameters that are in scope in that containing function. On the other hand, unlike a lambda you don't need it as a first class object, so you don't care to give it a delegate type and allocate an actual delegate object. Also you may want it to be recursive or generic, or to implement it as an iterator. Lambdas or local functions? --------------------------- There are two ways of approaching it: * Make lambdas work for this * Add local function syntax to the language On the face of it, this seems like it would be better to reuse an existing feature. After all, we are not looking to address a new problem but just make existing code more convenient to write. In VB this scenario works pretty nicely with lambdas already - can't we just take a page out of VB's book? Well, it turns out you need a plethora of little features to achieve the full effect with lambdas: * lambdas would need to have an intrinsic (compiler-generated) delegate type that we can infer for them when they are not target typed to a specific delegate type: ``` c# var f = (int x) => x * x; // infers a compiler generated delegate type for int -> int. ``` * lambdas need to be able to recursively call themselves through a variable name they get assigned to. This introduces a problem with inferring a return type: ``` c# var f = (int x, int c) => c = 0 ? x : f(x) * x; // can we reasonably infer a return type here? var f = (int x, int c) => (int)(c = 0 ? x : f(x) * x); // or do we need to cast the result? ``` * lambdas need to be able to be iterators. Since they don't have a specified return type, how can we know if the iterator is supposed to be `IEnumerable`, `IEnumerator`, `IEnumerable` or `IEnumerator`? ``` c# var f = (int x, int c) => { for (int i = 0; i < c; i++) { yield return x; } }; // default to IEnumerable? ``` * We'd want lambdas to be generic. What's the syntax for a generic lambda - where do the type parameters go? Presumably in front of the parameter list? And wait a minute, we don't even have a notion of delegate types for generic functions! ``` c# var f = (IEnumerable src) => src.FirstOrDefault(); // Is this unambiguous? What's the delegate type? ``` VB does a subset of these, probably enough to get by, but all in all for C# it seems both the better and easier path to simply let you define a function in method scope. On top of this is the performance aspect: the lambda approach implies a lot of allocations: one for the delegate and one for the closure object to capture surrounding variables and type parameters. Sometimes one or both of these can be optimized away by a clever compiler. But with functions, the delegate is never there (unless you explicitly decide to create one when you need it), and if the function itself is not captured as a delegate, the closure can be a struct on the stack. **Conclusion**: Local functions are the better choice. Let's try to design them. Syntax ------ The syntax of a local functions is exactly as with a method, except that it doesn't allow the syntactic elements that are concerned with it being a member. More specifically: * No attributes * No modifiers except `async` and `unsafe` * The name is always just an identifier (not `Interface.MemberName`) * The body is never just `;` (always `=> ...` or `{ ... }`) We'll consider *local-function-declaration* to be a third kind of *declaration-statement*, next to *local-variable-declaration* and *local-constant-declaration*. Thus it can appear as a statement at the top level of a block, but cannot in itself be e.g. a branch of an if statement or the body of a while statement. Local functions need to be reconciled with top level functions in script syntax, so that they work as similarly as possible. Nested local functions in blocks, just like nested local variables, would truly be local functions even in script. Examples -------- A classical example is that of doing argument validation in an iterator function. Because the body of an iterator method is executed lazily, a wrapper function needs to do the argument checking. The actual iterator function can now be a local function, and capture all the arguments to the wrapper function: ``` c# public static IEnumerable Filter(IEnumerable source, Func predicate) { if (source == null) throw new ArgumentNullException(nameof(source)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); IEnumerable Iterator() { foreach (var element in source) if (predicate(element)) yield return element; } return Iterator(); } ``` An example of a recursive local function would be a Quicksort, for instance: ``` c# public static void Quicksort(T[] elements) where T : IComparable { void Sort(int start, int end) { int i = start, j = end; var pivot = elements[(start + end) / 2]; while (i <= j) { while (elements[i].CompareTo(pivot) < 0) i++; while (elements[j].CompareTo(pivot) > 0) j--; if (i <= j) { T tmp = elements[i]; elements[i] = elements[j]; elements[j] = tmp; i++; j--; } } if (start < j) Sort(elements, start, j); if (i < end) Sort(elements, i, end); } Sort(elements, 0, elements.Length - 1); } ``` Again it captures parameters and type parameters of the enclosing method, while calling itself recursively. For optimization purposes, some async methods are implemented with a "fast path" where they don't allocate a state machine or a resulting `Task` unless they discover that it's necessary. These aren't themselves `async`, but can have a nested local async function that they call when necessary, returning the resulting `Task`. Something along the lines of: ``` c# public Task GetByteAsync() { async Task ActuallyGetByteAsync() { await buffer.GetMoreBytesAsync(); byte result; if (!buffer.TryGetBufferedByte(out result)) throw ...; // we just got more return result; } byte result; if (!buffer.TryGetBufferedByte(out result)) return ActuallyGetByteAsync(); // slow path if (taskCache[result] == null) taskCache[result] = Task.FromResult(result); return taskCache[result]; } ``` By the way, we just did `taskCache[result]` three times there in the last two lines, each with its own bounds check. We could maybe optimize a little with a local function taking a ref: ``` c# Task GetTask(ref Task cache) { if (cache == null) cache = Task.FromResult(result); return cache; } return GetTask(ref taskCache[result]); // only indexing once ``` Of course if we *also* add ref locals to the language, such trickery would not be necessary. Scope and overloading --------------------- In various ways we need to choose whether local functions are more like methods or more like local variables: * Local variables only allow one of a given name, whereas methods can be overloaded * Local variables shadow anything of the same name in outer scopes, whereas method lookup will keep looking for applicable methods * Local variables are not visible (it is an error to use them) before their declaration occurs, whereas methods are. There are probably scenarios where it would be useful to overload local functions (or at least more elegant not to have to give them all separate names). You can also imagine wanting to augment a set of existing overloads with a few local ones. However, it is somewhat problematic to allow local functions to be visible before their declaration: they can capture local variables, and it would provide an easy loophole for manipulating those variables before their declaration: ``` c# f(3); int x; // x is now 3! void f(int v) { x = v; } ``` Such pre-declaration manipulation is certainly possible today, but it requires more sneaky use of lambdas or goto statements, e.g.: ``` c# goto Assign; Before: goto Read; Declare: int x = 5; goto Read; Assign: x = 3; goto Before; Read: WriteLine(x); if (x == 3) goto Declare; ``` This prints 3 and 5. Yeah, yuck. We should think twice before we allow local functions to make this kind of thing so much easier that you might likely do it by mistake. On the other hand, there's no nice way to write mutually recursive local functions, unless the first can also see the second. (The workaround would be to nest one within the other, but that's horrible). For now, for prototyping purposes, we'll say that local functions are more like local variables: there can only be one of a given name, it shadows same-named things in outer scopes, and it cannot be called from a source location that precedes its declaration. We can consider loosening this later. Definite assignment analysis of locals captured by local functions should be similar to lambdas for now. We can think of refining it later. Type inference -------------- Lambdas have a mechanism for inferring their return type. It is used for instance when a lambda is passed to a generic method, as part of that generic method's type inference algorithm. We could allow local functions to be declared with `var` as their return type. Just as `var` on local variables doesn't always succeed, we could fail whenever finding the return type of the local function is too hard; e.g. if it is recursive or an iterator. ``` c# var fullName(Person p) => $"{p.First} {p.Last}"; // yeah, it's probably going to be a string. ``` We don't need this for prototyping, but it is nice to consider for the final design of the feature. Declaration expressions ----------------------- One thing lambdas have going for them is that they can be embedded in expression contexts, whereas local declarations currently can't, though we had a proposal for declaration expressions in C# 6. If we were to do some sort of let expressions, local functions should ideally work alongside local variables. The proposed C# 6 scheme would work for that: ``` c# return (IEnumerable Impl() { ... yield return ... }; Impl()); // Or no semicolon? ``` Why would you do that? For encapsulation, but especially if you're in a big expression context where pulling it out is too far. Imagine a string-interpolated JSON literal that has an array inside it that I want to construct with an iterator: ``` c# return $@"""hello"": ""world"" ... // 30 lines of other stuff ""list"" : { ( IEnumerable GetValues() { ... yield return ... }; JsonConvert.SerializeObject(GetValues())) }"; ``` We don't have to think about this now, but it is nice to know that it's possible if we ever do declaration expressions to include local functions as one of the things you can declare in an expression context. Slippery slope -------------- Local classes might be the next ask, but we perceive them as much less frequent, and other features we're discussing would make them even less so. ================================================ FILE: meetings/2015/LDM-2015-05-25.md ================================================ # C# Design Meeting Notes for May 25, 2015 Discussion for these notes can be found in https://github.com/dotnet/roslyn/issues/3912. ## Agenda Today we went through a bunch of the proposals on GitHub and triaged them for our list of features in issue #2136. Due to the vastness of the list, we needed to use *some* criterion to sort by, and though far from ideal we ordered them by number of comments, most to least. Here's where we landed, skipping things that were already "Strong interest". Some are further elaborated in sections below. 1. Method contracts <*Stay at "Some interest"*>(#119) 2. Destructible types <*Stay at "Probably never"*> (#161) 3. Params IEnumerable <*Stay at "Small but Useful*>(#36) 4. Multiple returns <*Addressed by tuples. Close.*> (#102) 5. More type inference <*Not a coherent proposal. Close*> (#17) 6. Readonly parameters and locals <*Stay at "Some interest"*>(#115) 7. Implicitly typed lambdas <*Add at "Probably never"*>(#14) 8. Immutable types <*Stay at "Some interest*>(#159) 9. Object initializers for immutable objects <*Add at "Some interest"*>(#229) 10. First item is special <*Add at "Never"*>(#131) 11. Array slices <*Keep at "Interesting but needs CLR support"*>(#120) 12. Vararg calling convention <*Merge with params IEnumerable*>(#37) 13. XML Literals <*Add to "Never"*>(#1746) 14. Local Functions <*Move to "Some interest*>(#259) 15. Covariant returns <*Stay at "Some interest*>(#357) # Params IEnumerable This needs more thinking - let's not just implement the straightforward design. There are perf issues, for instance, around implementing through the `IEnumerable` interface instead of arrays directly. # More type inference Not a coherent proposal. But even if there was one, we probably wouldn't want it in C#. # Implicitly typed lambdas These are mostly subsumed by local functions, which we'd rather do. It has some individual usefulness but not much synergy. # Object initializers for immutable objects We want to think this together with withers, not sure what form it would take. # first item in loops is special We recognize the scenario but it's not worthy of a feature. # vararg calling convention Roll it in with params IEnumerable discussion for investigation. # XML literals Never! We won't bake in a specific format. ================================================ FILE: meetings/2015/LDM-2015-07-01.md ================================================ # C# Design Meeting Notes for Jul 1, 2015 Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/3913. ## Agenda We are gearing up to prototype the tuple feature, and put some stakes in the ground for its initial design. This doesn't mean that the final design will be this way, and some choices are arbitrary. We merely want to get to where a prototype can be shared with a broader group of C# users, so that we can gather feedback and learn from it. # Tuples The tuples proposal #347 is pretty close to what we want, but has some open questions and unaddressed aspects. ## Names We want the elements of tuples to be able to have names. It is exceedingly useful to be able to describe which elements have which meaning, and to be able to dot into a tuple with those names. However, it is probably not useful to make the names too strongly a part of the type of a tuple. There is no really good reason to consider (int x, int y) an fundamentally different tuple type than (int a, int b). In fact, according to the analogy with parameter lists, the names should be of secondary importance: useful for getting at the elements, yes, but just as parameter lists with different parameter names can overwrite each other, as long as the types match at the given parameter positions, so should tuple types be considered equivalent when they have the same types at the same positions. Another way to view it is that all tuples with the same types at the same positions share a common underlying type. We will make that type denotable in the language, in that you can write anonymous tuple types, `(int, int)`, even though you cannot write an anonymous parameter list. For now we don't think we will allow partially named tuples: it is either all names or none. ``` c# (int x, int y) t1 = ...; (int a, int b) t2 = t1; // identity conversion (int, int) t3 = t1; // identity conversion ``` All "namings" of a tuple type are considered equivalent, and are convertible to each other via an identity conversion. For type inference purposes, an inferred tuple type will have the names if all "candidate types" with names agree on them, otherwise it will be unnamed. ``` c# var a1 = new[]{ t1, t1 }; // infers (int x, int y)[], since all agree var a2 = new[]{ t1, t2 }; // infers (int, int)[], since not all agree var a3 = new[]{ t1, t3 }; // infers (int x, int y)[] since all with names agree ``` For method overriding purposes, a tuple type in a parameter or return position can override a differently named tuple type. The rule for which names apply at the call site are the same as those used for named arguments: the names from the most specific statically known overload. Tuple literals likewise come in named an unnamed versions. They can be target typed, but sometimes have a type of their own: ``` c# var t1 = ("Hello", "World"); // infers (string, string) var t2 = (first: "John", last: "Doe"); // infers (string first, string last) var t3 = ("Hello", null); // fails to infer because null doesn't have a type var t4 = (first: "John", last: null); // fails to infer because null doesn't have a type (string, string) t5 = ("Hello", null); // target typed to (string, string) (string first, string last) t6 = ("John", null); // target typed to (string first, string last) (string first, string last) t7 = (first: "John", second: "Doe"); // error: when given, names must match up (string first, string last) t8 = (last: "Doe", first: "John"); // fine, values assigned according to name ``` The last two are probably the only possibly controversial examples. When target typing with names in the literal, this seems very similar to using named arguments for a method call. These rules match that most closely. This is something we may want to return to, as it has some strange consequences. For instance, if we introduce a temporary variable for the tuple, and do not use target typing: ``` c# var tmp = (last: "Doe", first: "John"); // infers (string last, string first) (string first, string last) t8 = tmp; // assigns by position, so first = "Doe" ``` But for now, let's try these rules out and see what they feel like. ## Encoding Are core question is what IL we generate for tuples. The equivalence-across-names semantics make it easy to rely on a fixed set of underlying generic library types. We want tuples to be value types, since we expect them to be created often and copied less often, so it's probably worthwhile avoiding the GC overhead. Strangely perhaps, we want tuples to be mutable. We think that there are valid scenarios where you want to keep some data in a tuple, but be able to modify parts of it independently without overwriting the whole tuple. Also, calling methods on readonly tuple members that are themselves value types, would cause those methods to be called on a throw-away copy. This is way too expensive - it means that there may be no non-copying way of doing e.g. value-based equality on such tuples. So we encode each tuple arity as a generic struct with mutable fields. It would be very similar to the current Tuple<...> types in the BCL, and we would actively avoid purely accidental differences. For this reason, the fields will be called `Item1` etc. Also, tuples bigger than a certain arity (probably 8) will be encoded using nesting through a field called `Rest`. So we are looking at types like this: ``` c# public struct ValueTuple { public T1 Item1; public T2 Item2; public T3 Item3; } ``` Possibly with a constructor, some `Equals` and `GetHashCode` overrides and maybe an implementation of `IEquatable<...>` and `IStructurallyEquatable<...>`, but at its core exceedingly simple. In metadata, a named tuple is represented with its corresponding `ValueTuple<...>` type, plus an attribute describing the names for each position. The attribute needs to be designed to also be able to represent names in nested tuple types. The encoding means that an earlier version of C#, as well as other languages, will see the tuple members as `Item1` etc. In order to avoid breaking changes, we should probably keep recognizing those names as an alternative way of getting at any tuple's elements. To avoid confusion we should disallow `Item1` etc. as names in named tuples - except, perhaps, if they occur in their right position. ## Deconstruction syntax Most tuple features have a deconstruction syntax along the following lines: ``` c# (int sum, int count) = Tally(...); ``` If we want such a syntax there are a number of questions to ask, such as can you deconstruct into existing fields or only newly declared ones? For now we sidestep the question by not adding a deconstruction syntax. The names make access much easier. If it turns out to be painful, we can reconsider. ## Other issues We will not consider tuples of arity zero or one - at least for now. They may be useful, especially from the perspective of generated source code, but they also come with a lot of questions. It seems reasonable to consider other conversions, for instance implicit covariant conversions between tuples, but this too we will let lie for now. ================================================ FILE: meetings/2015/LDM-2015-07-07.md ================================================ # C# Design Notes for Jul 7 2015 Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/5031. Quotes of the day: > "I don't think I've had a Quote of the Day for years \" > "What you just described is awful, so we can't go there!" ## Agenda With Eric Lippert from Coverity as an honored guest, we looked further at the nullability feature. 1. Adding new warnings 2. Generics and nullability # New warnings The very point of this feature is to give new warnings about existing bugs. What's a reasonable experience here? Ideally folks will always be happy to be told more about the quality of the code. Of course there needs to be super straightforward ways of silencing warnings for people who just need to continue to build, especially when they treat warnings as errors (since those would be breaking). We've previously been burned by providing new warnings that then broke people. There's a problem when those warnings are false positives, which though hopefully rare, is going to happen: I checked for null in a way the compiler doesn't recognize. Can I build an analyzer to turn warnings *off* if it knows better? Anti-warnings? That's a more general analyzer design question. Coverity for instance have very advanced analysis to weed out false positives, and avoid the analysis becoming noisy. # Generics There's a proposal to treat generics in the following way: Both nullable and nonnullable types are allowed as type arguments. Nullability flows with the type. Type inference propagates nullability - if any input type is nullable the inferred type will be, too. Constraints can be nullable or nonnullable. Any nonnullable constraints mean that only non-nullable reference types can satisfy them (without warning). Unconstrained generics is reinterpreted to mean constrained by `object?`, in order to continue to allow all safe types as type arguments. If a type parameter `T` is constrained to be nonnullable, `T?` can be used without warning. Thus: ``` c# class C where T : Animal? { public T x; public T? y; // warning } class D where T : Animal { public T x; public T? y; } C C D D // warning ``` Inside of generics, type parameters are always expected to possibly be nonnullable. Therefore assigning null or nullable values to them always yields a warning (except probably when they are from the *same* possibly nullable type parameter!). This is nice and consistent. Unfortunately it isn't quite expressive enough. There are cases such as `FirstOrDefault` where we'd really want to return "the nullable version of T if it isn't already nullable". Assume an operator "`^`" (that shouldn't *actually* be the syntax) to mean take the nullable of any nonnullable reference type, leave it alone otherwise: ``` c# public static T^ FirstOrDefault(this IEnumerable source); var a = new string[] { "a", "b", "c" }; var b = new string[] {}; var couldBeNull = condition ? a.FirstOrDefault() : b.FirstOrDefault(); // string? ``` We would need to decide what the syntax for `^` *actually* is if we want to have that expressiveness. `T^` can also serve the purpose of helping null-check code *inside* of a generic method or type. A `List` type for instance could declare its storage array to be `T^[]` instead of `T[]` so that it warns you where you use it that the array elements could be null. `T?` cannot exist when T is not constrained to either struct or class, because it means different things in the two cases, and we can't code gen across them. Cast doesn't do null check, just suppresses warning. ================================================ FILE: meetings/2015/LDM-2015-08-18.md ================================================ # C# Design Notes for Aug 18, 2015 Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/5033. ## Agenda A summary of the design we (roughly) landed on in #5031 was put out on GitHub as #5032, and this meeting further discussed it. 1. Array creation 2. Null checking operator 3. Generics # Array creation with non-nullable types For array creation there is the question whether to allow (big hole) or disallow (big nuisance?) on non-nullable reference types. We'll leave it at allow for now, but may reconsider. # Null checking operator Casting to a non-nullable reference type would not, and should not, do a runtime null check. Should we, however, have an *operator* for checking null, throwing if the value *is* null, resulting in the non-null value if it isn't? This seems like a good idea. The operator is postfix `!`, and it should in fact apply to values of nullable *value* types as well as reference types. It "upgrades" the value to non-nullable, by throwing if it is null. ``` c# if (person.Kind == Student) { List courses = person.Courses!; // I know it's not null for a student, but the compiler doesn't. ... } ``` The `!` operator naturally leads to `x!.y`, which is great! Although `!.` is two operators, it will feel as a cousin of `?.` (which is one operator). While the latter is conditional on null, the former just plows through. Naively, it implies two redundant null checks, one by `!` and one by `.`, but we'll optimize that of course. ``` c# if (person.Kind == Student) { var passed = !person.Courses!.Any(c => c.Grade == F); ... } ``` Technically this would allow `x!?.y`, which comes quite close to swearing. We should consider warning when you use `?.` on non-null things. VB may have a problem with post-fix `!`. We'll cross that bridge when we get there. # Generics and nullability Is it too heavyhanded to require `?` on constraints to allow nullable type arguments? Often, when you have a constraint it is because you want to operate on instances. So it's probably good that the default is not nullable. It may feel a bit egregious to require it on *all* the constraints of a type parameter, though. Should we put any `?`'s on the type parameter declaration instead of in the constraints? No, that is too weird and different. The case of multiple nullable constraints is probably sufficiently rare that it is reasonable to ask folks to put a `?` on each. In fact we should disallow having `?` on only some, since those question marks won't have an effect: they'll be cancelled by the non-nullable fellow constraints. The proposal talks about allowing `?` on the use of type parameters to explicitly override their nullness. Maybe we should have an explicit `!` as well, to explicitly override in the other direction: non-nullable. Think for instance of a `FirstNonNull` method. ``` c# T! FirstNonNull(IList list) { ... } T? FirstOrDefault(IList list) { ... } ``` This means complexity slowly creeps into the proposal, thanks to generics. However, it seems those overrides are relatively rare, yet really useful when you need them. `T!` would only be allowed on type parameters, and only when they are not already non-null by constraint. ================================================ FILE: meetings/2015/LDM-2015-09-01.md ================================================ C# Design Meeting Sep 1 2015 ============================ Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/5233. Agenda ------ The meeting focused on design decisions for prototypes. There's no commitment for these decisions to stick through to a final feature; on the contrary the prototypes are being made in order to learn new things and improve the design. 1. ref returns and ref locals 2. pattern matching Ref returns and ref locals ========================== Proposal in #118, Initial PR for prototype in #4042. This feature is a generalization of ref parameters, and as such doesn't have much new, either conceptually or in how you work with it. As is already the case, refs cannot be null (they always reference a memory location), and you access the location they point to without explicitly dereferencing. The new thing is that you can return refs from methods and other functions, and that you can have locals that are refs to other memory locations. There's a new notion of "safe-to-return". It would be bad if you could return a ref to your own stack frame, for instance. Therefore there's a simple analysis to always track if a ref is "safe-to-return" - and a compile time error if you return something that isn't. A given method can get refs from a number of places: 1. refs to locals in this method 2. ref parameters passed to it 3. refs into heap objects 4. refs returned from calls 5. ref locals in this method The 1st are never safe to return; the 2nd and 3rd always are. The 4th are safe to return if every ref passed *into* the call is safe to return - since we then know that the returned ref cannot be to a local of the calling (or called) method. The 5th is more involved, as it depends on which semantics we adopt for ref locals. Ref locals ---------- There are a couple of different questions to consider for ref locals: 1. Can they be reassigned or are they fixed to a location at declaration time (like ref parameters)? 2. When are they safe to return? Always? Never? Depends? If ref locals can be reassignable *and* unsafe-to-return, that means that we have to worry about lifetime analysis: ``` c# ref int r; while (...) { int x = ...; if (...) ref r = ref x; } WriteLine(r); // What now? ``` In other words we have to concern ourselves with the situation where locals are assigned to a ref that is longer lived than the local itself. We would have to detect it and either forbid it or resort to expensive techniques (similar to variable capture by lambdas) that run counter to the perf you were probably hoping for by using refs in the first place. On the other hand, if ref locals cannot be assigned after initialization, there will be no opportunity to assign variables to them that live in a more nested, and hence shorter-lived, scope. And if they are required to be safe-to-return, then *no* locals can be assigned to them, regardless of lifetime. Either of these seem more attractive! We think that not being able to assign locals to ref locals is too limiting. For instance, it would be common to keep a struct in a local, and then call a function with a ref to it to locate and return a ref to some data nested in it. You'd want to store the result of that in a ref local: ``` c# Node n = ...; struct ref Node l = ref GetLeftMostSubNode(ref n); l.Contents = ...; // modify leftmost node ``` So we definitely want to allow ref locals that are non-reassignable and unsafe-to-return. However, it is also reasonable to want to call similar helper methods on refs that *are* safe to return, to store the results and to then return them: ``` c# ref Node M(ref Node n) { if (...) { ref Node l = ref GetLeftMostSubNode(ref n); l.Contents = ...; // modify leftmost node return ref l; } ... } ``` It seems that we want ref locals to be safe-to-return or not *depending on what they are initialized with*. We think that there is also some value in having reassignable ref locals. For instance, you can imagine a ref local being the current "pointer" in a loop over a struct array. We can imagine a rule that simply says that ref locals are reassignable *except if they are initialized with an unsafe-to-return ref*. Then, only safe-to-return refs can be assigned to the reassignable ones, and we still don't have lifetime tracking issues. On closer scrutiny, though, this scenario does raise harder issues. If you use a ref as a current pointer, what's its initial value? Do we need null refs in the language now? How do you increment it? Can it point "past the edge" at the end? These are questions we don't want to answer right now for a somewhat hypothetical scenario. **Conclusion**: for the prototype, ref locals are non-reassignable, and get their "safe-to-return" status from their mandatory ref initializer. If there are scenarios not covered by this, we'll discover as folks start using the prototype and tell us. By avoiding reassignment we avoid answering a couple of difficult questions: * What's the syntax of a ref assignment? `ref x = y`? `ref x = ref y`? `x = ref y`? * Are ref assignments expressions, and what does that mean? * Null/default values or definite assignment analysis to prevent the need for them `this` in structs ----------------- For non-readonly structs, `this` is generally described as being like a ref parameter. However, considering it safe-to-return like ref parameters in struct methods leads to undesirable behavior: It lets a ref to `this` be returned from the method. Therefore a caller needs to consider a ref returned from a method on a struct local *not* safe to return (the returned ref could be the struct local itself or part of it). In essence, the ref `this` "contaminates" any ref-returning method called on a local value type. Scenarios where this is a problem include using structs as wrapper types for heap objects. Somewhat counterintuitively perhaps, the better rule is to consider `this` in struct methods unsafe-to-return. Since struct methods will never return a ref to the receiver, you can call them with safe-to-return ref parameters and get safe-to-return ref results. This also solves a problem with generics, where you don't *know* if a receiver is going to be a struct. Now you don't have to program defensively against that; ref returning methods called on *anything* will be independent of their receiver with regards to safe-to-return determination. Other issues ------------ * We may want an unsafe language-level feature to convert between refs and pointers. We'll look at that later. * PEVerify considers ref return unsafe. We need to chase that down, but for the prototype it's fine. * It'd be useful to have a notion of "readonly refs" - refs through which you cannot write to the referenced storage. This is a separate (big) topic for later. Pattern matching ================ Proposal in #206, initial PR in #4882. Syntax: There are patterns, extended is-expressions, and (later) switch. ``` c# pattern: * type identifier // scope is a separate issue type { id is pattern, ... } [identifier] type (pattern, ...) constant-expression is-expression: relational-expression is pattern ``` For constants, we would have integral constants be flexible between types. Floats probably not. This has tons of levers, that we can obsess over, but in the prototype this is what we'll go with. Relational operators in patterns? `e is > 3`? Maybe later. For VB probably more issues. Save for later. Scope for variables: in scope in nearest enclosing statement, *except* the else-block of an if-statement. This is a weird but useful exception that allows subsequent else-if's to introduce variables with the same name as previous ones in the conditions. FOr now, the introduced variables are not reassignable. This could have both positive and negative perf impact. It's an opportunity to do "the right thing", but also a deviation from what we do with other variables. We'll try it in the prototype and see what it feels like. Deconstruction and guards are interesting further topics that we don't want to pursue yet. Record types are not part of this prototype. Some concerns about the active pattern part being too weird or different. Best resolved by prototyping it and playing with it. ================================================ FILE: meetings/2015/LDM-2015-09-02.md ================================================ C# Design Notes Sep 2 2015 ========================== Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/5234. Quote of the day: Are you pulling the curtain away from my magic retort? Agenda ------ 1. Extending nameof 2. What do branches mean 3. Supersedes Extending nameof ================ Should we allow empty type argument lists? When we first designed nameof the idea was to allow the same operands as typeof. During design we took it more in the direction of expressions, which seemed more useful. However, it leaves a nuisance of providing type arguments that don't really provide any value. One problem with this approach is what to do about situations like `MyList<>.First.P` where `First`'s type is the type parameter of `MyList`. We would think of `MyList<>` as having a type argument that has no members at all - not even the ones from object. Thus, if you dot on it, you get an error. More simply, just allow what's in typeof, followed by one member access. Make sure that, like in typeof, type arguments are either given everywhere or nowhere. For inherited members/nested types, we should do the same as typeof does. We don't expect this to upset analyzers much. We would make sure that the unbound types do have members in the semantic model. *Those* members just don't have members. Thus `MyList<>.First` is fine, but `MyList<>.First.P` is not. We like this. What do branches mean ===================== As prototypes start to come online, we need to decide how we use our branches. The Future branch is for when things are at prototype level for the whole experience - including IDE. Features will be guarded by a feature flag until it's the confident plan of record to ship the feature in its current form. We are working on enabling VSIXes to change the language model in the IDE without the need for customers to install over VS. Hopefully this capability will be available in an update to VS in a not too distant future. Once that is in place, we can ship prototypes that run without risk to customers' VS installation. Supersedes ========== For metaprogramming, like Javas annotation processing tool (APT), we would like to facilitate generating additional code from existing code (e.g. based on attributes or patterns). Roslyn now provides an excellent stack for that. As a language C# is already better suited for this than Java because of partial types and methods: they allow the merging of existing and generated code to be less convoluted. However we can do even better. We can allow omitting `partial` on one of the parts, so the user written code doesn't need it. But more interestingly we can consider a `supersedes` feature (where the keyword `supersedes` may need to be replaced by something that people have less trouble spelling!): ``` c# class C { public void M() { // do stuff } [INPC] public int X { get { ... } set { ... } } // generated partial class C { public supersedes void M() { // do something superseded(); // calls original method // do more } public supersedes int X { get { return superseded; } set { superseded = value; RaiseNPC(nameof(X)); } } } ``` The compiler would generate code to keep the original `M` and `X` under a mangled name, inlined or something. Challenge: composing multiple rewriters: either we have an ordering rule, or we get errors. This is a challenging problem. Another good question: Do you have just one generator phase, or keep going to a fix point. Java keeps going until no changes. Is the generated code visible to the user? You should see it like generated code today, and the compiler would parse it again to guard from mistakes. That way, code could also be generated by t4 and other tools, as well as Roslyn. Concern: we are adding a new concept. Would it be better to use existing concepts, like Java does? Concern: efficiency. Forces binding, throws away first result and binds again against generated code. This is very interesting and promising, but there are many juicy challenges ahead. ================================================ FILE: meetings/2015/LDM-2015-09-08.md ================================================ # C# Design Notes for Sep 8, 2015 Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/5383. ## Agenda 1. Check-in on some of our desirable features to see if we have further thoughts 2. Start brainstorming on async streams # Check-in on features ## Bestest betterness Overload resolution will sometimes pick a candidate method only to lead to an error later on. For instance, it may pick an instance method where only a static one is allowed, a generic method where the constraints aren't satisfied or an overload the relies on a method group conversion that will ultimately fail. The reason for this behavior is usually to keep the rules simple, and to avoid accidentally picking another overload than what you meant. However, it also leads to quite a bit of frustration. It might be time to sharpen the rules. ## Private protected In C# 6 we considered adding a new accessibility level with the meaning protected *and* internal (`protected internal` means protected *or* internal), but gave up because it's hard to settle on a syntax. We wanted to use `private protected` but got a lot of loud push back on it. No-one has come up with a better syntax in the meantime though. We are inclined to think that `private protected` just takes a little getting used to. We may want to try again. ## Attributes There are a number of different features related to attributes. We should return to these in a dedicated meeting looking at the whole set of proposals together: * Generic attributes: They seem to be allowed by the runtime, but for some reason are disallowed by the language * Compile-time only attributes: For attributes that are intended to be used only before IL gen, it'd be nice to have them elided and not bloat metadata * Attributes in more places: There are more places where it makes sense to allow attributes, such as on lambda expressions. If they are compile-time only, they can even be in places where there is no natural code gen. * Attributes of more types: For attributes that are not going into code gen, we could relax the restrictions on the types of their constructor parameters. A lot of these would be particularly helpful to Roslyn based tools such as analyzers and metaprogramming. ## Local extern functions We should allow local functions to be extern. You'd almost always want to wrap an extern method in a safe to call wrapper method. ## Withers for arbitrary types If we want to start focusing more on immutable data structures, it would be nice if there was a language supported way of creating new objects from existing ones, changing some subset of properties along the way: ``` c# var newIfStatement = ifStatement with { Condition = newCondition, Statement = newStatement }; // other properties copied ``` Currently the Roslyn code base, for example, uses the pattern of "withers", which are methods that return a new object with one property changed. There needs to be a wither per property, which is quite bloatful, and in Roslyn made feasible by having them automatically generated. Even so, changing multiple properties is suboptimal: ``` c# var newIfStatement = ifStatement.WithCondition(newCondition).WithStatement(newStatement); ``` It would be nice if we could come up with an efficient API pattern to support a built-in, efficient `with` expression. Related to this, it would also be nice to support object initializers on immutable objects. Again, we would need to come up with an API pattern to support it; possibly the same that would support the with expression. ## Params IEnumerable This is a neat little feature that we ultimately rejected or didn't get to in C# 6. It lets you do away with the situation where you have to write two overloads of a method, one that takes an `IEnumerable` (for generality) and another one that takes `params T[]` and calls the first one with its array. The main problem raised against params IEnumerable is that it encourages an inefficient pattern for how parameters are captured: An array is allocated even when there are very few arguments (even zero!), and the implementation then accesses the elements through interface dispatches and further allocation (of an IEnumerator). Probably this won't matter for most people - they can start out this way, and then build a more optimal pattern if they need to. But it might be worthwhile considering a more general language pattern were folks can build a params implementation targeting something other than arrays. # Async streams We shied back from a notion of asynchronous sequences when we originally introduced async to C#. Part of that was to see whether there was sufficient demand to introduce framework and language level concepts, and get more experience to base their design on. But also, we had some fear that using async on a per-element level would hide the true "chunky" degree of asynchrony under layers of fine-grained asynchronous abstractions, at great cost to performance. ## IAsyncEnumerable At this point in time, though, we think that there is definitely demand for common abstractions and language support: foreach'ing, iterators, etc. Furthermore we think that the performance risks - allocation overhead in particular - of fine grained asynchrony can large be met with a combination of the compiler's existing optimizations and a straightforward asynchronous "translation" of `IEnumerable` into `IAsyncEnumerable`: ``` c# public interface IAsyncEnumerable { public IAsyncEnumerator GetEnumerator(); } public interface IAsyncEnumerator { public T Current { get; } public Task MoveNextAsync(); } ``` The only meaningful difference is that the `MoveNext` method of the enumerator interface has been made async: it returns `Task` rather than `bool`, so that you need to await it to find out whether there is a next element (which you can then acquire from the `Current` property) or the sequence is finished. ## Allocations Let's assume that you are foreach'ing over such an asynchronous sequence, which is buffered behind the scenes, so that 99.9% of the time an element is available locally and synchronously. Whenever a `Task` is awaited that is already completed, the compiler avoids the heavy machinery and just gets the value straight out of the task without pause. If *all* awaited Tasks in a given method call are already completed, then the method will never allocate a state machine, or a delegate to store as a continuation, since those are only constructed the first time they are needed. Even when the async method reaches its return statement synchronously, without the awaits having ever paused, it needs to construct a Task to return. So normally this would still require one allocation. However, the helper API that the compiler uses for this will actually cache completed Tasks for certain common values, including `true` and `false`. In summary, a `MoveNextAsync` call on a sequence that is buffered would typically not allocate anything, and the calling method often wouldn't either. The lesson is that fine-grained asynchrony is bad for performance if it is done in terms of `Task` where completed Tasks are never or rarely cached, e.g. `Task` or `Task`. It should be done in terms of `Task` or even non-generic `Task`. We think that there may or may not be scenarios where people want to get explicit about the "chunks" that data is transmitted in. If so, they can express this as `IAsyncEnumerable>` or some such thing. But there is no need to complicate asynchronous streaming by forcing people to deal with chunking by default. ## Linq bloat Another concern is that there are many API's on `IEnumerable` today; not least the Linq query operators `Select`, `Where` and so on. Should all those be duplicated for `IAsyncEnumerable`? And when you think about it, we are not just talking about one extra set of overloads. Because once you have asynchronously foreach'able streams, you'll quickly want the delegates applied by the query operators to *also* be allowed to be async. So we have potentially four combinations: ``` c# public static IEnumerable Where(this IEnumerable source, Func predicate); public static IAsyncEnumerable Where(this IAsyncEnumerable source, Func predicate); public static IAsyncEnumerable Where(this IEnumerable source, Func> predicate); public static IAsyncEnumerable Where(this IAsyncEnumerable source, Func> predicate); ``` So either we'd need to multiply the surface area of Linq by four, or we'd have to introduce some new implicit conversions to the language, e.g. from `IEnumerable` to `IAsyncEnumerable` and from `Func` to `Func>`. Something to think about, but we think it is probably worth it to get Linq over asynchronous sequences one way or another. Along with this, we'd need to consider whether to extend the query syntax in the language to also produce async lambdas when necessary. It may not be worth it - using the query operators may be good enough when you want to pass async lambdas. ## Language support In the language we would add support for foreach'ing over async sequences to consume them, and for async iterators to produce them. Additionally (we don't discuss that further here) we may want to introduce a notion of `IAsyncDisposable`, for which we could add an async version of the `using` statement. One concern about async versions of language features such as foreach (and using) is that they would generate `await` expressions that aren't there in source. Philosophically that may or may not be a problem: do you want to be able to see where all the awaiting happens in your async method? If that's important, we can maybe add the `await` or `async` keyword to these features somewhere: ``` c# foreach (string s in asyncStream) { ... } // or foreach async (string s in asyncStream) { ... } // or foreach (await string s in asyncStream) { ... } // etc. ``` Equally problematic is when doing things such as `ConfigureAwait`, which is important for performance reasons in libraries. If you don't have your hands on the Task, how can you `ConfigureAwait` it? The best answer is to add a `ConfigureAwait` extension method to `IAsyncEnumerable` as well. It returns a wrapper sequence that will return a wrapper enumerator whose `MoveNextAsync` will return the result of calling `ConfigureAwait` on the task that the wrapped enumerator's `MoveNextAsync` method returns: ``` c# foreach (string s in asyncStream.ConfigureAwait(false)) { ... } ``` For this to work, it is important that async foreach is pattern based, just like the synchronous foreach is today, where it will happily call any `GetEnumerator`, `MoveNext` and `Current` members, regardless of whether objects implement the official "interfaces". The reason for this is that the result of `Task.ConfigureAwait` is not a `Task`. A related issue is cancellation, and whether there should be a way to flow a `CancellationToken` to the `MoveNextAsync` method. It probably has a similar solution to `ConfigureAwait`. ## Channels in Go The Go language has a notion of channels, which are communication pipes between threads. They can be buffered, and you can put things in and take them out. If you put things in while the channel is full, you wait. If you take things out while it is empty, you wait. If you imagine a `Channel` abstraction in .NET, it would not have blocking waits on the endpoints; those would instead be asynchronous methods returning Tasks. Go has an all-powerful language construct called `select` to consume the first available element from any set of channels, and choosing the logic to apply to that element based on which channel it came from. It is guaranteed to consume a value from only one of the channels. It is worthwhile for us to look at Go channels, learn from them and consider to what extent a) we need a similar abstraction and b) it is connected to the notion of async streams. Some preliminary thoughts: Channels and select statements are very easy to understand, conceptually. On the other hand they are somewhat low-level, and extremely imperative: there is a strong coupling from the consumer to the producer, and in practice there would typically only *be* one consumer. It seems like a synchronization construct like semaphores or some of the types from the DataFlow library. The "select" functionality is interesting to ponder more generally. If you think about it from an async streams perspective, maybe the similar thing you would do would be to merge streams. That would need to be coupled with the ability to tie different functionality to elements from different original streams - or with different types. Maybe pattern matching is our friend here? ``` c# foreach (var e in myInts.Merge(myStrings)) { switch (e) { case int i: ... case string s: ... } } ``` Either way, it isn't as elegant by a long shot. If we find it's important, we'd need to consider language support. Another important difference between IAsyncEnumerable and Channels is that an enumerable can have more than one consumer. Each enumerator is independent of the others, and provides access to all the members - at least from the point in time where it is requested. ## Conclusion We want to keep thinking about async streams, and probably do some prototyping. ================================================ FILE: meetings/2015/LDM-2015-10-07-Design-Review.md ================================================ Notes on Records and Pattern Matching for 2015-10-07 design review ================================================================== Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/5757. ### Records and Pattern Matching (https://github.com/dotnet/roslyn/issues/206) Prototyped together last year by @semihokur, but that prototype is based on very out-of-date Roslyn code. We also have some design changes since that time and we want to separate a pattern-matching prototype from records/ADTs so we can make independent decisions about whether and when to include them in the language(s). First step is to port pattern matching to the latest sources. In-progress port at https://github.com/dotnet/roslyn/pull/4882 ### Spec changes since the 2014 prototype For pattern matching: 1. Scoping of pattern-introduced variables (with "funny" rule for `if`) 2. Rules for `switch` statement that make it a compatible extension of the existing construct (https://github.com/dotnet/roslyn/issues/4944) 3. An expression form of multi-arm pattern-matching (https://github.com/dotnet/roslyn/issues/5154) 4. A `when` clause added to `switch` cases. And, for records: 1. No `record` keyword necessary 2. `with` expressions (https://github.com/dotnet/roslyn/issues/5172) 3. Approach for algebraic data types ### Implementation status of prototype port 1. For pattern matching, checklist at https://github.com/dotnet/roslyn/pull/4882 tracking the progress 2. For records, port not started ### Making the extension of `switch` backward-compatible - We say that the cases are matched in order, except `default` which is always the last resort. - Integral-typed case labels match any integral-valued control expression with the same value. - One issue around user-defined conversions to switchable types is resolved (https://github.com/dotnet/roslyn/issues/4944). In the draft spec, a conversion will be applied on the `case`s, not on the control-expression unilaterally. Instead of converting only to `switchable` types, each `case` arm will consider any conversions that allow the `case` to be applied. Any given conversion would be applied at most once. ```cs Foo foo = ...; // has a conversion to int switch (foo) { case 1: // uses the converted value case Foo(2): // uses the original value case 3: // uses the converted value } ``` - The `goto case` statement is extended to allow any expression as its argument. ### Expression form of multi-arm pattern matching (https://github.com/dotnet/roslyn/issues/5154) ```cs var areas = from primitive in primitives let area = primitive match ( case Line l: 0 case Rectangle r: r.Width * r.Height case Circle c: Math.PI * c.Radius * c.Radius case *: throw new ApplicationException() ) select new { Primitive = primitive, Area = area }; ``` There is no `default` here, so cases are handled strictly in order. I propose the spec require that the compiler "prove" that all cases are handled in a `match` expression using not-yet-specified rules. Writing those rules is an open work item, but I imagine it will require the compiler to build a decision tree and check it for completeness. That will also be needed to implement checks that no case is subsumed by a previous case, which will cause a warning (for `switch`) or error (for `match`). ### With-expressions (https://github.com/dotnet/roslyn/issues/5172) ```cs class Point(int X, int Y, int Z); ... Point p = ...; Point q = p with { Y = 2 }; ``` The latter is translated into ```cs Point q = new Point(X: p.X, Y: 2, Z: p.Z); ``` We know how to do this for record types (because the language specifies the mapping between constructor parameters and properties). We're examining how to extend it to more general types. To support inheritance, rather than directly using the constructor (as above) the generated code will invoke a compiler-generated (but user-overridable) factory method. ```cs Point q = p.With(X: p.X, Y: 2, Z: p.Z); ``` ### Draft approach for algebraic data types ```cs abstract sealed class Expression { class Binary(Operator Operator, Expression Left, Expression Right) : Expression; class Constant(int Value) : Expression; } ``` None of these classes would be permitted to be extended elsewhere. a `match` expression that handles both `Binary` and `Constant` cases would not need a `*` (default) case, as the compiler can prove it is complete. ### Remaining major issues 1. We need to specify the rules for checking - If the set of cases in a `match` is complete - If a `case` is subsumed by a previous `case` 2. We need more experience with algebraic data types and active patterns. 3. Can we extend `with` expressions to non-record types? ================================================ FILE: meetings/2015/LDM-2015-11-02-Design-Demo.md ================================================ Here’s an outline of a demo at the MVP summit on 2015-11-02 =========================================================== Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/6505. ### Let’s talk about local functions. A method often has other private “helper” methods that are used in its implementation. Those methods are in the scope of the enclosing type, even though they are only intended to be used in a single place. Local functions allow you to define a function where it is used. For example, given a helper method static int Fib(int n) { return (n < 2) ? 1 : Fib(n - 1) + Fib(n - 2); } Or, using the new syntax added in C# 6: static int Fib(int n) => (n < 2) ? 1 : Fib(n - 1) + Fib(n - 2); And the method that it is used in static void Main(string[] args) { Console.WriteLine(Fib(7)); Console.ReadKey(); } In C# 7 you’ll be able to define the helper function in the scope where it is used: static void Main(string[] args) { int Fib(int n) => (n < 2) ? 1 : Fib(n - 1) + Fib(n - 2); //! Console.WriteLine(Fib(7)); Console.ReadKey(); } Local functions can use variables from the enclosing scope: static void Main(string[] args) { int fib0 = 1; //! int Fib(int n) => (n < 2) ? fib0 : Fib(n - 1) + Fib(n - 2); Console.WriteLine(Fib(7)); Console.ReadKey(); } You can imagine having to pass such state as additional parameters to a helper method if it were declared in the enclosing type, but local function can use local variables directly. Capturing state like this does not require allocating frame objects on the heap as it would for delegates, or allocating a delegate object either, so this is much more efficient than what you would have to do to simulate this feature by hand. ### Let’s talk about pattern matching. With object-oriented programming, you define a virtual method when you have to dispatch an operation on the particular kind of object. That works best when the author of the types can identify ahead of time all of the operations (virtual methods) on the types, but it enables you to have an open-ended set of types. In the functional style, on the other hand, you define your data as a set of types without virtual functions, and define the functions separately from the data. Each operation provides an implementation for each type in the type hierarchy. That works best when the author of the types can identify ahead of time all of the shapes of the data, but it enables you to have an open-ended set of operations. C# does a great job for the object-oriented style, but the functional style (where you cannot identify all the operations ahead of time) shows up as a frequent source of awkwardness in C# programs. Let’s get really concrete. Suppose I have a small hierarchy of types // class Person(string Name); class Person { public Person(string name) { this.Name = name; } public string Name { get; } } // class Student(string Name, double Gpa) : Person(Name); class Student : Person { public Student(string name, double gpa) : base(name) { this.Gpa = gpa; } public double Gpa { get; } } // class Teacher(string Name, string Subject) : Person(Name); class Teacher : Person { public Teacher(string name, string subject) : base(name) { this.Subject = subject; } public string Subject { get; } } The comments, by the way, shows a possible future syntax we are considering for C# 7 that we call records. We’re still working on records, so I won’t say more about that today. Here is an operation that uses these types static string PrintedForm(Person p) { Student s; Teacher t; if ((s = p as Student) != null && s.Gpa > 3.5) { return $"Honor Student {s.Name} ({s.Gpa})"; } else if (s != null) { return $"Student {s.Name} ({s.Gpa})"; } else if ((t = p as Teacher) != null) { return $"Teacher {t.Name} of {t.Subject}"; } else { return $"Person {p.Name}"; } } And for the purposes of the demo, a client of that operation static void Main(string[] args) { Person[] oa = { new Student("Einstein", 4.0), new Student("Elvis", 3.0), new Student("Poindexter", 3.2), new Teacher("Feynmann", "Physics"), new Person("Anders"), }; foreach (var o in oa) { Console.WriteLine(PrintedForm(o)); } Console.ReadKey(); } Note the need to declare the variables `s` and `t` ahead of time in `PrintedForm`. Even though they are only used in one branch of the series of if-then-else statements, they are in scope throughout. That means that you have to think up distinct names for all of these temporary variables. As part of the pattern-matching feature we are repurposing the “is” operator to take a pattern on the right-hand-side. And one kind of pattern is a variable declaration. That allows us to simplify the code like this static string PrintedForm(Person p) { if (p is Student s && s.Gpa > 3.5) //! { return $"Honor Student {s.Name} ({s.Gpa})"; } else if (p is Student s) { return $"Student {s.Name} ({s.Gpa})"; } else if (p is Teacher t) { return $"Teacher {t.Name} of {t.Subject}"; } else { return $"Person {p.Name}"; } } Now the temporary variables `s` and `t` are declared and scoped to just the place they need to be. Unfortunately we’re testing against the type `Student` more than once. Back to that in a moment. We’ve also repurposed the `switch` statement so that the case branches are patterns instead of just constants (though constants are one kind of pattern). That enables you to use `switch` as a "type switch": static string PrintedForm(Person p) { switch (p) //! { case Student s when s.Gpa > 3.5 : return $"Honor Student {s.Name} ({s.Gpa})"; case Student s : return $"Student {s.Name} ({s.Gpa})"; case Teacher t : return $"Teacher {t.Name} of {t.Subject}"; default : return $"Person {p.Name}"; } } The compiler is careful so that we don’t type-test against `Student` more than once in the generated code for `switch`. Note the new `when` clause in the switch statement. We’re also working on an expression equivalent to the switch statement, which is like a multi-branch `?:` operator for pattern matching: static string PrintedForm(Person p) { return p match ( //! case Student s when s.Gpa > 3.5 : $"Honor Student {s.Name} ({s.Gpa})" case Student s : $"Student {s.Name} ({s.Gpa})" case Teacher t : $"Teacher {t.Name} of {t.Subject}" case * : $"Person {p.Name}" ); } Because you sometimes need to throw an exception when some condition is unexpected, we’re adding a *throw expression* that you can use in a match expression: return p match ( case Student s when s.Gpa > 3.5 : $"Honor Student {s.Name} ({s.Gpa})" case Student s : $"Student {s.Name} ({s.Gpa})" case Teacher t : $"Teacher {t.Name} of {t.Subject}" case null : throw new ArgumentNullException(nameof(p)) //! case * : $"Person {p.Name}" ); Another useful kind of pattern allows you to match on members of a type: return p match ( case Student s when s.Gpa > 3.5 : $"Honor Student {s.Name} ({s.Gpa})" case Student { Name is "Poindexter" } : //! "A Nerd" case Student s : $"Student {s.Name} ({s.Gpa})" case Teacher t : $"Teacher {t.Name} of {t.Subject}" case null : throw new ArgumentNullException(nameof(p)) case * : $"Person {p.Name}" ); Since this is an expression, we can use the new “=>” form of a method. Our final method is static string PrintedForm(Person p) => p match ( case Student s when s.Gpa > 3.5 : $"Honor Student {s.Name} ({s.Gpa})" case Student { Name is "Poindexter" } : "A Nerd" case Student s : $"Student {s.Name} ({s.Gpa})" case Teacher t : $"Teacher {t.Name} of {t.Subject}" case null : throw new ArgumentNullException(nameof(p)) case * : $"Person {p.Name}" ); In summary: - Local functions (capturing state is cheap) - Pattern-matching - Operator is - Switch, “when” clauses - `match` expression - Patterns - Constant patterns e.g. `1` in an ordinary switch - type-match patterns e.g. `Student s` - property patterns e.g. `Student { Name is "Poindexter" }` - wildcard e.g. `*` - Still working on - Records, algebraic data types - Tuples ================================================ FILE: meetings/2015/README.md ================================================ # C# Language Design Notes for 2015 Overview of meetings and agendas for 2015 ## Jan 21, 2015 [C# Design Meeting Notes for Jan 21, 2015](LDM-2015-01-21.md) This is the first design meeting for the version of C# coming after C# 6. We shall colloquially refer to it as C# 7. The meeting focused on setting the stage for the design process and homing in on major themes and features. 1. Design process 2. Themes 3. Features ## Jan 28, 2015 [C# Design Meeting Notes for Jan 28, 2015](LDM-2015-01-28.md) 1. Immutable types 2. Safe fixed-size buffers 3. Pattern matching 4. Records ## Feb 4, 2015 [C# Design Meeting Notes for Feb 4, 2015](LDM-2015-02-04.md) 1. Internal Implementation Only (C# 6 investigation) 2. Tuples, records and deconstruction 3. Classes with value semantics ## Feb 11, 2015 [C# Design Meeting Notes for Feb 11, 2015](LDM-2015-02-11.md) 1. Destructible types <*we recognize the problem but do not think this is quite the right solution for C#*> 2. Tuples <*we like the proposal, but there are several things to iron out*> ## Mar 4, 2015 [C# Design Meeting Notes for Mar 4, 2015](LDM-2015-03-04.md) 1. `InternalImplementationOnly` attribute <*no*> 2. Should `var x = nameof(x)` work? <*no*> 3. Record types with serialization, data binding, etc. <*keep thinking*> 4. "If I had a [billion dollars](http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare)...": nullability <*daunting but worth pursuing*> ## Mar 10 and 17, 2015 [C# Design Meeting Notes for Mar 10 and 17, 2015](LDM-2015-03-10-17.md) These two meetings looked exclusively at nullable/non-nullable reference types. I've written them up together to add more of the clarity of insight we had when the meetings were over, rather than represent the circuitous path we took to get there. 1. Nullable and non-nullable reference types 2. Opt-in diagnostics 3. Representation 4. Potentially useful rules 5. Safely dereferencing nullable reference types 6. Generating null checks ## Mar 18, 2015 [C# Design Meeting Notes for Mar 18, 2015](LDM-2015-03-18.md) In this meeting we looked over the top [C# language feature requests on UserVoice](http://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c) to see which ones are reasonable to push on further in C# 7. 1. Non-nullable reference types (*already working on them*) 2. Non-nullary constructor constraints (*require CLR support*) 3. Support for INotifyPropertyChanged (*too specific; metaprogramming?*) 4. GPU and DirectX support (*mostly library work; numeric constraints?*) 5. Extension properties and static members (*certainly interesting*) 6. More code analysis (*this is what Roslyn analyzers are for*) 7. Extension methods in instance members (*fair request, small*) 8. XML comments (*Not a language request*) 9. Unmanaged constraint (*requires CLR support*) 10. Compilable strings (*this is what nameof is for*) 11. Multiple returns (*working on it, via tuples*) 12. ISupportInitialize (*too specific; hooks on object initializers?*) 13. ToNullable (*potentially part of nullability support*) 14. Statement lambdas in expression trees (*fair request, big feature!*) 15. Language support for Lists, Dictionaries and Tuples (*Fair; already working on tuples*) A number of these are already on the table. ## Mar 24, 2015 [C# Design Meeting Notes for Mar 24, 2015](LDM-2015-03-24.md) In this meeting we went through a number of the performance and reliability features we have discussed, to get a better reading on which ones have legs. They end up falling roughly into three categories: * Green: interesting - let's keep looking * Yellow: there's something there but this is not it * Red: probably not As follows: 1. ref returns and locals <*green*> (#118) 2. readonly locals and parameters <*green*> (#115) 3. Method contracts <*green*> (#119) 4. Does not return <*green*> (#1226) 5. Slicing <*green*> (#120) 6. Lambda capture lists <*yellow - maybe attributes on lambdas*> (#117) 7. Immutable types <*yellow in current form, but warrants more discussion*> (#159) 8. Destructible types <*yellow - fixing deterministic disposal is interesting*> (#161) 9. Move <*red*> (#160) 10. Exception contracts <*red*> 11. Static delegates <*red*> 12. Safe fixed-size buffers in structs <*red*> (#126) Some of these were discussed again, some we just reiterated our position. ## Mar 25, 2015 (Design Review) [C# Language Design Review, Mar 25, 2015](LDM-2015-03-25-Design-Review.md) [Additional Notes](LDM-2015-03-25-Notes.md) We've recently changed gears a little on the C# design team. In order to keep a high design velocity, part of the design team meets one or two times each week to do detailed design work. Roughly monthly the full design team gets together to review and discuss the direction. This was the first such review. 1. Overall direction 2. Nullability features 3. Performance and reliability features 4. Tuples 5. Records 6. Pattern matching ## Apr 1 and Apr 8, 2015 [C# Design Meeting Notes for Apr 1 and Apr 8, 2015](LDM-2015-04-01-08.md) Matt Warren wrote a Roslyn analyzer as a low cost way to experiment with nullability semantics. In these two meetings we looked at evolving versions of this analyzer, and what they imply for language design. The analyzer is [here](https://github.com/mattwar/nullaby). ## Apr 14, 2015 [C# Design Meeting Notes for Apr 14, 2015](LDM-2015-04-14.md) Bart De Smet visited from the Bing team to discuss their use of Expression Trees, and in particular the consequences of their current shortcomings. The Expression Tree API today is not able to represent all language features, and the language supports lambda conversions even for a smaller subset than that. ## Apr 15, 2015 [C# Design Meeting Notes for Apr 15, 2015](LDM-2015-04-15.md) In this meeting we looked at nullability and generics. So far we have more challenges than solutions, and while we visited some of them, we don't have an overall approach worked out yet. 1. Unconstrained generics 2. Overriding annotations 3. FirstOrDefault 4. TryGet ## Apr 22, 2015 (Design Review) [C# Language Design Review, Apr 22, 2015](LDM-2015-04-22-Design-Review.md) 1. Expression tree extension 2. Nullable reference types 3. Facilitating wire formats 4. Bucketing ## May 20, 2015 [C# Design Meeting Notes for May 20, 2015](LDM-2015-05-20.md) 1. We discussed whether and how to add local functions to C#, with the aim of prototyping the feature in the near future. ## May 25, 2015 [C# Design Meeting Notes for May 25, 2015](LDM-2015-05-25.md) Today we went through a bunch of the proposals on GitHub and triaged them for our list of features. ## Jul 1, 2015 [C# Design Meeting Notes for Jul 1, 2015](LDM-2015-07-01.md) We are gearing up to prototype the tuple feature, and put some stakes in the ground for its initial design. This doesn't mean that the final design will be this way, and some choices are arbitrary. We merely want to get to where a prototype can be shared with a broader group of C# users, so that we can gather feedback and learn from it. ## Jul 7, 2015 [C# Design Meeting Notes for Jul 7, 2015](LDM-2015-07-07.md) With Eric Lippert from Coverity as an honored guest, we looked further at the nullability feature. 1. Adding new warnings 2. Generics and nullability ## Aug 18, 2015 [C# Design Meeting Notes for Aug 18, 2015](LDM-2015-08-18.md) A summary of the design we (roughly) landed on in #5031 was put out on GitHub as #5032, and this meeting further discussed it. 1. Array creation 2. Null checking operator 3. Generics ## Sep 1, 2015 [C# Design Meeting Notes for Sep 1, 2015](LDM-2015-09-01.md) The meeting focused on design decisions for prototypes. There's no commitment for these decisions to stick through to a final feature; on the contrary the prototypes are being made in order to learn new things and improve the design. 1. ref returns and ref locals 2. pattern matching ## Sep 2, 2015 [C# Design Meeting Notes for Sep 2, 2015](LDM-2015-09-02.md) 1. Extending nameof 2. What do branches mean 3. Supersedes ## Sep 8, 2015 [C# Design Meeting Notes for Sep 8, 2015](LDM-2015-09-08.md) 1. Check-in on some of our desirable features to see if we have further thoughts 2. Start brainstorming on async streams ## Oct 7, 2015 (Design Review) [Preparatory Notes on Records and Pattern Matching for Oct 7, 2015 Design Review](LDM-2015-10-07-Design-Review.md) ## Nov 2, 2015 (Design Demo) [Outline of demo at the Nov 2, 2015 MVP summit](LDM-2015-11-02-Design-Demo.md) ================================================ FILE: meetings/2016/LDM-2016-02-29.md ================================================ C# Language Design Notes Feb 29, 2016 ===================================== Discussion for these notes can be found at https://github.com/dotnet/roslyn/issues/9330. ## Catch up edition (deconstruction and immutable object creation) Over the past couple of months various design activities took place that weren't documented in design notes. The following is a summary of the state of design regarding positional deconstruction, with-expressions and object initializers for immutable types. # Philosophy We agree on the following design tenets: Positional deconstruction, with-expressions and object initializers are separable features, enabled by the presence of certain API patterns on types that can be expressed manually, as well as generated by other language features such as records. # API Patterns API patterns for a language feature facilitate two things: * Provide actual APIs to call _at runtime_ when the language feature is used * Inform the compiler _at compile time_ about how to generate code for the feature It turns out the biggest design challenges are around the second part. Specifically, all these API patterns turn out to need to bridge between positional and name-based expressions of the members of types. How each API pattern does that is a central question of its design. Assume the following running example: ``` c# public class Person { public string FirstName { get; } public string LastName { get; } public Person(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } } ``` In the following we'll consider extending and changing this type to expose various API patterns as we examine the individual language features. Here's an example of using the three language features: ``` c# var p = new Person { FirstName = "Mickey", LastName = "Mouse" }; // object initializer if (p is Person("Mickey", *)) // positional deconstruction { return p with { FirstName = "Minney" }; // with-expression } ``` Semantically this corresponds to something like this: ``` c# var p = new Person("Mickey", "Mouse"); // constructor call if (p.FirstName == "Mickey") // property access { return new Person("Minney", p.LastName); // constructor call } ``` Notice how the new features that use property names correspond to API calls using positional parameters, whereas the feature that uses positions corresponds to member access by name! # Object initializers for immutable objects (See e.g. #229) This feature allows an object initializer for which assignable properties are not found, to fall back to a constructor call taking the properties' new values as arguments. ``` c# new Person { FirstName = "Mickey", LastName = "Mouse" } ``` becomes ``` c# new Person("Mickey", "Mouse") ``` The question then is: how does the compiler decide to pass the given FirstName as the first argument? Somehow it needs clues from the `Person` type as to which properties correspond to which constructor parameters. These clues cannot just be the constructor body: we need this to work across assemblies, so the clues must be evident from metadata. Here are some options: 1: The type or constructor explicitly includes metadata for this purpose, e.g. in the form of attributes. 2: The names of the constructor parameters must match exactly the names of the corresponding properties. The former is unattractive because it requires the type's author to write those attributes. It requires the type to be explicitly edited for the purpose. The latter is better in that it doesn't require extra API elements. However, API design guidelines stipulate that public properties start with uppercase, and parameters start with lower case. This pattern would break that, and for the same reason is highly unlikely to apply to any existing types. This leads us to: 3: The names of the constructor parameters must match the names of the corresponding properties, _modulo case_! This would allow a large number of existing types to just work (including the example above), but at the cost of introducing case insensitivity to this part of the C# language. # With-expressions (see e.g. #5172) With-expressions are similar to object initializers, except that they provide a source object from which to copy all the properties that aren't specified. Thus it seems reasonable to use a similar strategy for compilation; to call a constructor, this time filling in missing properties by accessing those on the source object. Thus the same strategies as above would apply to establish the connection between properties and constructor parameters. ``` c# p with { FirstName = "Minney" } ``` becomes ``` c# new Person("Minney", p.LastName) ``` However, there's a hitch: if the runtime source object is actually of a derived type with more properties than are known from its static type, it would typically be expected that those are copied over too. In that case, the static type is also likely to be abstract (most base types are), so it wouldn't offer a callable constructor. For this situation there needs to be a way that an abstract base class can offer "with-ability" that correctly copies over members of derived types. The best way we can think of is to offer a virtual `With` method, as follows: ``` c# public abstract class Person { ... public abstract Person With(string firstName, string lastName); } ``` In the presence of such a `With` method we would generate a with expression to call that instead of the constructor: ``` c# p.With("Minney", p.LastName) ``` We can decide whether to make with-expressions _require_ a `With` method, or fall back to constructor calls in its absence. If we _require_ a `With` method, that makes for less interoperability with existing types. However, it gives us new opportunities for how to provide the position/name mapping metadata through the declaration of that `With` method: For instance, we could introduce a new kind of default parameter that explicitly wires the parameter to a property: ``` c# public abstract Person With(string firstName = this.FirstName, string lastName = this.LastName); ``` To explicitly facilitate interop with an existing type, a mandatory `With` method could be allowed to be provided as an extension method. It is unclear how that would work with the default parameter approach, though. # Positional deconstruction (see e.g. #206) This feature allows a positional syntax for extracting the property values from an object, for instance in the context of pattern matching, but potentially also elsewhere. Ideally, a positional deconstruction would simply generate an access of each member whose value is obtained: ``` c# p is Person("Mickey", *) ``` becomes ``` c# p.FirstName == "Mickey" ``` Again, this requires the compiler's understanding of how positions correspond to property names. Again, the same strategies as for object initializers are possible. See e.g. #8415. Additionally, just as in with-expressions, one might wish to override the default behavior, or provide it if names don't match. Again, an explicit method could be used: ``` c# public abstract class Person { ... public void Person GetValues(out string firstName, out string lastName); } ``` There are several options as to the shape of such a method. Instead of out-parameters, it might return a tuple. This has pros and cons: there could be only one tuple-returning `GetValues` method, because there would be no parameters to distinguish signatures. This may be a good or a bad thing. Just as the `With` method, we can decide whether deconstruction should _require_ a `GetValues` method, or should fall back to metadata or to name matching against the constructor's parameter names. If the `GetValues` method is used, the compiler doesn't need to resolve between positions and properties: the deconstruction as well as the method are already positional. We'd generate the code as follows: ``` c# string __p1; string __p2; p.GetValues(out __p1, out __p2); ... __p1 == "Mickey" ``` Somewhat less elegant for sure, and possibly less efficient, since the `LastName` is obtained for no reason. However, this is compiler generated code that no one has to look at, and it can probably be optimized, so this may not be a big issue. # Summary For each of these three features we are grappling with the position-to-property match. Our options: 1. Require specific metadata 2. Match property and parameter names, possibly in a case sensitive way 3. For deconstruction and with-expressions, allow or require specific methods (`GetValues` and `With` respectively) to implement their behavior, and possibly have special syntax in `With` methods to provide the name-to-position matching. We continue to work on this. ================================================ FILE: meetings/2016/LDM-2016-04-06.md ================================================ C# Design Notes for Apr 6, 2016 =============================== Discussion for these design notes can be found at https://github.com/dotnet/roslyn/issues/10429. We settled several open design questions concerning tuples and pattern matching. # Tuples ## Identity conversion Element names are immaterial to tuple conversions. Tuples with the same types in the same order are identity convertible to each other, regardless of the names. That said, if you have an element name at *one* position on one side of a conversion, and the same name at *another* position on the other side, you almost certainly have bug in your code: ``` c# (string first, string last) GetNames() { ... } (string last, string first) names = GetNames(); // Oops! ``` To catch this glaring case, we'll have a warning. In the unlikely case that you meant to do this, you can easily silence it e.g. by assigning through a tuple without names at all. ## Boxing conversion As structs, tuples naturally have a boxing conversion. Importantly, the names aren't part of the runtime representation of tuples, but are tracked only by the compiler. Thus, once you've "cast away" the names, you cannot recover them. In alignment with the identity conversions, a boxed tuple will unbox to any tuple type that has the same element types in the same order. ## Target typing A tuple literal is "target typed" whenever possible. What that means is that the tuple literal has a "conversion from expression" to any tuple type, as long as the element expressions of the tuple literal have an implicit conversion to the element types of the tuple type. ``` c# (string name, byte age) t = (null, 5); // Ok: the expressions null and 5 convert to string and byte ``` In cases where the tuple literal is not part of a conversion, it acquires its "natural type", which means a tuple type where the element types are the types of the constituent expressions. Since not all expressions have types, not all tuple literals have a natural type either: ``` c# var t = ("John", 5); // Ok: the type of t is (string, int) var t = (null, 5); // Error: null doesn't have a type ``` A tuple literal may include names, in which case they become part of the natural type: ``` c# var t = (name: "John", age: 5); // The type of t is (string name, int age) ``` ## Conversion propagation A harder question is whether tuple types should be convertible to each other based on conversions between their element types. Intuitively it seems that implicit and explicit conversions should just "bleed through" and compose to the tuples. This leads to a lot of complexity and hard questions, though. What kind of conversion is the tuple conversion? Different places in the language place different restrictions on which conversions can apply - those would have to be "pushed down" as well. ``` c# var t1 = ("John", 5); // (string, int) (object, long) t2 = t1; // What kind of conversion is this? Where is it allowed ``` On the whole we think that, while intuitive, the need for such conversions isn't actually that common. It's hard to construct an example that isn't contrived, involving for instance tuple-typed method parameters and the like. When you really need it, you can deconstruct the tuple and reconstruct it with a tuple literal, making use of target typing. We'll keep an eye on it, but for now the decision is *not* to propagate element conversions through tuple types. We do recognize that this is a decision we don't get to change our minds on once we've shipped: adding conversions in a later version would be a significant breaking change. ## Projection initializers Tuple literals are a bit like anonymous types. The latter have "projection initializers" where if you don't specify a member name, one will be extracted from the given expression, if possible. Should we do that for tuples too? ``` c# var a = new { Name = c.FirstName, c.Age }; // Will have members Name and Age var t = (Name: c.FirstName, c.Age); // (string Name, int Age) or error? ``` We don't think so. The difference is that names are optional in tuples. It'd be too easy to pick up a random name by mistake, or get errors because two elements happen to pick up the same name. ## Extension methods on tuples This should just work according to existing rules. That means that extension methods on a tuple type apply even to tuples with different element names: ``` c# static void M(this (int x, int y) t) { ... } (int a, int b) t = ...; t.M(); // Sure ``` ## Default parameters Like other types, you can use `default(T)` to specify a default parameter of tuple type. Should you also be allowed to specify a tuple literal with suitably constant elements? ``` c# void M((string, int) t = ("Bob", 7)) { ... } // Allowed? ``` No. We'd need to introduce a new attribute for this, and we don't even know if it's a useful scenario. ## Syntax for 0-tuples and 1-tuples? We lovingly refer to 0-tuples as nuples, and 1-tuples as womples. There is already an underlying `ValueTuple` of size one. We should will also have the non-generic `ValueTuple` be an empty struct rather than a static class. The question is whether nuples and womples should have syntactic representation as tuple types and literals? `()` would be a natural syntax for nuples (and would no doubt find popularity as a "unit type" alternative to `void`), but womples are harder: parenthesized expressions already have a meaning! We made no final decisions on this, but won't pursue it for now. ## Return tuple members directly in scope There is an idea to let the members of a tuple type appearing in a return position of a method be in scope throughout the method: ``` c# (string first, string last) GetName() { first = ...; last = ...; // Assign the result directly return; // No need to return an explicit value } ``` The idea here is to enhance the symmetry between tuple types and parameter lists: parameter names are in scope, why should "result names"? This is cute, but we won't do it. It is too much special casing for a specific placement of tuple types, and it is also actually preferable to be able to see exactly what is returned at a given `return` statement. # Integrating pattern matching with is-expressions and switch-statements For pattern matching to feel natural in C# it is vital that it is deeply integrated with existing related features, and does in fact take its queues from how they already work. Specifically we want to extend is-expressions to allow patterns where today they have types, and we want to augment switch-statements so that they can switch on any type, use patterns in case-clauses and add additional conditions to case-clauses using when-clauses. This integration is not always straightforward, as witnessed by the following issues. In each we need to decide what patterns should *generally* do, and mitigate any breaking changes this would cause in currently valid code. ## Name lookup The following code is legal today: ``` c# if (e is X) {...} switch(e) { case X: ... } ``` We'd like to extend both the places where `X` occurs to be patterns. However, `X` means different things in those two places. In the `is` expression it must refer to a type, whereas in the `case` clause it must refer to a constant. In the `is` expression we look it up as a type, ignoring any intervening members called `X`, whereas in the `case` clause we look it up as an expression (which can include a type), and give an error if the nearest one found is not a constant. As a pattern we think `X` should be able to both refer to a type and a constant. Thus, we prefer the `case` behavior, and would just stop giving an error when `case X:` refers to a type. For `is` expressions, to avoid a breaking change, we will first look for just a type (today's behavior), and if we don't find one, rather than error we will look again for a constant. ## Conversions in patterns An `is` expression today will only acknowledge identity, reference and boxing conversions from the run-time value to the type. It looks for "the actual" type, if you will, without representation changes: ``` c# byte b = 5; WriteLine(b is byte); // True: identity conversion WriteLine((object)b is byte); // True: boxing conversion WriteLine((object)b is object); // True: reference conversion WriteLine(b is int); // False: numeric conversion changes representation ``` This seems like the right semantics for "type testing", and we want those to carry over to pattern matching. Switch statements are more weird here today. They have a fixed set of allowed types to switch over (primitive types, their nullable equivalents, strings). If the expression given has a different type, but has a unique implicit conversion to one of the allowed ones, then that conversion is applied! This occurs mainly (only?) when there is a user defined conversion from that type to the allowed one. That of course is intended only for constant cases. It is not consistent with the behavior we want for type matching per the above, and it is also not clear how to generalize it to switch expressions of arbitrary type. It is behavior that we want to limit as much as possible. Our solution is that in switches *only* we will apply such a conversion on the incoming value *only* if all the cases are constant. This means that if you add a non-constant case to such a switch (e.g. a type pattern), you will break it. We considered more lenient models involving applying non-constant patterns to the *non*-converted input, but that just leads to weirdness, and we don't think it's necessary. If you *really* want your conversion applied, you can always explicitly apply it to the switch expression yourself. ## Pattern variables and multiple case labels C# allows multiple case labels on the same body. If patterns in those case labels introduce variables, what does that mean? ``` c# case int i: case byte b: WriteLine("Integral value!"); break; ``` Here's what it means: The variables go into the same declaration space, so it is an error to introduce two of the same name in case clauses for the same body. Furthermore, the variables introduced are not definitely assigned, because the given case clause assigns them, and you didn't necessarily come in that way. So the above example is legal, but the body cannot read from the variables `i` and `b` because they are not definitely assigned. It is tempting to consider allowing case clauses to share variables, so that they could be extracted from similar but different patterns: ``` c# case (int age, string name): case (string name, int age): WriteLine($"{name} is {age} years old."); break; ``` We think that is way overboard right now, but the rules above preserve our ability to allow it in the future. ## Goto case It is tempting to ponder generalizations of `goto case x`. For instance, maybe you could do the whole switch again, but on the value `x`. That's interesting, but comes with lots of complications and hidden performance traps. Also it is probably not all that useful. Instead we just need to preserve the simple meaning of `goto case x` from current switches: it's allowed if `x` is constant, if there's a case with the same constant, and that case doesn't have a `when` clause. ## Errors and warnings Today `3 is string` yields a warning, while `3 as string` yields and error. They philosophy seems to be that the former is just asking a question, whereas the other is requesting a value. Generalized `is` expressions like `3 is string s` are sort of a combination of `is` and `as`, both answering the question and (conditionally) producing a value. Should they yield warnings or errors? We didn't reach consensus and decided to table this for later. ## Constant pattern equality In today's `switch` statement, the constants in labels must be implicitly convertible to the governing type (of the switch expression). The equality is then straightforward - it works the same as the `==` operator. This means that the following case will print `Match!`. ``` c# switch(7) { case (byte)7: WriteLine("Match!"); break; } ``` What should be the case if we switch on something of type object instead?: ``` c# switch((object)7) { case (byte)7: WriteLine("Match!"); break; } ``` One philosophy says that it should work the same way regardless of the static type of the expression. But do we want constant patterns everywhere to do "intelligent matching" of integral types with each other? That certainly leads to more complex runtime behavior, and would probably require calling helper methods. And what of other related types, such as `float` and `double`? There isn't similar intelligent behavior you can do, because the representations of most numbers will differ slightly and a number such as 2.1 would thus not be "equal to itself" across types anyway. The other option is to make the behavior different depending on the compile-time type of the expression. We'll use integral equality only if we know statically which one to pick, because the left hand side was also known to be integral. That would preserve the switch behavior, but make the pattern's behavior dependent on the static type of the expression. For now we prefer the latter, as it is simpler. # Recursive patterns There are several core design questions around the various kinds of recursive patterns we are envisioning. However, they seem to fall in roughly two categories: 1. Determine the syntactic shape of each recursive pattern in itself, and use that to ensure that the places where patterns can occur are syntactically well-formed and unambiguous. 2. Decide exactly how the patterns work, and what underlying mechanisms enable them. This is an area to focus more on in the future. For now we're just starting to dig in. ## Recursive pattern syntax For now we envision three shapes of recursive patterns 1. Property patterns: `Type { Identifier_1 is Pattern_1, ... , Identifier_n is Pattern_n }` 2. Tuple patterns: `(Pattern_1, ... Pattern_n)` 3. Positional patterns: `Type (Pattern_1, ... Pattern_n)` There's certainly room for evolution here. For instance, it is not lost on us that 2 and 3 are identical except for the presence of a type in the latter. At the same time, the presence of a type in the latter seems syntactically superfluous in the cases where the matched expression is already known to be of that type (so the pattern is used purely for deconstruction and/or recursive matching of the elements). Those two observations come together to suggest a more orthogonal model, where the types are optional: 1. Property patterns: `Type_opt { Identifier_1 is Pattern_1, ... , Identifier_n is Pattern_n }` 2. Positional patterns: `Type_opt (Pattern_1, ... Pattern_n)` In this model, what was called "tuple patterns" above would actually not just apply to tuples, but to anything whose static type (somehow) specifies a suitable deconstruction. This is important because it means that "irrefutable" patterns - ones that are known to always match - never need to specify the type. This in turn means that they can be used for unconditional deconstruction even in syntactic contexts where positional patterns would be ambiguous with invocation syntax. For instance, we could have what would amount to a "deconstruction" variant of a declaration statement, that would introduce all its match variables into scope as local variables for subsequent statements: ``` c# (string name, int age) = GetPerson(); // Unconditional deconstruction WriteLine($"{name} is {age} years old"); // name and age are in scope ``` ## How recursive patterns work Property patterns are pretty straightforward - they translate into access of fields and properties. Tuple patterns are also straightforward if we decide to handle them specially. Positional patterns are more complex. We agree that they need a way to be specified, but the scope and mechanism for this is still up for debate. For instance, the `Type` in the positional pattern may not necessarily trigger a type test on the object. Instead it may name a class where a more general "matcher" is defined, which does its own tests on the object. This could be complex stuff, like picking a string apart to see if it matches a certain format, and extracting certain information from it if so. The syntax for declaring such "matchers" may be methods, or a new kind of user defined operator (like `is`) or something else entirely. We still do not have consensus on either the scope or shape of this, so there's some work ahead of us. The good news is that we can add pattern matching in several phases. There can be a version of C# that has pattern matching with none or only some of the recursive patterns working, as long as we make sure to "hold a place for them" in the way we design the places where patterns can occur. So C# 7 can have great pattern matching even if it doesn't yet have *all* of it. ================================================ FILE: meetings/2016/LDM-2016-04-12-22.md ================================================ # C# Design Notes for Apr 12-22, 2016 These notes summarize discussions across a series of design meetings in April on several topics related to tuples and patterns: - Tuple syntax for non-tuple types - Tuple deconstruction - Tuple conversions - Deconstruction and patterns - Out vars and their scope There's still much more to do here, but lots of progress. # Tuple syntax for other types We are introducing a new family of `System.ValueTuple<...>` types to support tuples in C#. However, there are already types that are tuple-like, such as `System.Tuple<...>` and `KeyValuePair<...>`. Not only are these often used throughout C# code, but the former is also what's targeted by F#'s tuple mechanism, which we'd want our tuple feature to interoperate well with. Additionally you can imagine allowing other types to benefit from some, or all, of the new tuple syntax. The obvious kinds of interop would be tuple construction and deconstruction. Since tuple literals are target typed, consider a tuple literal that is assigned to another tuple-like type: ``` c# System.Tuple t = (5, null); ``` We could think of this as calling `System.Tuple`'s constructor, or as a conversion, or maybe something else. Similarly, tuples will allow deconstruction and "decorated" names tracked by the compiler. Could we allow those on other types as well? There are several levels of support we could decide land on: 1. Only tuples are tuples. Other types are on their own. 2. Specific well-known tuple-like things are also tuples (probably `Tuple<...>` and `KeyValuePair<...>`). 3. An author of a type can make it tuple-like through certain API patterns 4. I can make anything work with tuple syntax without being the author of it 5. All types just work with it Level 2 would be enough to give us F# interop and improve the experience with existing APIs using `Tuple<...>` and `KeyValuePair<...>`. Option 3 could rely on any kind of declarations in the type, whereas option 4 would limit that to instance-method patterns that someone else could add through extension methods. It is hard to see how option 5 could work for deconstruction, but it might work for construction, simply by treating a tuple literal as an argument list to the type's constructor. One might consider it invasive that a type's constructor can be called without a `new` keyword or any mention of the type! On the other hand this might also be seen as a really nifty abbreviation. One problem would be how to use it with constructors with zero or one argument. So far we haven't opted to add syntax for 0-tuples and 1-tuples. We haven't yet decided which level we want to target, except we want to at least make `Tuple<...>` and `KeyValuePair<...>` work with tuple syntax. Whether we want to go further is a decision we probably cannot put off for a later version, since a later addition of capabilities might clash with user-defined conversions. # Tuple deconstruction Whether deconstruction works for other types or not, we at least want to do it for tuples. There are three contexts in which we consider tuple deconstruction: 1. **Assignment:** Assign a tuple's element values into existing variables 2. **Declaration:** Declare fresh variables and initialize them with a tuple's element values 3. **Pattern matching:** Recursively apply patterns to each of a tuple's element values We would like to add forms of all three. ## Deconstructing assignments It should be possible to assign to existing variables, to fields and properties, array elements etc., the individual values of a tuple: ``` c# (x, y) = currentFrame.Crop(x, y); // x and y are existing variables (a[x], a[x+1], a[x+2]) = GetCoordinates(); ``` We need to be careful with the evaluation order. For instance, a swap should work just fine: ``` c# (a, b) = (b, a); // Yay! ``` A core question is whether this is a new syntactic form, or just a variation of assignment expressions. The latter is attractive for uniformity reasons, but it does raise some questions. Normally, the type and value of an assignment expression is that of its left hand side after assignment. But in these cases, the left hand side has multiple values and types. Should we construct a tuple from those and yield that? That seems contrary to this being about _deconstructing_ not _constructing_ tuples! This is something we need to ponder further. As a fallback we can say that this is a new form of assignment _statement_, which doesn't produce a value. ## Deconstructing declarations In most of the places where local variables can be introduced and initialized, we'd like to allow deconstructing declarations - where multiple variables are declared, but assigned collectively from a single tuple (or tuple-like value): ``` c# (var x, var y) = GetCoordinates(); // locals in declaration statements foreach ((var x, var y) in coordinateList) ... // iteration variables in foreach loops from (x, y) in coordinateList ... // range variables in queries M(out (var x, var y)); // tuple out parameters ``` For range variables in queries, this would depend on clever use of transparent identifiers over tuples. For out parameters this may require some form of post-call assignment, like VB has for properties passed to out parameters. That may or may not be worth it. For syntax, there are two general approaches: "Types-with-variables" or "types-apart". ``` c# // Types-with-variables: (string first, string last) = GetName(); // Types specified (var first, var last) = GetName(); // Types inferred var (first, last) = GetName(); // Optional shorthand for all var // Types-apart: (string, string) (first, last) = GetName(); // Types specified var (first, last) = GetName(); // All types inferred (var, var) (first, last) = GetName(); // Optional long hand for types inferred ``` This is mostly a matter of intuition and taste. For now we've opted for the types-with-variables approach, allowing the single `var` shorthand. One benefit is that this looks more similar to what we envision deconstruction in tuples to look like. Feedback may change our mind on this. Multiple variables won't make sense everywhere. For instance they don't seem appropriate or useful in using-statements. We'll work through the various declaration contexts one by one. ## Other deconstruction questions Should it be possible to deconstruct a longer tuple into fewer variables, discarding the rest? As a starting point, we don't think so, until we see scenarios for it. Should we allow optional tuple member names on the left hand side of a deconstruction? If you put them in, it would be checked that the corresponding tuple element had the name you expected: ``` c# (x: a, y: b) = GetCoordinates(); // Error if names in return tuple aren't x and y ``` This may be useful or confusing. It is also something that can be added later. We made no immediate decision on it. # Tuple conversions Viewed as generic structs, tuple types aren't inherently covariant. Moreover, struct covariance is not supported by the CLR. And yet it seems entirely reasonable and safe that tuple values be allowed to be assigned to more accommodating tuple types: ``` c# (byte, short) t1 = (1, 2); (int, int t2) = t1; // Why not? ``` The intuition is that tuple conversion should be thought of in a _pointwise_ manner. A tuple type is convertible (in a given manner) to another if each of their element types are pairwise convertible (in the same manner) to each other. However, if we are to allow this we need to build it into the language specifically - sometimes implementing tuple assignment as assigning the elements one-by-one, when the CLR doesn't allow the wholesale assignment of the tuple value. In essence we'd be looking at a situation similar to when we introduced nullable value types in C# 2. Those are implemented in terms of generic structs, but the language adds extensive special semantics to these generic structs, allowing operations - including covariant conversions - that do not automatically fall out from the underlying representation. This language-level relaxation comes with some subtle breaks that can happen on upgrade. Consider the following code, where C.dll is a C# 6 consumer of C# 7 libraries A.dll and B.dll: ``` c# A.dll: (int, long) Foo()... // ValueTuple Foo(); B.dll: void Bar(object o) void Bar((int?, long?) t) C.dll: Bar(Foo()); ``` Because C# 6 has no knowledge of tuple conversions, it would pick the first overload of `Bar` for the call. However, when the owner of C.dll upgrades to C# 7, relaxed tuple conversion rules would make the second overload applicable, and a better pick. It is important to note that such breaks are esoteric. Exactly parallel examples could be constructed for when nullable value types were introduced; yet we never saw them in practice. Should they occur they are easy to work around. As long as the underlying type (in our case `ValueTuple<...>`) and the conversion rules are introduced at the same time, the risk of programmers getting them mixed in a dangerous manner is minimal. Another concern is that "pointwise" tuple conversions, just like nullable value types, are a pervasive change to the language, that affects many parts of the spec and implementation. Is it worth the trouble? After all it is pretty hard to come up with compelling examples where conversions between two tuple _types_ (as opposed to _from_ tuple literals or _to_ individual variables in a deconstruction) is needed. We feel that tuple conversions are an important part of the intuition around tuples, that they are primarily "groups of values" rather than "values in and of themselves". It would be highly surprising to developers if these conversions didn't work. Consider the baffling difference between these two pieces of code if tuple conversions didn't work: ``` c# (long, long) tuple = (1, 1); var tmp = (1, 1); (long, long) tuple = tmp; // Doesn't work??!? ``` All in all we feel that pointwise tuple conversions are worth the effort. Furthermore it is crucial that they be added at the same time as the tuples themselves. We cannot add them later without significant breaking changes. ## Tuples vs ValueTuple In accordance with this philosophy we cannot say more about the relationship between language level tuple types and the underlying `ValueTuple<...>` types. Just like `Nullable` is equivalent to `T?`, so `ValueTuple` should be in every way equivalent to the unnamed `(T1, T2, T3)`. That means the pointwise conversions also work when tuple types are specified using the generic syntax. If the tuple is bigger than the limit of 7, the implementation will nest the "tail" as a tuple into the eighth element recursively. This nesting is visible by accessing the `Rest` field of a tuple, but that field is considered an implementation detail, and is hidden from e.g. auto-completion, just as the ItemX field names are hidden but allowed when a tuple has named elements. A well formed "big tuple" will have names `Item1` etc. all the way up to the number of tuple elements, even though the underlying type doesn't physically have those fields directly defined. The same goes for the tuple returned from the `Rest` field, only with the numbers "shifted" appropriately. All this says is that the tuple in the `Rest` field is treated the same as all other tuples. # Deconstructors and patterns Whether or not we allow arbitrary values to opt in to the unconditional tuple deconstruction described above, we know we want to enable such positional deconstruction in recursive patterns: ``` c# if (o is Person("George", var last)) ... ``` The question is: how exactly does a type like `Person` specify how to be positionally deconstructed in such cases? There are a number of dimensions to this question, along with a number of options for each: - Static or instance/extension member? - `GetValues` method or new `is` operator? - Return tuple or tuple out parameter or several out parameters? Selecting between these, we have to observe a number of different tradeoffs: 1. Overloadable or not? 2. Yields tuple or individual values? 3. Growth path to "active patterns"? 4. Can be applied to existing types without modifying them? Let's look at these in turn. ## Overloadability If the multiple extracted values are returned as a tuple from a method (whether static or instance) then that method cannot be overloaded. ``` c# public (string firstName, string lastName) GetValues() { ... } ``` The deconstructor is essentially canonical. That may not be a big deal from a usability perspective, but it does hamper the evolution of the type. If it ever adds another member and wants to enable access to it through deconstruction, it needs to _replace_ the deconstructor, it cannot just add a new overload. This seems unfortunate. A method that yields it results through one or more out parameters can be overloaded in C#. Also, for a new kind of user defined operator we can decide the overloading rule whichever way we like. For instance, conversion operators today can be overloaded on return type. ## Tuple or individual values If a deconstructor yields a tuple, then that confers special status to tuples for deconstruction. Essentially tuples would have their own built-in deconstruction mechanism, and all other types would defer to those by supplying a tuple. Even if we rely on multiple out parameters, tuples cannot just use the same mechanism. In order to do so, long tuples would need to be enhanced by the compiler with an implementation that hides the nested nature of such tuples. There doesn't seem to be any strong benefit to yielding a single tuple over multiple values (in out parameters). ## Growing up to active patterns There's a proposal where one type gets to specify deconstruction semantics for another, along even with logic to determine whether the pattern applies or not. We do not plan to support that in the first go-around, but it is worth considering whether the deconstruction mechanism lends itself to such an extension. In order to do so it would need to be static (so that it can specify behavior for an object of _another_ type), and would benefit from an out-parameter-based approach, so that the return position could be reserved for returning a boolean when the pattern is conditional. There is a lot of speculation involved in making such concessions now, and we could reasonably rely on our future selves to invent a separate specification mechanism for active patterns without us having to accommodate it now. ## Conclusion This was an exploration of the design space. The actual decision is left to a future meeting. # Out vars and their scope We are in favor of reviving a restricted version of the declaration expressions that were considered for C# 6. This would allow methods following the TryFoo pattern to behave similarly to the new pattern-based is-expressions in conditions: ``` c# if (int.TryParse(s, out var i) && i > 0) ... ``` We call these "out vars", even though they are perfectly fine to specify a type. The scope rules for variables introduced in such contexts would be the same as variables coming from a pattern: they generally be in scope within all of the nearest enclosing statement, except when that is an if-statement, where they would not be in scope in the else-branch. On top of that there are some relatively esoteric positions we need to decide on. If an out var occurs in a _field initializer_, where should it be in scope? Just within the declarator where it occurs, not even in subsequent declarators of the same field declaration. If an out var occurs in a _constructor initializer_ (`this(...)` or `base(...)`) where should it be in scope? Let's not even allow that - there's no way you could have written equivalent code yourself. ================================================ FILE: meetings/2016/LDM-2016-05-03-04.md ================================================ # C# Design Notes for May 3-4, 2016 This pair of meetings further explored the space around tuple syntax, pattern matching and deconstruction. 1. Deconstructors - how to specify them 2. Switch conversions - how to deal with them 3. Tuple conversions - how to do them 4. Tuple-like types - how to construct them Lots of concrete decisions, that allow us to make progress on implementation. # Deconstructors In [#11031](https://github.com/dotnet/roslyn/issues/11031) we discussed the different contexts in which deconstruction should be able to occur, namely deconstructing _assignment_ (into existing variables), _declaration_ (into freshly declared local variables) and _patterns_ (as part of applying a recursive pattern). We also explored the design space of how exactly "deconstructability" should be specified for a given type, but left the decision open - until now. Here's what we decided - and why. We'll stick to these decisions in initial prototypes, but as always are willing to be swayed by evidence as we roll them out and get usage. **_Deconstruction should be specified with an instance (or extension) method**_. This is in keeping with other API patterns added throughout the history of C#, such as `GetEnumerator`, `Add`, and `GetAwaiter`. The benefit is that this leads to a relatively natural kind of member to have, and it can be specified with an extension method so that existing types can be augmented to be deconstructable outside of their own code. The choice limits the ability of the pattern to later grow up to facilitate "active patterns". We aren't too concerned about that, because if we want to add active patterns at a later date we can easily come up with a separate mechanism for specifying those. **_The instance/extension method should be called `Deconstruct`**_. We've been informally calling it `GetValues` for a while, but that name suffers from being in too popular use already, and not always for a similar purpose. This is a decision we're willing to alter if a better name comes along, and is sufficiently unencumbered. **_The Deconstruct method "returns" the component values by use of individual out parameters**_. This choice may seem odd: after all we're adding a perfectly great feature called tuples, just so that you can return multiple values! The motivation here is primarily that we want `Deconstruct` to be overloadable. Sometimes there are genuinely multiple ways to deconstruct, and sometimes the type evolves over time to add more properties, and as you extend the `Deconstruct` method you also want to leave an old overload available for source and binary compat. This one does nag us a little, because the declaration form with tuples is so much simpler, and would be sufficient in a majority of cases. On the other hand, this allows us to declare decomposition logic _for_ tuples the same way as for other types, which we couldn't if we depended on tuples for it! Should this become a major nuisance (we don't think so) one could consider a hybrid approach where both tuple-returning and out-parameter versions were recognized, but for now we won't. All in all, the deconstructor pattern looks like one of these: ``` c# class Name { public void Deconstruct(out string first, out string last) { first = First; last = Last; } ... } // or static class Extensions { public static void Deconstruct(this Name name, out string first, out string last) { first = name.First; last = name.Last; } } ``` # Switch conversions Switch statements today have a wrinkle where they will apply a unique implicit conversion from the switched-on expression to a (currently) switchable type. As we expand to allow switching on any type, this may be confusing at times, but we need to keep it at least in some scenarios, for backwards compatibility. ``` c# switch (expr) // of some type Expression, which "cleverly" has a user defined conversion to int for evaluation { case Constant(int i): ... // Won't work, though Constant derives from Expression, because expr has been converted to int ... } ``` Our current stance is that this is fringe enough for us to ignore. If you run into such a conversion and didn't want it, you'll have to work around it, e.g. by casting your switch expression to object. If this turns out to be more of a nuisance we may have to come up with a smarter rule, but for now we're good with this. # Tuple conversions In #11031 we decided to add tuple conversions, that essentially convert tuples whenever their elements convert - unlike the more restrictive conversions that follow from `ValueTuple<...>` being a generic struct. In this we view nullable value types as a great example of how to imbue a language-embraces special type with more permissive conversion semantics. As a guiding principle, we would like tuple conversions to apply whenever a tuple can be deconstructed and reassembled into the new tuple type: ``` c# (string, byte) t1 = ...; (object, int) t2 = t1; // Allowed, because the following is: (var a, var b) = t1; // Deconstruct, and ... (object, int) t2 = (a, b); // reassemble ``` One problem is that nullable value type conversions are rather complex. They affect many parts of the language. It'd be great if we could make tuple conversions simpler. There are two principles we can try to follow: 1. A tuple conversion is a specific _kind_ of conversion, and it allows specific _kinds_ of conversions on the elements 2. A tuple conversion works in a given setting if all of its element conversions would work in that setting The latter is more general, more complex and possibly ultimately necessary. However, somewhat to our surprise, we found a definition along the former principle that we cannot immediately poke a hole in: > An _implicit tuple conversion_ is a standard conversion. It applies between two tuple types of equal arity when there is _any_ implicit conversion between each corresponding pair of types. (Similarly for explicit conversions). The interesting part here is that it's a standard conversion, so it is able to be composed with user defined conversions. Yet, its elements are allowed to perform their own user defined conversions! It feels like something could go wrong here, with recursive or circular application of user defined conversions, but we haven't been able to pinpoint an example. A definition like this would be very desirable, because it won't require so much special casing around the spec. We will try to implement this and see if we run into problems. # Tuple-like construction of non-tuple types We previously discussed to what extent non-tuple types should benefit from the tuple syntax. We've already decided that the deconstruction syntax applies to any type with a deconstructor, not just tuples. So what about construction? The problem with allowing tuple literal syntax to construct any type is that _all_ types have constructors! There's no opt-in. This seems too out of control. Furthermore, it doesn't look intuitive that any old type can be "constructed" with a tuple literal: ``` c# Dictionary d = (16, EqualityComparer.Default); / Huh??? ``` This only seems meaningful if the constructor arguments coming in through a "tuple literal" are actually the constituent data of the object being created. Finally, we don't have syntax for 0 and 1-tuples, so unless we add that, this would only even work when there's more than one constructor argument to the target type. All in all, we don't think tuple literals should work for any types other than the built-in tuples. Instead, we want to brush off a feature that we've looked at before; the ability to omit the type from an object creation expression, when there is a target type: ``` c# Point p = new (3, 4); // Same as new Point(3, 4) List l1 = new (10); // Works for 0 or 1 argument List l2 = new (){ 3, 4, 5 }; // Works with object/collection initializers, but must have parens as well. ``` Syntactically we would say that an object creation expression can omit the type when it has a parenthesized argument list. In the case of object and collection initializers, you cannot omit both the type and the parenthesized argument list, since that would lead to ambiguity with anonymous objects. We think that this is promising. It is generally useful, and it would work nicely in the case of existing tuple-like types such as `System.Tuple<...>` and `KeyValuePair<...>`. ================================================ FILE: meetings/2016/LDM-2016-05-10.md ================================================ # C# Language Design Notes for May 10, 2016 In this meeting we took a look at the possibility of adding new kinds of extension members, beyond extension methods. ## Extension members Ever since we added extension methods in C# 3.0, there's been a consistent ask to add a similar feature for other kinds of function members. While "instance methods" are clearly the most important member kind to "extend with" from the outside, it seems arbitrary and limiting not to have e.g. properties, constructors or static members. Unfortunately, the declaration syntax for extension methods is sort of a local optimum. The static method with the extra `this` parameter works great for methods: It is low cost, and it comes with an obvious disambiguation syntax. However, this approach doesn't carry over well to other kinds of members. A property with an extra `this` parameter? And how would extension static members attach to their type - cannot be through a `this` parameter representing an instance! As we were designing C# 4 we tried very hard to answer these questions and design an "extension everything" feature that was a syntactic extension (no pun intended) of extension methods. We failed, creating a succession of monsters until we gave up. The other option is to take a step back, and do a different style of design. Most proposals nowadays do this, and the idea actually goes all the way back to debates we had when adding extension methods in the first place: instead of the extension _members_ saying what they extend, maybe they should be grouped into "extensions"; class-like groups of extension methods that all extend the same type. With the enclosing "extension class" doing all the extension, the members can just be declared with their normal syntax: ``` c# class Person { public string Name { get; } public Person(string name) { Name = name; } } extension class Enrollee extends Person { static Dictionary enrollees = new (); // static field public void Enroll(Professor supervisor) { enrollees[this] = supervisor; } // instance method public Professor Supervisor => enrollees.TryGetValue(this, out var supervisor) ? supervisor : null; // instance property public static ICollection Students => enrollees.Keys; // static property public Person(string name, Professor supervisor) : this(name) { this.Enroll(supervisor); } // constructor } ``` Issue [#11159](https://github.com/dotnet/roslyn/issues/11159) summarizes the recent proposals along these lines. This syntactic approach has clear advantages - writing an extension member is as simple as writing a normal member. It does have some challenges, too. One is, what does it compile into? Another is, how do you disambiguate when several extension members are in scope? Those are trivially answered by the current extension methods, but need more elaborate answers here. Looking at our current BCL libraries, it is clear that there are many places where layering is poor. Essentially, there's a trade off between layering (separating dependencies) and discoverability (offering members _on_ the important types). _If_ you have a file system, you want to offer a constructor overload that takes a file name. Extension constructors (for instance) address this. Also, extension members allow putting concrete functionality on interfaces, and on specific instances of generic types. We think that the most useful and straightforward kinds of extension methods to add would be instance and static versions of methods, properties and indexers, as well as static fields. We don't think instance fields make sense - where would they live? The answer to that is at best complicated, and probably not efficient. Extension constructors would also be useful. There's a bit more of a design space here, depending on whether you want them to be just like instance constructors (which would require the specific extended type to already have a constructor to delegate to), or more like factories (which could be added to interfaces etc. and produce instances of more derived types). Extension operators are in a bit of a gray zone. Some of them, like arithmetic operators, probably make sense. Equality operators are hard, because everyone's already got one, so the "built-in" would always shadow. Implicit conversion operators are especially problematic, because their invocation doesn't have an associated syntax, so it's very subtle when they are applied, and there's no easy way to choose _not_ to do it. Implementation-wise, the extension class would probably turn into a static class, and the extension members would probably be represented as static methods, with the most obvious mapping we can find. There's a case for implementing extension properties via "indexed properties" which are supported in IL (and VB), though maybe we'd prefer mapping to something that could be written directly in C#. The name of the extension would be the name of the static class, and can also be used in disambiguation, for which we would need a syntax. Maybe existing syntax, such as `((Enrollee)person).Supervisor` or `(person as Enrollee).Supervisor` could be used when disambiguating instance members or operators, whereas static members would just be accessed directly off of the generated static class, as in `Enrollee.Students`. Arguably, consumption is more important than production. In this case, that is an argument both for and against introducing a new syntactic approach to declaring extension members: On the one hand, it makes it less of a problem if we effectively deprecate the existing syntax, on the other it makes it less compelling to add syntax for it, instead of maybe some attribute-based approach, or other less syntax-heavy solutions. For extension properties, people often point to `Enumerable.Count(IEnumerable src)` as an example of why they want them, but that's a terrible example, because it isn't efficient. Even so, there's probably a good need for these - the Roslyn code base for instance has many little extension methods on top of interfaces, that "should" have been properties. `Kind()` is a perfect example. ## Conclusion We think this is worth pursuing further. We will come back to it in future design meetings to start ironing out the details. ================================================ FILE: meetings/2016/LDM-2016-07-12.md ================================================ # C# Language Design Notes for Jul 12, 2016 ## Agenda Several design details pertaining to tuples and deconstruction resolved. # All or nothing with element names? There's certainly a case for partial names on literals - sometimes you want to specify the names of _some_ of the elements for clarity. These names have to fit names in the tuple type that the literal is being converted to: ``` c# List<(string firstName, string lastName, bool paid)> people = ...; people.Add(("John", "Doe", paid: true)); ``` Do we also want to allow partial names in _types_? Yes we do. We don't have strong arguments against it, and generality calls for it. It's likely that there are scenarios, and not allowing will feel arbitrary. "The `bool` deserves a clarifying name, but the `TypeSymbol` is clear". ``` c# var t = (1, y: 2); // infers (int, int y) (int x, int) t = (1, 2); ``` As a reminder, we distinguish between leaving out the name and explicitly specifying `Item1`, `Item2`, etc. You get a warning if you use `Item1`... in literals if they are not explicitly there in the target type. (Warning)` ``` c# (int, int) t = (Item1: 1, Item2: 2); // Warning, names are lost ``` For the editor experience we imagine offering completion of a named element in the corresponding position of a tuple literal. # ITuple ``` c# interface ITuple { int Size; object this[int i] { get; } } ``` `ITuple` is really an interface for dynamically checking for deconstructability. Whether we should add `ITuple` now depends on whether we think we want to allow recursive patterns to do that, once we add them. We _do_ think we want that. We cannot use an existing collection interface (such as `IReadOnlyCollection`) because we want to be able to distinguish between deconstructable objects and collections (which might one day get their own pattern). It is fine for a class to implement both. `ValueTuple<...>` should of course implement `ITuple`, and the translation of long tuples should work. The Framework team should chime in on the specific shape and names on the interface. Is `ITuple` the right name, or is it too narrow? Is `IDeconstructable` more to the point or is it too long? Should it be `Size` or `Length` or `Count`? # var when var type exists ``` c# class var {} var (x, y) = e; ``` People use this trick to block the use of `var`, and we should let them, even though we disagree with the practice. So even though a type isn't valid in that position, we will let the presence of a `var` type turn this into an error. # var method What if there's a _method_ called `var`? ``` c# ref int var(int x, int y); var(x, y) = e; // deconstruction or call? ``` Here there is no prior art, so we will always interpret the `var` to mean a deconstructing declaration. If you wanted the method call, parenthesize the call or use @. ================================================ FILE: meetings/2016/LDM-2016-07-13.md ================================================ # C# Language Design Notes for Jul 13, 2016 ## Agenda We resolved a number of questions related to tuples and deconstruction, and one around equality of floating point values in pattern matching. # Handling conflicting element names across type declarations For tuple element names occurring in partial type declarations, we will require the names to be the same. ``` c# partial class C : IEnumerable<(string name, int age)> { ... } partial class C : IEnumerable<(string fullname, int)> { ... } // error: names must be specified and the same ``` For tuple element names in overridden signatures, and when identity convertible interfaces conflict, there are two camps: 1. Strict: the clashes are _disallowed_, on the grounds that they probably represent programmer mistakes - when overriding or implementing a method, tuple element names in parameter and return types must be preserved - it is an error for the same generic interface to be inherited/implemented twice with identity convertible type arguments that have conflicting tuple element names 2. Loose: the clashes are _resolved_, on the grounds that we shouldn't (and don't otherwise) dictate such things (e.g. with parameter names) - when overriding or implementing a method, different tuple element names can be used, and on usage the ones from the most derived statically known type win, similar to parameter names - when interfaces with different tuple element names coincide, the conflicting names are elided, similar to best common type. ``` c# interface I1 : IEnumerable<(int a, int b)> {} interface I2 : IEnumerable<(int c, int d)> {} interface I3 : I1, I2 {} // what comes out when you enumerate? class C : I1 { public IEnumerator<(int e, int f)> GetEnumerator() {} } // what comes out when you enumerate? ``` We'll go with the strict approach, barring any challenges we find with it. We think helping folks stay on the straight and narrow here is the most helpful. If we discover that this is prohibitive for important scenarios we haven't though of, it will be possible to loosen the rules in later releases. # Deconstruction of tuple literals Should it be possible to deconstruct tuple literals directly, even if they don't have a "natural" type? ``` c# (string x, byte y, var z) = (null, 1, 2); (string x, byte y) t = (null, 1); ``` Intuitively the former should work just like the latter, with the added ability to handle point-wise `var` inference. It should also work for deconstructing assignments: ``` c# string x; byte y; (x, y) = (null, 1); (x, y) = (y, x); // swap! ``` It should all work. Even though there never observably is a physical tuple in existence (it can be thought of as a series of point-wise assignments), semantics should correspond to introducing a fake tuple type, then imposing it on the RHS. This means that the evaluation order is "breadth first": 1. Evaluate the LHS. That means evaluate each of the expressions inside of it one by one, left to right, to yield side effects and establish a storage location for each. 2. Evaluate the RHS. That means evaluate each of the expressions inside of it one by one, left to right to yield side effects 3. Convert each of the RHS expressions to the LHS types expected, one by one, left to right 4. Assign each of the conversion results from 3 to the storage locations found in 1. This approach ensures that you can use the feature for swapping variables `(x, y) = (y, x);`! # var in tuple types? ``` c# (var x, var y) = GetTuple(); // works (var x, var y) t = GetTuple(): // should it work? ``` No. We will keep `var` as a thing to introduce local variables only, not members, elements or otherwise. For now at least. # Void as result of deconstructing assignment? We decided that deconstructing assignment should still be an expression. As a stop gap we said that its type could be void. This still grammatically allows code like this: ``` c# for (... ;; (current, next) = (next, next.Next)) { ... } ``` We'd like the result of such a deconstructing assignment to be a tuple, not void. This feels like a compatible change we can make later, and we are open to it not making it into C# 7.0, but longer term we think that the result of a deconstructing assignment should be a tuple. Of course a compiler should feel free to not actually construct that tuple in the overwhelming majority of cases where the result of the assignment expression is not used. The normal semantics of assignment is that the result is the value of the LHS after assignment. With this in mind we will interpret the deconstruction in the LHS as a tuple: it will have the values and types of each of the variables in the LHS. It will not have element names. (If that is important, we could add a syntax for that later, but we don't think it is). # Deconstruction as conversion and vice versa Deconstruction and conversion are similar in some ways - deconstruction feels a bit like a conversion to a tuple. Should those be unified somehow? We think no. the existence of a `Deconstruct` method should not imply conversion: implicit conversion should always be explicitly specified, because it comes with so many implications. We could consider letting user defined implicit conversion imply `Deconstruct`. It leads to some convenience, but makes for a less clean correspondence with consumption code. Let's keep it separate. If you want a type to be both deconstructable and convertible to tuple, you need to specify both. # Anonymous types Should they implement `Deconstruct` and `ITuple`, and be convertible to tuples? No. There are no really valuable scenarios for moving them forward. Wherever that may seem desirable, it seems tuples themselves would be a better solution. # Wildcards in deconstruction We should allow deconstruction to feature wildcards, so you don't need to specify dummy variables. The syntax for a wildcard is `*`. This is an independent feature, and we realize it may be bumped to post 7.0. # compound assignment with distributive semantics ``` c# pair += (1, 2); ``` No. # Switch on double What equality should we use when switching on floats and doubles? - We could use `==` - then `case NaN` wouldn't match anything. - We could use `.Equals`, which is similar except treating `NaN`s as equal. The former struggles with "at what static type"? The latter is defined independently of that. The former would equate 1 and 1.0, as well as byte 1 and int 1 (if applied to non-floating types as well). The latter won't. With the latter we'd feel free to optimize the boxing and call of Equals away with knowledge of the semantics. Let's do `.Equals`. ================================================ FILE: meetings/2016/LDM-2016-07-15.md ================================================ # C# Design Language Notes for Jul 15, 2016 ## Agenda In this meeting we took a look at what the scope rules should be for variables introduced by patterns and out vars. # Scope of locals introduced in expressions So far in C#, local variables have only been: 1. Introduced by specific kinds of statements, and 2. Scoped by specific kinds of statements `for`, `foreach` and `using` statements are all able to introduce locals, but at the same time also constitute their scope. Declaration statements can introduce local variables into their immediate surroundings, but those surroundings are prevented by grammar from being anything other than a block `{...}`. So for most statement forms, questions of scope are irrelevant. Well, not anymore! Expressions can now contain patterns and out arguments that introduce fresh variables. For any statement form that can contain expressions, we therefore need to decide how it relates to the scope of such variables. ## Current design Our default approach has been fairly restrictive: - All such variables are scoped to the nearest enclosing statement (even if that is a declaration statement) - Variables introduced in the condition of an `if` statement aren't in scope in the `else` clause (to allow reuse of variable names in nested `else if`s) This approach caters to the "positive" scenarios of `is` expressions with patterns and invocations of `Try...` style methods with out parameters: ``` c# if (o is bool b) ...b...; // b is in scope else if (o is byte b) ...b...; // fine because bool b is out of scope ...; // no b's in scope here ``` It doesn't handle unconditional uses of out vars, though: ``` c# GetCoordinates(out var x, out var y); ...; // x and y not in scope :-( ``` It also fits poorly with the "negative" scenarios embodied by what is sometimes called the "bouncer pattern", where a method body starts out with a bunch of tests (of parameters etc.) and jumps out if the tests fail. At the end of the test you can write code at the highest level of indentation that can assume that all the tests succeeded: ``` void M(object o) { if (o == null) throw new ArgumentNullException(nameof(o)); ...; // o is known not to be null } ``` However, the strict scope rules above make it intractable to extend the bouncer pattern to use patterns and out vars: ``` c# void M(object o) { if (!(o is int i)) throw new ArgumentException("Not an int", nameof(o)); ...; // we know o is int, but i is out of scope :-( } ``` ## Guard statements In Swift, this scenario was found so important that it earned its own language feature, `guard`, that acts like an inverted `if`, except that a) variables introduced in the conditions are in scope after the `guard` statement, and b) there must be an `else` branch that leaves the enclosing scope. In C# it might look something like this: ``` c# void M(object o) { guard (o is int i) else throw new ArgumentException("Not an int", nameof(o)); // else must leave scope ...i...; // i is in scope because the guard statement is specially leaky } ``` A new statement form seems like a heavy price to pay. And a guard statement wouldn't deal with non-error bouncers that correct the problem instead of bailing out: ``` c# void M(object o) { if (!(o is int i)) i = 0; ...; // would be nice if i was in scope and definitely assigned here } ``` (In the bouncer analogy I guess this is equivalent to the bouncer lending the non-conforming customer a tie instead of throwing them out for not wearing one). ## Looser scope rules It would seem better to address the scenarios and avoid a new statement form by adopting more relaxed scoping rules for these variables. _How_ relaxed, though? **Option 1: Expression variables are only scoped by blocks** This is as lenient as it gets. It would create some odd situations, though: ``` c# for (int i = foo(out int j);;); // j in scope but not i? ``` It seems that these new variables should at least be scoped by the same statements as old ones: **Option 2: Expression variables are scoped by blocks, for, foreach and using statements, just like other locals:** This seems more sane. However, it still leads to possibly confusing and rarely useful situations where a variable "bleeds" out many levels: ``` c# if (...) if (...) if (o is int i) ...i... ...; // i is in scope but almost certainly not definitely assigned ``` It is unlikely that the inner `if` intended `i` to bleed out so aggressively, since it would almost certainly not be useful at the outer level, and would just pollute the scope. One could say that this can easily be avoided by the guidance of using curly brace blocks in all branches and bodies, but it is unfortunate if that changes from being a style guideline to having semantic meaning. **Option 3: Expression variables are scoped by blocks, for, foreach and using statements, as well as all embedded statements:** What is meant by an embedded statement here, is one that is used as a nested statement in another statement - except inside a block. Thus the branches of an `if` statement, the bodies of `while`, `foreach`, etc. would all be considered embedded. The consequence is that variables would always escape the condition of an `if`, but never its branches. It's as if you put curlies in all the places you were "supposed to". ## Conclusion While a little subtle, we will adopt option 3. It strikes a good balance: - It enables key scenarios, including out vars for non-`Try` methods, as well as patterns and out vars in bouncer if-statements. - It doesn't lead to egregious and counter-intuitive multi-level "spilling". It does mean that you will get more variables in scope than the current restrictive regime. This does not seem dangerous, because definite assignment analysis will prevent uninitialized use. However, it prevents the variable names from being reused, and leads to more names showing in completion lists. This seems like a reasonable tradeoff. ================================================ FILE: meetings/2016/LDM-2016-08-24.md ================================================ C# Language Design Meeting, Aug 24, 2016 ======================================== ## Agenda After a meeting-free period of implementation work on C# 7.0, we had a few issues come up for resolution. 1. What does it take to be task-like? 2. Scope of expression variables in initializers # Task-like types The generalization of async return types requires the return type of an async method to provide an implementation for the "builder object" that the method body's state machine interacts with when it awaits, and when it produces the returned "task-like" value. How should it provide this implementation? There are a couple of options: 1. A `GetAsyncMethodBuilder` method on the task-like type following a specific compiler-recognized pattern 2. 1 + a modifier on the type signifying "buy-in" to being an async type 3. An attribute that encodes the builder type We like the attribute approach, because this is really a metadata concern. The attribute sidesteps issues with accessibility (is the type only task-like when the `GetAsyncMethodBuilder` method is accessible?), and doesn't pollute the member set of the type. Also it works on interfaces. For instance, it could conceivably apply to WinRT's `IAsyncAction` etc. We only expect very few people to ever build their own task-like types. We may or may not choose to ever actually *ship* the attribute we will use. Implementers can just roll their own internal implementation. The compiler only looks for the name - which will be `System.Runtime.CompilerServices.AsyncBuilderAttribute`. There are a couple of options for what the attribute should contain: 1. Nothing - it is just a marker, and there is still a static method on the task-like type 2. The name of a method to call on the task-like type 3. The builder type itself 4. A static type for *getting* the builder Option 1 and 2 we've already ruled out, because we don't want to depend on accessible members on the task-like type itself. Option 3 would require a `Type` argument to the constructor that is either non-generic (for void-yielding or non-generic result-yielding task-like types) or an open generic type with one type parameter (for generic result-yielding task-like types) Option 4 is essentially a "builder builder", and would let us have a less dirty relationship between type parameters on the builder and task-like: ``` c# static class ValueTaskBuilderBuilder { ValueTaskBuilder GetBuilder(ValueTask dummy); } ``` That is probably a bridge too far, so we will go with Option 3. The builder type given in the attribute is required to have a static, accessible, non-generic Create method. This would work for `IAsyncAction` etc. from WinRT, assuming that a builder is implemented for them, and an attribute placed on them. The proposal to allow the builder to be referenced from the async method is attractive, but something for another day. # Scope of expression variables in initializers ``` c# int x = M(out int t), y = t; ``` If this is a local declaration, `t` is in scope after the declaration. If this is a _field_ declaration, though, should it be? It's problematic if it is, and inconsistent if it isn't! Also, if we decide one way or another, we can never compatibly change our mind on it. Let's block this off and not allow expression variables to be introduced in field initializers! It's useless today, and we should save the scoping decisions for when future features give us a use case. ``` c# public C() : base(out int x) { // use x? } ``` A variable declared in a `this` or `base` initializer should be in scope in the whole constructor body. ================================================ FILE: meetings/2016/LDM-2016-09-06.md ================================================ # C# Language Design Notes for Sep 6, 2016 ## Agenda 1. How do we select `Deconstruct` methods? # How do we select Deconstruct methods? `(int x, var y) = p` cannot just turn into `p.Deconstruct(out int x, out var y)`, because we want it to find a `Deconstruct` method with a more specific type than `int`, e.g. `byte`. We should look only at the arity of the `Deconstruct` method. If there's more than one with the given arity, we fail. If necessary, we will then translate this into passing temporary variables to the `Deconstruct` method, instead of the ones declared in the deconstruction. E.g., if `p` has ``` C# void Deconstruct(out byte x, out byte y) ...; ``` We would translate it equivalently to: ``` c# p.Deconstruct(out byte __x, out byte __y); (int x, int y) = (__x, __y); ``` ================================================ FILE: meetings/2016/LDM-2016-10-18.md ================================================ C# Language Design Meeting Notes, Oct 18, 2016 ============================================== ## Agenda Go over C# 7.0 features one last (?) time to make sure we feel good about them and address remaining language-level concerns. 1. Wildcard syntax 2. Design "room" between tuples and patterns 3. Local functions 4. Digit separators 5. Throw expressions 6. Tuple name mismatch warnings 7. Tuple types in `new` expressions # Pattern matching ## Wildcards If we want to reopen the discussion of using `_`, and it doesn't make C# 7.0, we may find ourselves wanting to block off use of `_` as an ordinary identifier in the new declaration contexts (patterns, deconstruction, out vars). Let's front load the final design of wildcards to decide if anything needs to happen here. ## Design "room" between tuples and pattern matching `case (int, int) x:` is not currently allowed, in order to leave design space. More specifically we want this to mean a recursive pattern, rather than just a tuple type, once we get to recursive patterns. We're good with leaving this an error in the meantime, even though it may occasionally be puzzling to developers. # Local functions We want to ideally allow any expression or set of statements to be lifted out in a local method. There are two ways in which this cannot be realized: 1. assignment to readonly fields in constructors - the runtime disallows those assignments if we lift them out to methods. 2. async methods and iterators - they aren't done executing when they return, so they can't generally contribute to definite assignment of enclosing variables. For async local functions we do recognize assignments that happen before the first await. Is this too subtle? Maybe, but it's fine to keep it. # Digit separators We don't allow leading or trailing underbars - they have to be *between* digits: they are digit *separators* after all! We think this is fine, but if we hear feedback to the contrary we can try to relax it later. # Throw expressions They are allowed as expression bodies, as the second operand of `??`, and as the second and third operand of `?:`. They are not allowed in `&&` and `||`, and cannot be parenthesized. We think this is a fine place to land. # Tuples ## Name mismatch warnings We don't currently warn about names moving to a different position. Should we? ``` c# (int first, int last) M() ... (int last, int first) t = M(); // Oops! ``` Ideally yes. There are a lot of weird cases that would be hard to track down, though. Let's get the obvious ones. Essentially where we do implicit conversions we would check and warn. ## Use of tuples in `new` You cannot `new` up a tuple type. However, we should certainly allow arrays of tuples to be created. It is probably also fine to keep allowing `new`ing of nullable tuple types: ``` c# var array = new (int x, int y)[10]; // Absolutely var nullable = new (int x, int y)?(); // Why not? ``` ================================================ FILE: meetings/2016/LDM-2016-10-25-26.md ================================================ C# Language Design Notes for Oct 25 and 26, 2016 ================================================ Agenda ------ - Declaration expressions as a generalizing concept - Irrefutable patterns and definite assignment - Allowing tuple-returning deconstructors - Avoiding accidental reuse of out variables - Allowing underbar as wildcard character Declaration expressions ======================= In C# 6.0 we embraced, but eventually abandoned, a very general notion of "declaration expressions" - the idea that a variable could be introduced *as* an expression `int x`. The full generality of that proposal had some problems: * A variable introduced like `int x` is unassigned and therefore unusable in most contexts. It also leads to syntactic ambiguities * We therefore allowed an optional initializer `int x = e`, so that it could be used elsewhere. But that lead to weirdness around when `= e` meant assignment (the result of which is a *value*) and when it meant initialization (the result of which is a *variable* that can be assigned to or passed by ref). However, there's value in looking at C#'s new deconstruction and out variable features through the lens of declaration expressions. Currently we have * Deconstructing *assignments* of the form `(x, y) = e` * Deconstructing *declarations* of the form `(X x, Y y) = e` * Out variables of the form `M(out X x)` This calls for generalization. What if we said that * Tuple expressions `(e1, e2)` can be lvalues if all their elements are lvalues, and will cause deconstruction when assigned to * There are declaration expressions `X x` that can only occur in positions where an unassigned variable is allowed, that is * In or as part of the left hand side of an assignment * In or as part of an out argument Then all the above features - and more - can be expressed in terms of combinations of the two: * Deconstructing assignments are just a tuple on the left hand side of an assignment * Deconstructing declarations are just deconstructing assignments where all the nested variables are declaration expressions * Out variables are just declaration expressions passed as an out argument Given the short time left for fixes to C# 7.0 we could still keep it to these three special cases for now. However, if those restrictions were later to be lifted, it would lead to a number of other things being expressible: * Using a "deconstructing declaration" as an expression (today it is a statement) * Mixing existing and new variables in a deconstructing assignment `(x, int y) = e` * deconstruction in out context `M(out (x, y))` * single declaration expression on the left hand side of assignment `int x = e` The work involved in moving to this model *without* adding this functionality would be to modify the representation in the Roslyn API, so that it can be naturally generalized later. Conclusion ---------- Let's re-cast the currently planned C# 7.0 features in turns of declaration expressions and tuple expressions, and then plan to later add some or all of the additional expressiveness this enables. Irrefutable patterns ==================== Some patterns are "irrefutable", meaning that they are known by the compiler to be always fulfilled. We currently make very limited use of this property in the "subsumption" analysis of switch cases. But we could use it elsewhere: ``` c# if (GetInt() is int i) { ... } UseInt(i); // Currently an error. We could know that i is definitely assigned here ``` This might not seem to be of much value - why are you applying a pattern if it never fails? But in a future with recursive patterns, this may become more useful: ``` c# if (input is Assignment(Expression left, var right) && left == right) { ... } ... // condition failed, but left and right are still assigned ``` Conclusion ---------- This seems harmless, and will grow more useful over time. It's a small tweak that we should do. Tuple-returning deconstructors ============================== It's a bit irksome that deconstructors must be written with out parameters. This is to allow overloading on arity, but in practice most types would only declare one. We could at least optionally allow one of them to be defined with a return tuple instead: ``` c# (int x, int y) Deconstruct() => (X, Y); ``` Instead of: ``` c# void Deconstruct(out int x, out int y) => (x, y) = (X, Y); ``` Evidence is inconclusive as to which is more efficient: it depends on circumstances. Conclusion ---------- Not worth it. Definite assignment for out vars ================================ With the new scope rules, folks have been running into this: ``` c# if (int.TryParse(s1, out var i)) { ... i ... } if (int.TryParse(s2, out var j)) { ... i ... } // copy/paste bug - still reading i instead of j ``` It works the same as when people had to declare their own variables outside the `if`, but it seems a bit of a shame that we can't do better now. Could we do something with definite assignment of out variables that could prevent this situation? Conclusion ---------- Whatever we could do here would be too specific for a language solution. It would be a great idea for a Roslyn analyzer, which can - identify `TryFoo` methods by looking for "Try" in the name and the `bool` return type - find this bug in existing code declaring the variable ahead of time, *as well as* in new code using out variables Underbar wunderbar ================== Can we make `_` the wildcard instead of `*`? We'd need to be sneaky in order to preserve current semantics (`_` is a valid identifier) while allowing it as a wildcard in new kinds of code. ``` c# M(out var _); int _ = e.M(_ => ..._...x...); // outer _ is still a named variable? (int x, var _) = e.M(_ => ..._...x...); (_, _) = ("x", 2); ``` We would need to consider rules along the following lines: * Make `_` always a wildcard in deconstructions and patterns * Make `_` always a wildcard in declaration expressions and patterns * Allow `_` as a wildcard in other l-value situations (out arguments at least, but maybe assignment) when it's not already defined * Allow `_` to be declared more than once in the same scope, in which case it is a wildcard when mentioned * Allow `_` to be "redeclared" in an inner scope, and then it's a wildcard in the inner scope "Once a wildcard, always a wildcard!" Conclusion ---------- This is worth pursuing. More thinking is needed! ================================================ FILE: meetings/2016/LDM-2016-11-01.md ================================================ # C# Language Design Notes for Nov 1, 2016 *Raw notes - need cleaning up* ## Agenda - Abstracting over memory with `Span` # Abstracting over memory spans It's common for teams building IO/data pipelines to invent a type similar to Span\, which is intended to abstract over native and managed memory, so that you write code over them only ones. * Native memory * Managed arrays * stackalloc * stack-allocated We are now in a situation (unlike when C# started out) where every bit of performance counts. You can't afford overhead in terms of: * indirection * bounds checking * virtual calls * GC * copying Span needs to be as efficient to access/index as an array. It should essentially be a "safe pointer". logically: ``` c# struct Span { ref T data; int length; } ``` Can't be declared like this, because the CLR doesn't support ref fields. But there are IntPtr tricks that can be used. These need to be treated as "ref-like": * Can't go on the heap * Can't be captured by lambdas, iterators or async * Can't be a type argument * Etc... Also need a ReadOnlySpan\ or similar. So need a way to specify ref-like types, so that the compiler wants to enforce it. The stack-only-ness helps not having to spend anything on lifetime issues. It lets you pass it to something, and when they return you know they didn't keep any references to it. Should we make existing types "span-aware"? Arrays? Strings? We need to figure out what the right trade-off is. Generally we would keep Span\ at the 1% of users. It's analogous to array in the sense that it's rarely used directly in higher-level code. For the rest there's wrapper types that are not limited to the stack, and can go into Span-land on demand. Ref-like types ============== Let's call the ref-like types "ref structs". (Unlike refs these can be fields. ref fields require new runtime semantics, whereas we can otherwise make do with runtime *optimizations*, so we'll skip on ref fields for now) Ref-like-ness should be declarative; it shouldn't be something you infer. Because something could change somewhere else that changes whether you are or not. (The structs capturing ref-like locals when you have local functions would be inferred. But that's locally in code). A problem something like Span may have at the language level is that it doesn't *look* like something that restrictions apply to, unlike `ref int` or `int*`. We could at least have a naming convention. ``` c# M(await X, GetSpan()); ``` Lifting out GetSpan() to a local wouldn't work. So that speaks to there being a notation on usage. * Nothing? * Naming conventions? * syntax? `ref`? `*`? Is this really only going to be done by 1% of users (which is still many). Stackalloc ---------- ``` c# Span s = stackalloc byte[4]; ``` Ref return rules ---------------- Need to be adjusted a bit. YOu should only be able to return spans to things that are safe to return. Downlevel use ------------- Do we need to poison these type for downlevel import, and how do we do that? optreq`s? Mangled names? Obsolete it and recognize something about that in the new compiler that will make it not obsolete it. Readonly ref ============ If we want to enable spans representing strings, for instance, we need readonly spans. And their indexer needs to return a readonly ref. Not so central to this proposal, but generally interesting: Can we make struct methods that don't copy (their `this` would be readonly ref)? In practice you'd probably want to call the whole struct readonly. Slicing ======= Essentially an API feature, but would want nice overloadable syntax. Structs that can't be passed by value ------------------------------------- TO avoid copying costs, semantic errors etc. Icing. FOr Memory\ ------------- Not ready to discuss yet, but is there a way that you can prevent a Memory\ from getting freed while a Span is "out". Maybe some version of "fixed" can help. A challenge to generalize this from Span to any ref-like type. ``` c# Span b = ...; fixed(byte* p = &b.GetRawPointer()) ``` Maybe this can be expressed more easily? Limitations around ref locals today ----------------------------------- There are many limitations to keep the rules simple, including the fact that ref locals are readonly at the ref level. What should the restrictions be around ref-like types? One difference is that e.g. Span has a default value; 0-length span. Next steps ---------- At the language level: Add an attribute to express ref-like-ness, and build a compiler to enforce it! ================================================ FILE: meetings/2016/LDM-2016-11-15.md ================================================ C# Language Design Notes, Nov 15, 2016 ====================================== > *Quote of the day*: "Bad people don't get good features!" Agenda ------ - Tuple name warnings - "Discards" Tuple name warnings =================== There are two kinds of warnings on the table regarding names in tuples: 1. A warning on tuple *literals* only, if the literal uses an element name that isn't in the target type 2. A warning on all expressions of tuple type if an element name in the expression is used for a *different* element in the target type The first warning helps get the names right when writing a tuple literal, and while maintaining it. It is already in the product: ``` c# (int x, int y) t = (a: 1, b: 2); // Oops, forgot to update tuple literal? Names a and b would be lost ``` The second warning would help guard against bugs due to reordering of tuple elements: ``` c# (string firstName, string lastName) GetName(); (string lastName, string firstName) name = GetName(); // Oops, forgot to swap the element names in name? ``` This warning is currently *not* in the product. We would like it to be, but don't have the runway to implement it. In the future it could be added as an analyzer, or as a compiler warning protected by warning waves (#1580). "Discards" ========== We got great feedback on "wildcards" at the MVP Summit, including changing its name to the term "discards", which is spreading in industry. We are also encouraged to use `_` as the discard character instead of `*`. It works better visually, and is what many other languages already use. There is a small matter of `_` already being a valid identifier. In order for it to coexist as a discard with existing valid uses we need to use a few tricks. Here are the essential rules: 1. A standalone `_` when no `_` is defined in scope is a discard 2. A "designator" `var _` or `T _` in deconstruction, pattern matching and out vars is a discard Discards are like unassigned variables, and do not have a value. They can only occur in contexts where they are assigned to. Examples: ``` c# M(out _, out var _, out int _); // three out variable discards (_, var _, int _) = GetCoordinates(); // deconstruction into discards if (x is var _ && y is int _) { ... } // discards in patterns ``` One use case is to silence warnings when dropping Tasks from async methods: ``` c# _ = FooAsync(); ``` We also want to allow discards as lambda parameters `(_, _) => 0`, but don't expect that to make it in C# 7.0. Today `_` is allowed as an ordinary identifier; the rule would be that it would become a discard if there's more than one. We can also consider allowing top-level discards in foreach loops: ``` c# foreach (_ in e) { ... } // I don't care about the values ``` But that does not seem too important, especially since you can use `var _` to the same effect. In general, whenever use of standalone `_` as a discard is precluded, either by syntactic restrictions or by `_` being declared in the scope, `var _` is often a fine substitute with the same meaning. However, if the declared `_` is a parameter or local, there are situations where you are out of luck: ``` c# public override void M(int _) // declaring _ to signal that this override doesn't care about it { _ = TryFoo(); // Error: cannot assign bool result to int variable _ var _ = TryFoo(); // Error: cannot declare local _ when one is already in scope } ``` These situations are always local to a member, so it is relatively easy to rewrite them, e.g. to rename the incoming parameter. We could consider accommodating them in the future: the error for a local redeclaration of `_` when one is already in scope could be changed to allowing it, but consider the second declaration a discard. This may be too subtle, and not useful enough, but it is worth considering post C# 7.0. ================================================ FILE: meetings/2016/LDM-2016-11-16.md ================================================ C# Language Design Notes for Nov 16, 2016 ========================================= In this meeting we looked at the proposal for nullable reference types in [nullable-reference-types.md](https://github.com/dotnet/csharplang/tree/7981ea1fb4d89571fd41e3b75f7c9f7fc178e837/proposals/nullable-reference-types.md). The below has few answers and many questions. It is essentially a laundry list of things to work on as we design the feature. Nullable types ============== ## Calling it "types" MVP Feedback: There's a bit of a dissonance between calling it "types" and not having guarantees. Maybe talk about it more like analyzers. Should this be part of a general annotations framework? Do we want to keep doing this specifically per feature or have a general framework for it? Maybe. Let's keep that in the back of our minds. We don't want to be blocked by figuring that out right now. ## Overload resolution It's not the intention that knowing something is not-null influences overload resolution. ## Tracking nested reference fields and properties We need to decide whether to track `x.y` when both are reference types, and to what extent we will track aliasing. Guarantees vs usefulness. ## The dammit operator Should it generate a runtime check? There are pros and cons. It would sort of break with a principle that nullability stuff has no runtime semantics, and also impose runtime cost (though that can be eliminated by a smart compiler, when the next thing that follows - e.g. dereferencing - also already starts with a null check). On the other hand it might be good to verify non-nullness right then and there, rather than experience random fallout later. Is it an "I know what I'm doing" or a "check that I know what I'm doing" operator? ## Nested annotations Is it really that useful to have nested nullability annotations, as in `string?[]` or `List`? On arrays you almost always have null elements. We suspect that it *is* useful, and would detract from the combo with generics if not there, but on the other hand it is a great simplification to only track nullability at the top level. One problem with nested nullability is that casts will not be able to check the nullability of type arguments. A cast cannot distinguish `(List)o` from `(List)o`, since the runtime object in `o` won't know if it was instantiated with `string` or `string?`. So the notion that "all is good after a successful cast" is lost. If we *do* allow nullable type parameters, should you be able to say `!` on type parameters that may be nullable? For generics we should find a few samples that we believe are representative, and focus on them. May be able to reuse ones from the Midori project. ## Warning types The distinction between nullable and non-null warnings is not useful, and some of them (conversions) kind of belong to both. ## When are types the same Issue: We need to think about when types are considered to be the same for declarative reasons (overrides, etc) It's similar to tuples in that we have runtime types ornated with extra compile time information. That's a good analogy for a check list, but we should feel free to reach different conclusions. ## null literals Rules should apply not just to the null literal but to null constants ## The construction rule There's a rule that checks that all non-nullable reference fields and properties are initialized by a constructor. It needs to also look recursively through fields and properties of value types for nested non-null reference types. We need to decide *when* to check during a constructor. It could be before calling helpers on the instance (since they may assume non-nullability) or only at the end. Usefulness vs guarantee! ## Arrays Should we disallow/discourage arrays of non-null, to try to avoid the array hole where you have an array of a non-nullable element type, initialized entirely with nulls? What should people do to go from `T?[]` to `T[]`? Can `!` be applied for the recursive case? Otherwise, casts may be necessary. ## Default Is `default(T)` of type `T` or `T?`? If it gives a warning, you could shut it up with `!`. ## Referenced assemblies Should it be evident from an assembly whether unannotated means non-null or not? The compiler would have to be able to embrace the "don't care" old fashioned kind of reference type, even when opted in to non-nullability. What are the semantics around such "shut-up types", and sources of them? ## Unconstrained type parameters When T is constrained by U, we need to ensure that T is assignable to T, and T to U. It's more complicated than just saying warn from both sides. ## Are the warnings part of the language Are this set of warnings part of the language, or are they a "compiler feature" - essentially some built-in analyzers. In the latter case we might allow them to evolve in a breaking way. ## Is nullability always explicit? It is probably fine that you can get nullable reference types implicitly (e.g. inference from `string`, `null`). ## Locals Not annotating locals would reduce the number of places that would need to be fixed when turning nullability on. They would then have their nullability inferred instead. It's a trade-off between expressiveness and upgrade burden. This goes to granularity of opt-in as well. It would cause a readability disconnect between locals and fields. Should `var?` be allowed? should `var` always be nullable? Should it infer its nullability? How to best express intent here? ## Parameters There's a proposal to allow `!` on parameter types `M(T! x)` to cause a runtime null check to be generated. That would be better written as `M(T x!)`, putting the `! on the parameter, since it's about the parameter itself, and would actually name the parameter in the exception thrown! ## Expressing TryGet Could get away with non-language flourishes, such as attributes. "DefinitelyAssignedWhenTrue". ## Applying flow analysis to value types The proposal of also doing the flow analysis for nullable value types is more concerning, because it's adding new runtime semantics. Also, it may work for dereferencing those values, but not so well if it is an argument: Does it influence overload resolution? What if there are many arguments? Flow for nullable value types is a nice-to-have add-on, not essential to the proposal. ================================================ FILE: meetings/2016/LDM-2016-11-30.md ================================================ C# Language Design Notes for Nov 30, 2016 ========================================= Agenda ------ - Scope of while condition expression variables - Mixed deconstruction - Unused expression variables - Declarations in embedded statements - Not-null pattern Scope of while condition expression variables ============================================= We are compelled by the argument in #15529 about the scope of expression variables occurring in `while` conditions. ``` c# while (src.TryGetNext(out int x)) { // Same or different `x` each time around? } // x in scope here? ``` More broadly we seem to have the following options for scopes and lifetimes of such declarations: 1. There is a single variable `x` for the whole `while` statement, which is initialized repeatedly, each time around. It is in scope outside the `while` statement. 2. There is a single variable `x` for the whole `while` statement, which is initialized repeatedly, each time around. It is only in scope inside the `while` statement. 3. There is a fresh variable `x` for each iteration of the `while` statement, initialized in the condition. It is only in scope inside the `while` statement. There is a clear argument for the lifetime of the variable being a single iteration. We know from `foreach` that this leads to better capture in lambdas, but perhaps more importantly it avoids the odd behavior of the same variable getting initialized multiple times. (Pretty much the only way to otherwise achieve that in C# is through insidious use of `goto`). This puts us squarely in option 3 above, where it is meaningless to put "the" variable in scope outside of a while loop. Indeed, from the outside there is no notion of "the" variable: there will be multiple `x`es over the execution of the loop. Consequences in for loops ------------------------- In light of a changed `while` loop we also need to examine what that means to the `for` loop, which, while not defined as such in the spec, can be informally understood as "desugaring" into a `while` loop. ``` c# for (; ; ) ==> { while() { cont: { } } } ``` So with respect to scopes and lifetimes, `` should behave the same as the condition in a while loop. Similarly, `` should be a fresh variable each time around. Furthermore it should occur in its own little nested scope, since variables introduced in it won't be definitely assigned until the end of each iteration. Conclusion ---------- We want to change to the narrow scopes and short lifetime for expression variables introduced in the condition of while and for loops, and in the increment of for loops. This means that the `if` statement becomes more of a special case, with expression variables in its condition having broad scope. That is probably a good place to land. The main scenarios for this behavior are driven by the if statement to begin with, and it is also the one kind of statement where the lifetime and definite assignment situation for such variables makes it reasonable for them to persist outside of the statement. Mixed deconstruction ==================== The reinterpretation of deconstruction in terms of declaration expressions would let us generalize so that: - Would allow mixing existing and newly declared variables in a deconstruction - Would allow newly declared variables in deconstruction nested in expressions - No error when occuring as an embedded statement Conclusion ---------- From a language design point of view we are confident in this generalization of the deconstruction feature. It is doubtful whether the change can make it into C# 7.0 at this point. Even though it is a small code change in and of itself, it introduces testing work and general churn. Warn about unused expression variables? ======================================= Should we warn when an expression variable goes unused? Typically such variables would be dummy variables, and with discards `_` you no longer need them. On the other hand, we could leave it to the IDE to suggest discards etc, rather than give a warning from the language. Conclusion ---------- Let's not add the warning. Keep the warning in place where it already is in existing C#. Declarations in embedded statements =================================== C# currently has a grammatically enforced restriction against declaring "useless" variables as "embedded statements": ``` c# if (x > 3) var y = x; // Error: declaration statement not allowed as an embedded statement ``` Of course expression variables now give you new ways of similarly declaring variables that are immediately out of scope and hence "useless". Should we give errors for those new situations also? Conclusion ---------- Let's not add errors for deconstruction situations. In principle we probably want to remove even the existing limitation, but it's work we won't push for in C# 7.0. Pattern to test not-null? ========================= It's been suggested to have a pattern for testing non-nullness (just as there is a `null` pattern for checking nullness). Conclusion ---------- No time in C# 7.0 but a good idea. ================================================ FILE: meetings/2016/LDM-2016-12-07-14.md ================================================ C# Language Design Notes for Dec 7 and Dec 14, 2016 ================================================= Agenda ------ - Expression variables in query expressions - Irrefutable patterns and reachability - Do-while loop scope Expression variables in query expressions ========================================= It seems desirable to allow expression variables in query clauses to be available in subsequent clauses: ``` c# from s in strings where int.TryParse(s, out int i) select i; ``` The idea is that the `i` introduced in the `where` clause becomes a sort of extra range variable for the query, and can be used in the `select` clause. It would even be definitely assigned there, because the compiler is smart enough to figure out that variables that are "definitely assigned when true" in a where clause expression would always be definitely assigned in subsequent clauses. This is intriguing, but when you dig in it does raise a number of questions. Translation ----------- How would a query like that be translated into calls of existing query methods? In the example above we would need to split the `where` clause into a call to `Select` to compute both the boolean result and the expression variable `i`, then a call to `Where` to filter out those where the boolean result was false. For instance: ``` c# strings .Select(s => new { s, __w = int.TryParse(s, out int i) ? new { __c = true, i } : new { __c = false, i = default } }) .Where(__p => __p.__w.__c); .Select(__p => __p.__c.i); ``` That first `Select` call is pretty unappetizing. We can do better, though, by using a trick: since we know that the failure case is about to be weeded out by the `Where` clause, why bother constructing an object for it? We can just null out the whole anonymous object to signify failure: ``` c# strings .Select(s => int.TryParse(s, out int i) ? new { s, i } : null) .Where(__p => __p != null) .Select(__p => __p.i); ``` Much better! Other query clauses ------------------- We haven't really talked through how this would work for other kinds of query clauses. We'd have to go through them one by one and establish what the meaning is of expression variables in each expression in each kind of query clause. Can they all be propagated, and is it meaningful and reasonable to achieve? Mutability ---------- One thing to note is that range variables are immutable, while expression variables are mutable. We don't have the option of making expression variables mutable across a whole query, so we would need to make them immutable either: - everywhere, or - outside of the query clause that introduces them. Having them be mutable inside their own query clause would allow for certain coding patterns such as: ``` from o in objects where o is int i || (o is string s && int.TryParse(s, out i)) select i; ``` Here `i` is introduced and then mutated in the same query clause. The above translation approaches would accommodate this "mutable then immutable" semantics if we choose to adopt it Performance ----------- With a naive query translation scheme, this could lead to a lot of hidden allocations even when an expression variable is *not* used in a subsequent clause. Today's query translation already has the problem of indiscriminately carrying forward all range variables, regardless of whether they are ever needed again. This feature would exacerbate that issue. We could think in terms of language-mandated query optimizations, where the compiler is allowed to shed range variables once they are never referenced again, or at least if they are never referenced outside of their introducing clause. Blocking off ------------ We won't have time to do this feature in C# 7.0. If we want to leave ourselves room to do it in the future, we need to make sure that we don't allow expression variables in query clauses to mean something *else* today, that would contradict such a future. The current semantics is that expression variables in query clauses are scoped to only the query clause. That means two subsequent query clauses can use the same name in expression variables, for instance. That is inconsistent with a future that allows those variables to share a scope across query clause boundaries. Thus, if we want to allow this in the future we have to put in some restrictions in C# 7.0 to protect the design space. We have a couple of options: - Disallow expression variables altogether in query clauses - Require that all expression variables in a given query expression have different names The former is a big hammer, but the latter requires a lot of work to get right - and seems at risk for not blocking off everything well enough. Deconstruction -------------- A related feature request is to allow deconstruction in the query clauses that introduce new range variables: ``` c# from (x, y) in points let (dx, dy) = (x - x0, y - y0) select Sqrt(dx * dx + dy * dy) ``` This, again, would simply introduce extra range variables into the query, and would sort of be equivalent to the tedious manual unpacking: ``` c# from __p1 in points let x = __p1.Item1 let y = __p1.Item2 let __p2 = (x - x0, y - y0) let dx = __p2.Item1 let dy = __p2.Item2 select Sqrt(dx * dx, dy * dy) ``` Except that we could do a much better job of translating the query into fewer calls: ``` c# points .Select(__p1 => new { x = __p1.Item1, y = __p1.Item2 }) .Select(__p2 => new { dx = __p2.x - x0, dy = __p2.y - y0, * = __p2 } .Select(__p3 => Sqrt(__p3.dx * __p3.dx, __p3.dy * __p3.dy) ``` Conclusion ---------- We will neither do expression variables nor deconstruction in C# 7.0, but would like to do them in the future. In order to protect our ability to do this, we will completely disallow expression variables inside query clauses, even though this is quite a big hammer. Irrefutable patterns and reachability ===================================== We could be smarter about reachability around irrefutable patterns: ``` c# int i = 3 if (i is int j) {} else { /* reachable? */ } ``` We could consider being smart, and realizing that the condition is always true, so the else clause is not reachable. By comparison, though, in current C# we don't try to reason about non-constant conditions: ``` c# if (false && ...) {} else { /* reachable today */ } ``` Conclusion ---------- This is not worth making special affordances for. Let's stick with current semantics, and not introduce a new concept for "not constant, but we know it's true". Do-while loop scope =================== In the previous meeting we decided that while loops should have narrow scope for expression variables introduced in their condition. We did not explicitly say that the same is the case for do-while, but it is. ================================================ FILE: meetings/2016/README.md ================================================ # C# Language Design Notes for 2016 Overview of meetings and agendas for 2016 ## Feb 29, 2016 [C# Language Design Notes for Feb 29, 2016](LDM-2016-02-29.md) *Catch up edition (deconstruction and immutable object creation)* Over the past couple of months various design activities took place that weren't documented in design notes. This a summary of the state of design regarding positional deconstruction, with-expressions and object initializers for immutable types. ## Apr 6, 2016 [C# Language Design Notes for Apr 6, 2016](LDM-2016-04-06.md) We settled several open design questions concerning tuples and pattern matching. ## Apr 12-22, 2016 [C# Language Design Notes for Apr 12-22, 2016](LDM-2016-04-12-22.md) These notes summarize discussions across a series of design meetings in April on several topics related to tuples and patterns: - Tuple syntax for non-tuple types - Tuple deconstruction - Tuple conversions - Deconstruction and patterns - Out vars and their scope ## May 3-4, 2016 [C# Language Design Notes for May 3-4, 2016](LDM-2016-05-03-04.md) This pair of meetings further explored the space around tuple syntax, pattern matching and deconstruction. 1. Deconstructors - how to specify them 2. Switch conversions - how to deal with them 3. Tuple conversions - how to do them 4. Tuple-like types - how to construct them ## May 10, 2016 [C# Language Design Notes for May 10, 2016](LDM-2016-05-10.md) In this meeting we took a look at the possibility of adding new kinds of extension members, beyond extension methods. ## Jul 12, 2016 [C# Language Design Notes for Jul 12, 2016](LDM-2016-07-12.md) Several design details pertaining to tuples and deconstruction resolved. ## Jul 13, 2016 [C# Language Design Notes for Jul 13, 2016](LDM-2016-07-13.md) We resolved a number of questions related to tuples and deconstruction, and one around equality of floating point values in pattern matching. ## Jul 15, 2016 [C# Design Language Notes for Jul 15, 2016](LDM-2016-07-15.md) In this meeting we took a look at what the scope rules should be for variables introduced by patterns and out vars. ## Aug 24, 2016 [C# Design Language Notes for Aug 24, 2016](LDM-2016-08-24.md) After a meeting-free period of implementation work on C# 7.0, we had a few issues come up for resolution. 1. What does it take to be task-like? 2. Scope of expression variables in initializers ## Sep 6, 2016 [C# Language Design Notes for Sep 6, 2016](LDM-2016-09-06.md) 1. How do we select `Deconstruct` methods? ## Oct 18, 2016 [C# Language Design Meeting Notes, Oct 18, 2016](LDM-2016-10-18.md) 1. Wildcard syntax 2. Design "room" between tuples and patterns 3. Local functions 4. Digit separators 5. Throw expressions 6. Tuple name mismatch warnings 7. Tuple types in `new` expressions ## Oct 25-26, 2016 [C# Language Design Meeting Notes, Oct 25-26, 2016](LDM-2016-10-25-26.md) 1. Declaration expressions as a generalizing concept 2. Irrefutable patterns and definite assignment 3. Allowing tuple-returning deconstructors 4. Avoiding accidental reuse of out variables 5. Allowing underbar as wildcard character ## Nov 1, 2016 [C# Language Design Meeting Notes, Nov 1, 2016](LDM-2016-11-01.md) 1. Abstracting over memory with `Span` ## Nov 15, 2016 [C# Language Design Meeting Notes, Nov 15, 2016](LDM-2016-11-15.md) 1. Tuple name warnings 2. "Discards" ## Nov 16, 2016 [C# Language Design Meeting Notes, Nov 16, 2016](LDM-2016-11-16.md) 1. Nullable reference types ## Nov 30, 2016 [C# Language Design Meeting Notes, Nov 30, 2016](LDM-2016-11-30.md) 1. Scope of while condition expression variables 2. Mixed deconstruction 3. Unused expression variables 4. Declarations in embedded statements 5. Not-null pattern ## Dec 7 and 14, 2016 [C# Language Design Meeting Notes, Dec 7 and 14, 2016](LDM-2016-12-07-14.md) 1. Expression variables in query expressions 2. Irrefutable patterns and reachability 3. Do-while loop scope ================================================ FILE: meetings/2017/CLR-2017-03-23.md ================================================ 2017-03-23 CLR Behavior for Default Interface Methods ========================================== Met today with - [Neal Gafter](https://github.com/gafter) - [David Wrighton](https://github.com/davidwrighton) - [Aleksey Tsingauz](https://github.com/AlekseyTs) - [Yi Zhang (CLR)](https://github.com/yizhang82) To discuss the CLR handling of default interface methods. ### (1) Runtime handling of ambiguities Question (1) is about what the CLR should do when the runtime does not find a most specific override for an interface method. For example, if this program is compiled (each interface being in a separate assembly) ``` c# public interface IA { void M(); } public interface IB : IA { override void IA.M() { WriteLine("IB"); } } public interface IC : IA { } class C : IB, IC { static void Main() { IA a = new C(); a.M(); } } ``` and then subsequently `IC` is changed ``` c# public interface IC : IA { override void IA.M() { WriteLine("IB"); } } ``` This would cause an error at compile-time if class `C` were recompiled (because there is no most specific override for `IA.M`). However, what happens if this program is run without recompiling `C`? There are two most choices: 1. The runtime selects, deterministically, between the implementations of `IA.M` in `IB` and `IC`; or 2. The runtime fails at some time before this invocation is run. a. At load time; or b. At the time of the invocation We did not have consensus which approach is preferred; there are advantages and disadvantages to either. We will defer this issue until it can be discussed in a larger group. ### Static methods Static methods are already permitted in interfaces. ### Protection levels From the point of view of the CLR, once we start accepting a protection level other than private, we might as well accept all of them. The semantics are clear. With the possible exception of `protected` and its related protection levels. We would need to clearly define both the language and runtime constraints implied by this protection level. For example, is a `protected` member in an interface accessible in a class that implements the interface? What would be the syntax in source (note that interface members are not inherited into classes that implement them)? What would be the verifiable form of the generated IL? We tentatively agreed that the CLR will accept protection levels other than `public` in interfaces, but open questions remain about `protected`. ### Sealed override It is an open question whether a `sealed override` should be permitted within an interface. Given the most specific override rule, such code could prevent unrelated sister interfaces from implementing the method. If it were permitted, we need to check that there is a way to represent it in IL. ### Non-virtual methods in interfaces Non-virtual methods in interfaces are not a problem for the CLR, presuming we have a way to represent them. We need to check that existing interface methods have a `newslot` bit set. We think so. If so, then we would simply omit that from non-virtual methods. ### Implicit overrides in interfaces We agree that the compiler-generated IL for an "implicit" override ``` c# public interface IA { void M(); } public interface IB : IA { override void M() { WriteLine("IB"); } } ``` should be roughly the same as for an "explicit" override ``` c# public interface IB : IA { override void IA.M() { WriteLine("IB"); } } ``` In both cases the compiler produces a `methodimpl` record for the overridden method(s). The CLR will not consider a method in an interface to be an implementation of another method without the `methodimpl` bit. We should confirm that we are OK with the binary compatibility implications of this. All override declarations should omit the `newslot` bit to ensure no new vtable slot is allocated. ### Open Question: Which interfaces are searched There is an open question which interfaces are searched for implementations, and in which order. For example, if we have ``` c# interface IA { void M() {} } interface IB: IA { override void M() {} } class Base : IB { } class Derived : Base, IA { } ``` Then is the override appearing in `IB` the final override selected for class `Derived`, even though `IB` does not appear in the interface list for `Derived`? ### Open Question: Diamond Inheritance between Class and Interface What should occur in cases involving diamond inheritance with classes and interfaces: ``` c# interface IA { void M() {} } interface IB: IA { override void M() {} } class Base : IA { void IA.M() { } } class Derived : Base, IB { } ``` In this case neither `Base` nor `IB` is a subtype of the other. Is class `Derived` legal? If so, what is its implementation of `IA.M`? ================================================ FILE: meetings/2017/LDM-2017-01-10.md ================================================ # C# Language Design Notes for Jan 10, 2017 ## Agenda - Discriminated unions via "closed" types # Discriminated unions via "closed" types There's a [proposal](https://github.com/dotnet/roslyn/issues/8729) to allow adding `closed` to an abstract type. This prevents inheriting from any other assembly, meaning that there would be a known set of derived types, all in the same assembly as the closed abstract type. This is somewhat similar to Scala's case classes. At the metadata level, this could possibly be implemented by generating an internal abstract member. In fact that member could make itself useful as a property returning a tag, so we can do efficient tag-based switching. A nice aspect is that `closed` can be added to existing hierarchies, adding a notion of completeness and protecting from "unauthorized" inheritance. Adding this to existing code may lead to completeness warnings in consuming code. In a lot of places where you switch, you don't even *want* completeness. It's almost like it's something you have to ask for at the switch site. Special syntax? "Catch all" is a problem. Often I want to have a catch all *even* as I want to be told if there's a new case. We *could* say that if there's a closed type, then you need to be complete in a switch. Which you can be with a `default`, but then you won't know if there's a new case: it's up to you. Enums: We could allow `closed` on those, and then eliminate the explicit conversion to it, as well as arithmetic. Switching could generate a default that throws, in case someone monkeyed an illegal value in there. A closed enum wouldn't get much value out of being an enum, since we don't want operators to work on them. We could consider making them not be enums from the runtime perspective. ## Conclusion An interesting approach to discriminated unions that might be a better fit with the spirit of C#. ================================================ FILE: meetings/2017/LDM-2017-01-11.md ================================================ # C# Language Design Notes for Jan 11, 2017 *Raw notes - need cleaning up* ## Agenda - Language aspects of [compiler intrinsics](https://github.com/dotnet/roslyn/issues/11475) Intrinsics Two compiler behaviors: 1. Recognize declarations as intrinsics, and implement them 2. Enforce whichever special rules apply on call Could grow the list over time. Scenario: avoid il rewrite or inefficient impls Lets you do cross-platform libraries. This feels cheap, and limits the implact to just the compiler. Can't be abstracted out into generic methods - at least without combinatorial explosion Some might be obsoleted over time as language features ("static delegates"?) come along. Could allow optionally specifying the intrinsic name in the attribute, so you could call the method something else if you like Open question: local functions. Wouldn't emit to metadata. How supported should this be, as a language feature? Should semantic analysis understand this, and give you good live feedback? Doesn't need a lot of tooling support out of the gate; it's a feature for a very small set of ``` c# switch(...) { case string s when .... x1: case int i when .... x2 & capture x1: case 1 when ... capture x1; break case string s when .... x3: case int i when .... x4: case 1 when .... & capture something break; } ``` The code needed to be surprised by the lifetime of expression variables in case headers being the whole switch block is quite convoluted (just like this sentence). ``` c# case 1 => WriteLine(...); case 2 { } case string s when (...) { ... } ``` - Broaden scope and lifetime of expr variables to whole switch block - Broaden lifetime of expr variables, but not the scope - Limit scope of variables in bodies of cases that are "new" - When there are expression variables in the case header, in the case body forbid: - ref locals - to expr variables - local functions - that capture case-section expr variables - labels - that are jumped to from other case sections Drop the subbullets for now, and maybe we can relax later Out of all these approaches, we still think expanding the lifetime (but not the scope) has the lowest risk. Fallout work is likely in the debugger, which will have a suboptimal experience in these scenarios. This work is probably puntable. ================================================ FILE: meetings/2017/LDM-2017-01-17.md ================================================ # C# Language Design Notes for Jan 17, 2017 ## Agenda A few C# 7.0 issues to review. 1. Constant pattern semantics: which equality exactly? 2. Extension methods on tuples: should tuple conversions apply? # Constant pattern semantics [Issue #16513](https://github.com/dotnet/roslyn/issues/16513) proposes a change to the semantics of constant patterns in `is` expressions. For the code ``` c# e is 42 ``` We currently generate the call `object.Equals(e, 42)` (or equivalent code), but we should instead generate `object.Equals(42, e)`. The implementation of `object.Equals` does a few reference equality and null checks, but otherwise delegates to the instance method `Equals` of its *first* argument. So with the current semantics the above would call `e.Equals(42)`, whereas in the proposal we would call `42.Equals(e)`. The issue lists several good reasons, and we can add more to the list: - The constant pattern isn't very *constant*, when it's behavior is determined by the non-constant operand! - Optimization opportunities are few when we cannot depend on known behavior of calling `c.Equals` on a constant value. - Intuitively, the pattern should do the testing, not the object being tested - Calling a method on the expression could cause side effects! - The difference from switch semantics is jarring - Switching would preserve the nice property of `is` expressions today that it only returns `true` if the left operand is implicitly convertible to the (type of the) right. There really is no downside to this, other than the little bit of work it requires to implement it. ## Conclusion Do it. # Extension methods on tuples [Issue #16159](https://github.com/dotnet/roslyn/issues/16159) laments the facts that extension methods only apply to tuples if the tuple types match exactly. This is because extension methods currently only apply if there is an *identity, reference or boxing conversion* from the receiver to the type of the extension method's first parameter. The spirit of this rule is that if it applies to a type or its bases or interfaces, it will work. We agree that it *feels* like it should also work for tuples - at least "sometimes". We cannot make it just always work for tuple conversions, though, since they may recursively apply all kinds of conversions, including user defined conversions. We could check *recursively* through the tuple type for "the right kind of conversion". Compiler-wise this is a localized and low-risk change. It makes tuples compose well with extension methods. It's another place where things should "distribute over the elements" of the tuple. This is a now-or-never kind of change. It would be a breaking change to add later. ## Conclusion Try to do it now if at all possible. ================================================ FILE: meetings/2017/LDM-2017-01-18.md ================================================ # C# Language Design Notes for Jan 18, 2017 *Raw notes - need cleaning up* ## Agenda - Async streams (visit from Oren Novotny) # Cancellation We decided per enumerator. This means: Can't have an enumerator be shared between multiple threads. You can imagine channel-like data sources that provide enumerators but each enumerator gets a distinct set of values. The interface is just giving you an access pattern, not a contract. They could be "hot" or "cold" / repeatable or not. Should we be close to Channel or is that something else? Should not force people to provide a token. For scenarios where that's needed, use analyzers. ``` c# struct AsyncEnumerableWithCancellation(IAsyncEnumerable src, CancellationToken ct) { public IAsyncEnumerator GetAsyncEnumerator() => src.GetAsyncEnumerator(ct); } public static class AsyncEnumerable { public static AsyncEnumerableWithCancellation WithCancellation(this IAsyncEnumerable src, CT ct) => new AEWC(...); } ``` `IAsyncEnumerable` itself could expose a nullary GetAsyncEnumerator in one of the following ways: 1. have another overload :-( 2. have a default parameter (CLS :-() 3. have an extension method (requires the extension method in scope) As an alternative, the compiler can know to pass a default CT ``` c# public interface IAsyncEnumerable { IAsyncEnumerator GetAsyncEnumerator(CancellationToken ct = default); } ``` Can use same trick for `ConfigureAwait`. Can combine the two sneakily. # Iterators How do you get the cancellation token? - You don't unless you're implementing the GetAsyncEnumerator yourself - There's special syntax to get at the state machine ``` c# public async IAsyncEnumerable Where(this IAE src, Func> pred)(CT token) { foreach (await var v in src.With(token: token)) { if (await pred(v, token)) yield return v; } } ``` This is overly specific. But a general solution might be a big piece of work, and would possibly eliminate the "shared -able/-tor" optimization. Options: - As above: special magic for iterators - an intrinsic method to get the token - other special syntax to get at the state machine - more general: single-method interfaces, anonymous classes... Can't make IAsyncEnumerable a delegate type,because we want to implement it on specific classes. Could we make IAsyncEnumerator a delegate? Would need to reduce to one method. But that can't both be covariant and return a Task\. Iterators may not be the *most* highly used feature, and it may be ok if this is a bit woolly. But it's *super* useful when you need it. # LINQ Would want overloads with async delegates (using `ValueTask` for efficiency) Syntax? - no different, when you use await we automatically add "async" to lambda - start the whole querty with `async` to "opt in" - `async` on each clause that needs it Need to think about what's intuitive, and what's useful Adding query operators with async delegates is probably a breaking change, unless we give them new names. `WhereAsync` etc. We need to do some experimenting. We may want overloads that take `IEnumerable` and async functions, and return `IAsyncEnumerable`. # ConfigureAwait # Performance # Batching # Syntax # # Cancellation Prior conclusion: Should be specified *per iteration*, i.e. every time `GetEnumerator` is called. Best shot: - Make it an (optional?) parameter to `GetEnumerator` - Provide a `WithCancellation` extension method on `IAsyncEnumerable` ``` c# public interface IAsyncEnumerable { IAsyncEnumerator GetEnumerator(CancellationToken token); IAsyncEnumerator GetEnumerator() => GetEnumerator(default(CancellationToken)); } public static class AsyncEnumerable { class AsyncEnumerableWithCancellation : IAsyncEnumerable{ ... } public static IAsyncEnumerable WithCancellation(this IAsyncEnumerable enumerable, CancellationToken token) => new IAsyncEnumerableWithCancellation(enumerable, token); // Or public static IAsyncEnumerator WithCancellation(this IAsyncEnumerable enumerable, CancellationToken token) => enumerable.GetEnumerator(token); } ``` Options here: `GetEnumerator` can have - One overload that takes a `CancellationToken` - One overload that takes an optional `CancellationToken` - Two overloads: One that takes a `CancellationToken` and one that doesn't. This works best if we have default interface member implementations The `await foreach` rewrite can - Work just on enumerables, requiring them to carry in a `CancellationToken` - Work just on enumerables, but offering a syntax to pass in a `CancellationToken` - Work also on enumerators, offering the option to create one manually with a `CancellationToken` `WithCancellation` can - Return a wrapper Enumerable that holds on to the `CancellationToken`, and passes it in on `GetEnumerator` calls - Be just a wrapper for `GetEnumerable`, passing the token along - Be deemed unnecessary # Iteration The `IAsyncEnumerator` interface needs to be covariant, so `T` must occur as a return type only. The most obvious candidate is this: ``` c# public interface IAsyncEnumerator { Task MoveNextAsync(); T Current { get; } } ``` # Controlling asynchronous getting It would be nice to provide the ability to understand if there's stuff "queued up", in order to make a decision whether to trigger the next get. There seems to be two approaches: - Provide a method on the interface to `TryMoveNext` synchronously. - Facilitate explicit chunking/batching `TryMoveNext` could look like: ``` bool? TryMoveNext(); ``` Where `true` and `false` are the usual `MoveNext` results, and a `null` would mean you need to call `MoveNextAsync` to know whether there's more left. The problem with this approach is that it's not meaningful to everyone, and some would not even be able to implement it usefully. Explicit chunking is something that you could expose if you want to, just using existing interfaces: ``` c# public IAsyncEnumerable> GetElementsAsync(); ``` Now the problem is that query operators and other things no longer work in terms of the core element type. You'd need some nifty type gymnastics to expose the enumerator pattern in terms of the nested collections, but implement the `IAsyncEnumerator` interface in terms of the core element type. It would also be hard for utility methods like queries to wire through this knowledge of whether a next element is available. # Async foreach What's the syntax? ``` c# foreach await (var v in src) ... await foreach (var v in src) ... foreach (await var v in src) ... ``` How does it deal with cancellation? (see above) - through explicit syntax - by being able to take enumerators as well as enumerables? - not at all - left to the library (calls `GetEnumerator()` with no arguments) How about disposing? - look for `IDisposable`? - look for `IAsyncDisposable`? - Both? Neither? # Iterators Could be like current iterators. Problem: How is cancellation exposed to the iterator body? - special syntax? - if you want to access cancellation, use the iterator to write the enumerator, not the enumerable? Both are painful ================================================ FILE: meetings/2017/LDM-2017-02-14.md ================================================ # C# Language Design Notes for Feb 14, 2017 *Upcoming meeting* ## Agenda - Meet with Unity to discuss language features relevant to game developers ================================================ FILE: meetings/2017/LDM-2017-02-15.md ================================================ # C# Language Design Notes for Feb 15, 2017 *Upcoming meeting* ## Agenda - Design Review ================================================ FILE: meetings/2017/LDM-2017-02-21.md ================================================ # C# Language Design Notes for Feb 21, 2017 ## Agenda We triaged some of the [championed features](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22), to give them a tentative milestone and ensure they had a champion. As part of this we revisited potential 7.1 features and pushed several out. 1. Implicit interface implementation in Visual Basic *(VB 16)* 2. Delegate and enum constraints *(C# X.X)* 3. Generic attributes *(C# X.0 if even practical)* 4. Replace/original *(C# X.0 if and when relevant)* 5. Bestest betterness *(C# 7.X)* 6. Null-coalescing assignments and awaits *(C# 7.X)* 7. Deconstruction in from and let clauses *(C# 7.X)* 8. Target-typed `new` expressions *(C# 7.X)* 9. Mixing fresh and existing variables in deconstruction *(C# 7.1)* 10. Implementing `==` and `!=` on tuple types *(C# 7.X)* 11. Declarations in embedded statements *(No)* 12. Field targeted attributes on auto-properties *(C# 7.1)* # Implicit interface implementation in Visual Basic [Champion "Implicit interface implementation"](https://github.com/dotnet/vblang/issues/38) This long-requested feature in VB would also help deal with problems related to [default implementations of interface members](https://github.com/dotnet/csharplang/issues/52). ## Conclusion Candidate for VB 16. # Delegate and enum constraints [Champion "Generic constraint `delegate` (or allow `System.Delegate` as a constraint)"](https://github.com/dotnet/csharplang/issues/103) [Champion "Generic constraint `enum` (or allow `System.Enum` as a constraint)"](https://github.com/dotnet/csharplang/issues/104) This is a common request. Could - semantically require an actual delegate/enum or just allow the base class - syntactically show up as either the base class or the keyword For delegates we need to consider how to deal with `MulticastDelegate`. ## Conclusion Candidates for a minor C# version. # Generic attributes [Champion "Allow Generic Attributes"](https://github.com/dotnet/csharplang/issues/124) Even though this would work in principle, there are bugs in most versions of the runtime so that it wouldn't work correctly (it was never exercised). We need a mechanism to understand which target runtime it works on. We need that for many things, and are currently looking at that. Until then, we can't take it. ## Conclusion Candidate for a major C# version, if we can make a sufficient number of runtime versions deal with it. # Replace/original [Champion "Replace/original and code generation extensions"](https://github.com/dotnet/csharplang/issues/107) If there's renewed investment in source generators, we'll add this, but this is not driven from the language end. ## Conclusion Candidate for a major C# version, if and when source generators or similar efforts take place. # Bestest betterness [Champion "Bestest Betterness"](https://github.com/dotnet/csharplang/issues/98) We want to do it, but not low-hanging enough for C# 7.1 time frame. ## Conclusion Candidate for a minor C# release in the 7.X wave. # Null-coalescing assignments and awaits [Champion "Null-coalescing assignments"](https://github.com/dotnet/csharplang/issues/34) [Champion "Null-conditional await"](https://github.com/dotnet/csharplang/issues/35) These need a little more design work and general bake time than what 7.1 allows. `await?` is an odd kind of keyword, but changing `await` to not throw on null may also be an issue. Thought needed. ## Conclusion Candidate for a minor C# release in the 7.X wave. # Deconstruction in from and let clauses [Champion "deconstruction in from and let"](https://github.com/dotnet/csharplang/issues/189) Too many worms in the can for 7.1. ## Conclusion Candidate for a minor C# release in the 7.X wave. # Target-typed `new` expressions [Champion "Target-typed `new` expression"](https://github.com/dotnet/csharplang/issues/100) Links well with target typed `default` expressions, but too much design and potential fallout for 7.1. Interaction with anonymous objects as well. ## Conclusion Candidate for a minor C# release in the 7.X wave. # Mixing fresh and existing variables in deconstruction [Champion "Mix Declarations and Variables in Deconstruction"](https://github.com/dotnet/csharplang/issues/125) This rounds out the C# 7.0 experience, and was a late design change we didn't get to implementing. May lead to occasional confusion, as in `M((int x, y) = e)` (declaring `y`)? We are OK with that. ## Conclusion Candidate for C# 7.1. # Implementing `==` and `!=` on tuple types [Champion "Support for == and != on tuple types"](https://github.com/dotnet/csharplang/issues/190) There's design work. It's not just call `.Equals` recursively. `==` today works between int and byte, for instance. We should probably make `(i1, i2) == (b1, b2)` work with that same equality. Also for generics, for a type parameter `T` and a `t` of that type, we should allow `(t, i) == (null, 0)` by recursively applying the special "compare to null" semantics of equality over type parameters of C# today. Too big for 7.1. ## Conclusion Candidate for a minor C# release in the 7.X wave. # Declarations in embedded statements Now that you can declare expression variables in an embedded statement, it's odd that old-fashioned declaration statements are forbidden in an embedded position (such as an if-branch, for instance). However, now that you can use a discard instead of a dummy variable declaration, the motivating scenario also goes away. ## Conclusion Let's not do it. # Field targeted attributes on auto-properties [Champion "Auto-Implemented Property Field-Targeted Attributes"](https://github.com/dotnet/csharplang/issues/42) Technically a breaking change, since the field target is already permitted today, but ignored. We don't expect anyone to be relying on this, though! ## Conclusion Candidate for 7.1. ================================================ FILE: meetings/2017/LDM-2017-02-22.md ================================================ # C# Language Design Notes for Feb 22, 2017 ## Agenda We went over the proposal for `ref readonly`: [Champion "Readonly ref"](https://github.com/dotnet/csharplang/issues/38). # readonly refs Passing and returning by ref addresses the copying and out parameter nightmare of performance critical code over structs. However, the use of it comes with risk: the recipient of a passed or returned ref can freely modify it. This proposal introduces a `readonly` modifier on ref parameters and returns that incurs restrictions on the recipient of the ref similar to those incurred by a `readonly` modifier on a field. Thus, a ref can safely be passed or returned without risk of the recipient mutating it. This eliminates one common source of (deliberate, defensive) struct copying in C# code. ## Conclusion We want to support this. # readonly struct types In and of itself, though, readonly refs contribute to another source of struct copying: when a method or property is invoked on a `readonly` struct (including now a readonly ref parameter or local), the C# compiler *implicitly* copies the struct, as a defense against the method or property invocation mutating the original struct. In the common case where the member is not actually mutating the struct, this is pure waste. To counter this, the proposal also allows the `readonly` modifier on struct type declarations themselves. This signifies that no function member is mutating, and therefore calling them does not need to incur a copy. Inside struct members, `this` would be a readonly ref instead of a ref. Once we have this feature, we could start warning on the compiler's defensive copying (with a warning wave for back compat). More detailed versions are possible, where readonly-ness can be per struct member. This may not be necessary in practice, based on experience from the Midori project. (Or we can make it not strictly enforced, or allow exceptions that are somehow marked.) ## Conclusion Support `readonly` just on whole struct types for now, until and unless evidence shows the need for per-member decisions. # Syntax We've been calling this feature "readonly ref", but it is really not the ref that is readonly; rather it is the thing that is referenced. For that reason is more correct to call it `ref readonly`, as in: ``` c# public ref readonly int Choose(ref readonly int i1, ref readonly int i2) { ... } ``` Currently, refs *themselves* are always single-assignment in C#, but if we decide at some point to make them reassignable by default (which we could do without breaking), then you might want to be able to *explicitly* put a `readonly` modifier on the refs themselves. That would be `readonly ref`. And of course you could have `readonly ref readonly` where both the ref and the target are readonly. ## `in` parameters `ref readonly` is a bit of a mouthful. For parameters, `ref readonly` is in fact the exact opposite of `out`: something that comes in but cannot be modified. It would make total sense to call them `in` parameters. However, it would probably be confusing to call a *return* value `in`, so `in` should be allowed as a shorthand for `ref readonly` exclusively on parameters. ``` c# public ref readonly int Choose(in int i1, in int i2) { ... } // Equivalent to the above ``` ## Call site annotations With today's refs you need a call site `ref` annotation. This is to warn the caller of potential side effects and make sure they buy into them. Do we need it for `ref readonly` parameters, where there are no side effects by definition? We believe not. ``` c# int x = 1, y = 2; int z = Choose(x, y); // implicitly passed by readonly ref ``` We didn't question the `ref` annotation on return statements in `ref readonly` returning methods, but maybe we should. ## Values as `in` arguments? The proposal allows literals to be passed as an `in` argument. A variable is created under the hood, assigned the value and passed along. Is that bad? On the one hand, it makes for less predictable performance, in that some arguments cause copying (into a fresh variable), and others do not. On the other hand, since no `ref` is required, the arguments look like value arguments, that are already copied. At least initially, as we prototype this, we will allow any expression of the right type, and we will just copy into a shadow variable when necessary. Even when a variable of a different but convertible type is passed. If this turns out to be a problem, or a cause of confusion or overly defensive programming, then we'll reevaluate. # Extension methods We want extension methods on structs to be able to work by ref, so that they don't copy. VB already allows this. We should allow both ref and ref readonly extension methods on value types. Ref extension methods on classes could be controversial - like an `EnsureNotNull` method that replaces the object reference in the variable. We are not necessarily opposed to this in principle, but we don't need it for this scenario, and we'd have to track down weird consequences of it. So for now, extension methods with `ref` and `in` this-parameters must extend a value type. # Readonly parameters and locals A separate feature, but seems to be part of the same package, and is a long standing request. Let's try to do these at the same time. # Versioning What happens when older code references newer code with ref readonly return types? The old compiler might not understand the `readonly` annotation, and would happily allow mutation. Upgrade the compiler, and you're broken. Can we encode it in metadata in such a way that it breaks downlevel? modreqs? Possibly, but then putting it on existing libraries is a library breaking change. The CLR already does not protect readonly-ness, and there are therefore already ways you can circumvent it and mutate anyway. You can't avoid bad actors (reflection etc), but we would like to avoid accidentally breaking guarantees, and also breaking code. ## Conclusion We'll build the first version using attributes, and therefore leaving us open to breaking on upgrade. We'll consider if there are mitigations, but poisoning downlevel code, e.g. with modreqs, seems like too big of a hammer. ================================================ FILE: meetings/2017/LDM-2017-02-28.md ================================================ # C# Language Design Notes for Feb 28, 2017 *Quote of the Day:* "I don't have time to dislike your proposal, but I do!" ## Agenda 1. Conditional operator over refs (*Yes, but no decision on syntax*) 2. Async Main (*Allow Task-returning Main methods*) # Conditional operator over refs [Champion "conditional ref operator"](https://github.com/dotnet/csharplang/issues/223) Choice between two variables is not easily expressed, even with ref returns. If array `a1` is non-null, you want to modify its first element, otherwise you want to modify the first element of `a2`: ``` c# Choose(a1 != null, ref a1[0], ref a2[0]) = value; // Null reference exception ``` It would be nice if the ternary conditional operator just worked with refs in the branches: ``` c# (a1 != null ? ref a1[0] : ref a2[0]) = value; // Right! ``` Both branches would have `ref`, and would need to evaluate to variables of the same type. ## Syntax With the syntax as proposed, there's a concern that there will be an abundance of `ref` keywords in common code, leading to confusion. For instance, to store the selected variable above in a ref local would be: ``` c# ref int r = ref (a1 != null ? ref a1[0] : ref a2[0]); // Four "ref"s ``` Alternative proposal: Do not add new syntax for this. Instead, if both branches of an existing conditional expression are variables of the same type, let the compiler treat the conditional expression as a whole as a variable: ``` c# (a1 != null ? a1[0] : a2[0]) = value; ref int r = ref (a1 != null ? a1[0] : a2[0]); // Two "ref"s ``` This does have a problem that it would subtly change semantics of existing code. Think of a value type `S` that has a mutating method `M`: ``` c# (b ? v1 : v2).M(); ``` This would now mutate the *original* `v1` or `v2` of type `S`, instead of a copy! In practice such code today is probably buggy: why would you want to call a method to mutate a throw-away copy? But the fact remains that this would be a breaking change, unless we think up some very insidious mitigation. An example of such a scheme would be to decide that a conditional is a variable, not based on what's *inside* it, but on what's *around* it. Whenever it's being used as a variable ( An approach is to define variable-ness of ternaries not from the inside out, but by "reinterpreting" it whenever it occurs in variable contexts not allowed today (ref, assignment). ## Readonly and safe-to-return This should also work for ["ref readonly"](https://github.com/dotnet/csharplang/issues/38), if and when that goes in. For that to work, we'd either need to require further syntax (`ref readonly` in the branches) or infer readonly-ness from the branches. The latter is more appetizing. We could: 1. require both or neither branch be readonly 2. infer that the conditional expression is readonly if at least one branch is readonly Let's do 2. There seems to be no good reason for a stronger restriction. Similarly we need to establish whether the resulting ref is safe-to-return. We can infer that the resulting ref is safe-to-return if both branches are safe-to-return. ## Order of evaluation There are subtle differences between the current spec and implementation when it comes to order of evaluation of an assignment. We need to make sure we fully understand how that plays in when the left hand side of an assignment is a conditional expression. ## Conclusion - Yes to doing it - Syntax: leaning towards no refs inside (recursively), but worried about breaking changes - Precedence not an issue - `ref` is not part of the expression - Field like events: sure, if it makes sense # Async Main [Champion "Async Main"](https://github.com/dotnet/csharplang/issues/97) We want to allow program entry points (`Main` methods) that contain `await`. The temptation is to allow the `async` keyword on existing entry points returning `void` (or `int`?). However, that makes it *look* like an `async void` method, which is fire-and-forget, whereas we actually want program execution to wait for the main method to finish. So we'd have to make it so that if the method is invoked as an *entry* point we (somehow) wait for it to finish, whereas if it's invoked by another method, it is fire-and-forget. That is not ideal. Instead, we should allow new entry points returning `Task` and `Task`: ``` c# Task Main(); Task Main(string[] args); Task Main(); Task Main(string[] args); ``` Since such methods can exist today, but are not entry points, we should give precedence to existing entry points for backwards compatibility. These new entry points are then entered as if being called like this: ``` c# Main(...).GetAwaiter().GetResult(); ``` In other words, they rely on the built-in capability of `Task` awaiters to block on the getting the result. C# 7.0 allows declaration of other "tasklike" types. We won't allow those in entry points yet, but that is easy to work around: Just `await` your tasklike in an `async Task Main`. ## Conclusion Allow `Task` and `Task` returning `Main` methods as entry points, invoking as `Main(...).GetAwaiter().GetResult()`. ================================================ FILE: meetings/2017/LDM-2017-03-01.md ================================================ # C# Language Design Notes for Mar 1, 2017 ## Agenda 1. Shapes and extensions (*exploration*) 2. Conditional refs (*original design adopted*) # Shapes and extensions [Exploration: Shapes and Extensions](https://github.com/dotnet/csharplang/issues/164) The write-up explores a version of "type classes" for C#, that can be implemented by types by means of extension declarations. It is aimed at improving the ability to adapt code, and to abstract over shapes of types without having to predict the need when the types are declared. This is static approach, where you imbue types with new members *statically*, but they are not part of the objects at runtime. This can be contrasted with runtime ideas of * "protocols", understood as real, runtime types that objects can fit "after the fact" * "extension interfaces", where a type can be made to implement an interface in separate code Both of these lead to objects having new types at runtime, whereas type classes - the present proposal - create a compile time "illusion", which leads to certain different trade-offs. These are competing approaches to attain many of the same goals, and in the end we'd have to weigh the trade-offs and decide one approach to go with. It's possible that we could devise a middle ground between the more static and the more dynamic approaches. ## Implementation One such trade-off is whether the runtime needs to change. The type-classes proposal comes with an efficient implementation strategy on top of the existing runtime. The implementation exploits generic specialization for value types. This leads to many instantiations of the same generic type at runtime, which usually isn't too much of a concern from a memory perspective, but does add significantly to startup cost. Precompilation mechanisms such as NGEN can help with this, but those vary between platforms. The proposal is assuming no changes to the runtime, but you could make the runtime do better and understand more, even in compatible ways. This is worth exploring. ## Impedance mismatch One downside of a static approach is that it relies on translation into different runtime concepts. Therefore the language-level compile-time world ends up differing more from the run-time world as seen through reflection and from other languages. And we have to remember that such "other languages" include existing versions of C#, unless the feature implementation does something to specifically "poison" the code for down-level consumption. It's worth calling out, for instance, that many approaches to dependency injection rely on selecting between interface implementations through reflection. This would probably not work well with the "shapes" of the proposal, as the "implements" relationship is no longer evident at runtime. This particular proposal goes further in hiding the details of the translation than e.g. [the proposal](https://github.com/MattWindsor91/roslyn/blob/master/concepts/docs/csconcepts.md) it is inspired by. While that leads to a simpler user-level model perhaps, it also hides details that are important at the next level down (such as extra type parameters!), and which would need to be reconciled with older versions of the C# compiler encountering this in libraries, as well as with other languages. A runtime approach would be able to keep a faithful representation of the language-level world. This would let typechecks and reflection "get it right", and counteract the language and runtime gliding further away from each other. However, it would lead to challenges interacting with existing code: If new concepts are introduced at the assembly level, older compilers are likely to be confounded, and effectively prevented from consuming it. ## Issues with the specific approach The proposal uses "extension declarations", as already envisioned through ["extension everything"](https://github.com/dotnet/roslyn/issues/11159) proposals, to also "witness" a type implementing a shape. This is intended to lower the concept count and unify some related ideas, but it also can be seen as too much of a conflation. The disambiguation scheme in the proposal will not work when type parameters are constrained by more than one shape, each of which needs to be witnessed. We should make sure we think through shapes that have more than one type parameter. ## Conclusion We should keep talking about ways to "add types to objects", and in particular try to do a similar exploration of more runtime-based approaches, so that we can compare. # ref conditional operator In the [previous meeting](LDM-2017-02-28.md) we left open the syntax for the conditional operator over variables: ``` c# cond ? ref v1 : ref v2 // original proposal cond ? v1 : v2 // alternative: just figure out if it's variable or not ``` The alternative was considered on account of being more succinct. However, it leads to problems. On the semantic level: ``` c# (cond ? v1 : v2).M(...); // breaking change? ``` What if `v1` and `v2` are of a value type, and `M` is a mutating method? Today `M` mutates a copy, which is probably silly and a bug. But it's been harmless for a long time, and there may be people depending on it. - If we change the behavior to mutate the selected variable, then that's a breaking change - If we don't, then you cannot as easily express mutating the original variable when it is *actually* useful There's also a more implementation-oriented issue: ``` c# (cond ? v1 : v2).M(await ...); // can't "spill" ``` When generating code for an await, we need to "spill" the stack, including any variable refs sitting on it, to the heap. Since refs cannot be in the heap, we do tricks: since we know the variable may need spilling, we don't actually resolve it in place. Instead we keep around the constituent parts (e.g. an array and an index, an object and a member, etc) and only look it up *after* the code has resumed after the await. But here we cannot easily store the constituent parts, because we won't know which branch of the conditional will end up getting executed, and each may lead to different forms of constituent parts! While there may be extremely sneaky and possibly expensive ways around this, we could also just forbid the await. However, the error message would be very strange indeed if it came on an ordinary shape of conditional, whereas it is easier to say referring to a special syntactic form of the conditional. ## Conclusion On the whole, these challenges lead us to prefer the original proposal with the explicit occurrences of `ref` in the syntax. ================================================ FILE: meetings/2017/LDM-2017-03-07.md ================================================ # C# Language Design Notes for Mar 7, 2017 Quote of the Day: "Now `(T)default` means the same as `default(T)`!" ## Agenda We continued to flesh out the designs for features currently considered for C# 7.1. 1. Default expressions (*design adopted*) 2. Field target on auto-properties (*yes*) 3. private protected (*yes, if things work as expected*) # Default expressions [github.com/dotnet/csharplang/issues/102](https://github.com/dotnet/csharplang/issues/102) We plan to allow target typed `default` expressions, where the type is left off: ``` c# int i = default(int); // Current int i = default; // New var i = default; // Error: no target type ``` In essence, `default` is like `null`: an inherently typeless expression, that can acquire a type through implicit or explicit conversion. Unlike `null`, `default` can be converted to *any* type `T`, including non-nullable value types, and is equivalent to `default(T)` for that `T`. For types where both `null` and `default` are allowed, they both have the same value, namely null. ## Constants Like `null` and `default(T)`, `default` is a constant expression *for certain types*. ``` c# const string S = null; // sure const string S = default; // sure const int? NI = null; // error const int? NI = default; // error ``` This is just a consequence of the existing limitations on which types can be constants. ## Constant patterns Constant patterns are interpreted in context of the type of the expression they are tested against. For instance, if the left hand side of an `is` expression is of an enum type `E`, `0` is the zero value of that enum type, not the int zero: ``` c# E myE = ...; if (e is 0) ... // 0 means (E)0, not (int)0; ``` For `null`, this means that the null value created by the constant pattern is of the reference or nullable value type of the left hand side. A `null` pattern is not allowed if the type of the left hand side is a non-nullable value type, or a type parameter not known to be a reference type. As a special dispensation, however, `null` *is* allowed as a constant pattern even if the type of the left hand side is not one that allows constants. We will allow `default` as a constant pattern when the type of the matched expression is a reference type, a nullable value type or a constant type. ### case default As a special case (no pun intended), this decision means that it would be permitted to write `case default:` in a switch statement! That is surely going to be confused with a `default:` label, and is almost certainly either a bug or a very bad idea. For this particular syntactic pattern, therefore, we should yield a warning. The warning goes away if you parenthesize `case (default):` or add a type `case default(T):` or similar. ## Optional parameters Expressions of the form `default(T)` are explicitly allowed as default arguments of optional parameters even when non-constant. Therefore, so is `default`: ``` c# void M(MyStruct ms = default) { ... } // sure ``` ## Special forms Today `null` is specially allowed in a number of situations, where it is treated as a value even though it is not given a type. We have to decide whether `default` is similarly allowed in those places. Our general position is "no": While `null` conceptually has a specific value, even though its representation varies, `default` conceptually has different values depending on context. Therefore it doesn't make sense to treat it as a value in typeless contexts. Specifics listed out below. ### Equality We allow `null == null` as a special case, called out specifically in the language spec. We don't think we should allow `default == default`. Even though it would probably be true regardless of the type, it doesn't make sense with no type. ### is-expressions `null` is allowed on the left hand side of `is` expressions: ``` c# bool b = null is T; // allowed, always false ``` Should `default` similarly be allowed? It is important to note that the type `T` in these expressions is *not* a context type for the expression: the compiler does *not* convert the expression to the type. For instance, when you write ``` c# if (0 is DateTime) { ... } ``` The result is always *false* (the compiler even warns about it), even though 0 converts to the enum `DateTime`. This is because `0` is interpreted on its own, not through a literal conversion, and on its own it has the type `int`. The `int` zero is not a `DateTime`, so the result is false. It is essentially as if the expression was boxed to `object`, and *then* checked at runtime against the type. By the same reasoning, if we allowed `default` on the left hand side it would not mean `default(T)` but `default(object)`. That would surely be confusing to the programmer: ``` c# if (0 is int) { ... } // True: 0 is an int if (default(int) is int) { ... } // True: 0 is still an int if (default is int) { ... } // Would be false! default(object) is null, which is not an int. ``` For this reason, we will not allow `default` on the left hand side of an is-expression. ### as-expressions `null` is allowed on the left hand side of `as` expressions: ``` c# T t = null as T; // allowed, always null ``` Should `default` similarly be allowed? In case of `as`, the type is restricted to be a reference type or a nullable value type. Therefore `default` always meaning `null` wouldn't be surprising in the same way that it would be for the `is` operator. In this particular case, therefore, it seems alright to allow `default`, essentially as an alias for `null`. ### throw statements and expressions You are allowed to `throw null` in C#. It leads to a runtime error, which ironically causes a `NullReferenceException` to be thrown. So it is a sneaky/ugly idiom for throwing a `NullReferenceException`. There's no good reason to allow this for `default`. We don't think the user has any intuition that that should work, or about what it does. ## Using `default` versus `null` When should I use `default`, and when should I use `null`? Are we setting up for style wars? One proposal to circumvent this issue is to not adopt the `default` syntax, but instead use `null` - even for non-nullable value types. Not only would this be confusing, it would also be breaking: It would allow `null` to convert to e.g. `int`, which would affect overload resolution. Another idea is to allow `default` *only* when the type is not known to be a reference or nullable value type. So the two would be mutually exclusive, and you'd never have a choice. Let's not restrict it in the language. It would be a matter of style, and an IDE might even give you code style options about it. The recommendation would probably be `null` when we can, `default` otherwise, and `default(T)` when we have to. But you can imagine for instance using `default` for nullable things for uniformity, where a swath of code initializes multiple things, some nullable and some non-nullable. Not allowing that seems unnecessarily restrictive. ## Conclusion Adopt `default` expressions with the design decisions described above, targeting C# 7.1. # Field target on auto-properties [github.com/dotnet/csharplang/issues/42](https://github.com/dotnet/csharplang/issues/42) Through several releases we have neglected to make the field target on attributes work right when applied to auto-implemented properties. It should attach the attribute to the underlying generated field, just as it does when applied to field-like events. There are extremely esoteric ways in which this can be a breaking change; essentially when someone exploits unintended leniency in today's compiler. There is absolutely no benefit from such exploitation, and we don't imagine anyone would rely on it. If it turns out we're wrong, we can reintroduce the leniency specifically for the breaking case, but we don't think it is worth it here. ## Conclusion Adopt the feature with a current target of C# 7.1. # Private protected We have a complete feature design and a very nearly complete implementation. What we need in order to go forward is to test out a lot of things: - Do completion, refactorings, etc. work in IDEs? - How does it work with languages that don't understand it, including F# and older versions of C# - How much would it confuse other tools such as CCI? - Does this fully and completely work as expected in the runtime? Even though the feature is already allowed in C++/CLI, there's reason to suspect it wasn't exercised all that broadly, so there may be corner cases that don't work as expected. This feature would need to be added to both C# and VB for API parity reasons. We would need to do the work to implement it in VB. ## Conclusion If all those things work out well enough, we do want the feature. ================================================ FILE: meetings/2017/LDM-2017-03-08.md ================================================ # C# Language Design Notes for Mar 8, 2017 ## Agenda We looked at default interface member implementations. 1. Xamarin interop scenario 2. Proposal 3. Inheritance from interface to class 4. Overriding and base calls 5. The diamond problem 6. Binary compatibility 7. Other semantic challenges # Xamarin interop scenario Android interfaces are written in Java, and can therefore now have default implementations on members. Xamarin won't be able to seamlessly project those interfaces into .NET. On iOS, Objective-C and Swift have protocols, of the general shape: ``` c# protocol Foo { void Hello(); @optional int Color { get; set; } int Bye(); } ``` Again, the best we can do is to project the non-optional parts into C# interfaces, whereas the optional parts must be handled in less appetizing ways: ``` c# interface IFoo { void Hello(); } ``` # Proposal [The current proposal](https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md) is to allow the following in interfaces: - member bodies - nonvirtual static and private members - overrides of members Putting a concrete body on your method (etc), means you don't have to implement it on the class. Not proposing (but may want to look at): - non-virtual public instance members - protected and internal members - nested types - operator declarations - etc... Don't want: - state! - conversions This gives parity with where Java is: they made tweaks over time based on feedback, and this is where they landed (private and static members were added). The more you go to the side of adding things, the more this is a philosophy change for interfaces. # Inheritance from interface to class Important question: is the member inherited into the class, or is it "explicitly implemented"? Our assumption is that it's explicitly implemented: ``` c# interface I { int M() => 0; } class C : I { } // C does not have a public member "M" ``` That means you can't "just" refactor similar implementations into an interface the way you do into a base class. # Overriding and base calls Should overrides be able to call `base`? Yes, but we would need new syntax to deal with ambiguity arising from the fact that you can have more than one base interface: ``` c# interface I1 { void M() { WriteLine("1"); } } interface I2 : I1 { override void M() { WriteLine("2"); } } interface I3 : I1 { override void M() { WriteLine("3"); } } interface I4 : I2, I3 { override void M() { base(I2).M(); } } ``` The exact syntax for disambiguating base calls is TBD. Some ideas: ``` c# I.base.M() // what Java has base(I).M() // similar to default(I) base.I.M() // ambiguous with existing meaning base.M() // looks like generics ``` # The diamond problem `I4` above inherits two default implementations, one each from `I2` and `I3`, but explicitly provides a new implementation, so that there's never a doubt as to which one applies. However, imagine: ``` c# interface I5: I2, I3 { } class C : I2, I 3 { } ``` The class declaration of `C` above must certainly be an error, since there is no good (symmetric) unambiguous way of determining a default implementation. Also, the interface declaration of `I5` should be an error, or at least a warning. This means that adding an override to an interface can break existing code. This can happen in source, but depending on compilation order, it may also be possible to successfully compile such code. # Binary compatibility What should the runtime do when an interface adds a default implementation and that causes an ambiguity in code that isn't recompiled? Should this be a runtime error? It would lead to some hard-to-understand failures. Should it "just pick one"? "Why did you not let my program run?" vs "why did you not prevent this hole"? We need to decide what is less harmful. We should look at what Java does and why. # Other semantic challenges The feature reveals some "seams" between C# and the CLR in how they understand interface overriding. In the following, imagine `I`, `C` and the consuming code are in three different assemblies: ``` c# // step 1 interface I { } class C : I { public void M() { /* C.M */ } } // step 2 interface I { void M() { /* I.M */} } I i = new C(); i.M(); // calls I.M default implementation // step 3: recompile C I i = new C(); i.M(); // calls C.M non-virtual method ``` The problem here is that the runtime doesn't consider non-virtual members able to implement interface members, but C# does. To bridge the gap, C# generates a hidden virtual stub method to implement the interface member. However, during step 1 there is no interface member to implement, and during step 2 the class declaration isn't recompiled. The runtime doesn't consider `C.M` an implementation of `I.M`, so if you call `M` on a `C` through the `I` interface, you get the default behavior. As soon as `C` is recompiled, however, the compiler inserts its stub, and the behavior changes. We have to decide it there is something we can and will do about this. We may just accept it as a wart. ================================================ FILE: meetings/2017/LDM-2017-03-15.md ================================================ # C# Language Design Notes for Mar 15, 2017 *Quote of the Day:* "Little leek - isn't that called a spring onion?" *Quote of the Other Day:* "Who's champion for recursive patterns? It says 'see champion for recursive patterns'." ## Agenda Triage of championed features 1. JSON literals 2. Fixing of captured locals 3. Allow shadowing of parameters 4. Weak delegates 5. Protocols/duck typing/concepts/type classes 6. Zero and one element tuples 7. Deconstruction in lambda parameters 8. Private protected # JSON literals No # Fixing of captured locals Not worth any effort, probably even as up for grabs # Allow shadowing of parameters Current rule saves your bacon a couple of times: ``` c# var t = FooAsync(...) t.ContinueWith(t => ... ); // whoops ``` Discards would make this worse, in a sense, because they lead people to be more likely to capture. Shadowing could be used to avoid capturing, but that would be better served by a non-capture feature. Could be a `static` on lambdas to avoid capture - or it could be a capture list approach. "Capture nothing", "capture what I want", "capture this" are three potential levels with different cost. Could be `this`, `static` and nothing in front of the lambda. But it might be nice to have an explicit annotation for the default, for people who care to express that they explicitly thought about it. Individual items in a capture list are worthless if you don't care about different *kinds* of capture for a given variable. Also a great candidate for custom annotations so we don't put it in the language. That would also allow folks that want more detail to have that. Attributes on lambdas seem like a more general feature. Let's feed this in as a scenario for attributes on lambdas. # Weak delegate Best approach is the small leak pattern which creates a wrapper delegate with a weak reference to the original receiver. That doesn't solve the whole problem, though - how about unregistering, etc? This isn't necessarily a language feature. This should be a library that you pass the method to, and it wraps it. It would be helped by the language allowing for a delegate constraint. Conclusion: Not as a language feature right now; figure it out as a library feature, and *maybe*, someday we would add syntax around it. # Protocols/duck typing/concepts/type classes More dynamic or more static approaches to "adding a 'type' after the fact". A separate important subissue is the dependency explosion. Different solutions to the same problem. They should be addressed as one design effort. 8.0 as a milestone is probably crazy and unrealistic, but helps us prioritize investigation work # Zero and one element tuples ``` c# var () = e; // who cares to deconstruct? var (x) = e; // just get through property? var (x, (y)) = e; // recursive - can't just grab a member ``` In other languages `()` are list-like unit type things - an alternative to `void`, that is more of a first class type and can therefore be used in generics. ``` c# () M(...) => (); // returns zero things ``` Also if we ever embrace bool-returning Deconstruct methods (or the like), you might want zero-length decontructors to mean "there wasn't anything". Womples (one-element tuples) are more likely to be useful, especially for deconstruction. But they are also the ones that clash most fiercely with existing syntax (parenthesized expressions!). ``` c# () t = (); (int x) = (5); (int x) = (d); // could go either way - deconstruct a womple, or deconstruct d? ``` Probably not that useful to have womple expressions. Could allow using named element(s) or even `:` alone: ``` c# var t = (x: 3); var t = (: 3); // yuck! ``` On a deconstruction side: ``` c# (x) = e; (x, (y)) = e; ``` that already means something today. You could do: ``` c# (x: x) = e; (: x) = e; ``` We would have ``` c# if (o is OneDPoint(3)) ... if (odp is (3)) ... ``` Open questions abound. # Deconstruction in lambda parameters It's a special case of deconstruction in parameters ``` c# (var (x, y)) => x + y; double M((int x1, int y1), (int x2, int y2)) => Sqrt(x1 * x2 + y1 * y2); ``` # private protected Would not use so much in the BCL, but in APIs with deeper hierarchies, definitely. ================================================ FILE: meetings/2017/LDM-2017-03-21.md ================================================ # C# Language Design Notes for Mar 21, 2017 ## Agenda Discussion of default interface member implementations, based on [this guided tour](https://github.com/dotnet/csharplang/issues/288). 1. Concerns raised on GitHub and elsewhere 2. Inheritance? 3. Breaking name lookup on `this` 4. Events 5. Modifiers 6. Methods 7. Properties 8. Overrides 9. Reabstraction 10. Most specific override 11. Static non-virtual members 12. Accessibility levels 13. Existing programs # Concerns raised in various fora - Traits: Not what we're out to address! It may have similar capabilities, but the design point is different, based on different motivations. - We are also *not* out to add multiple inheritance to the language - we already have it with interfaces, and we're just making sure that continues to work. - It's a bit funny that members of interfaces are inherited into interfaces but not into classes. Interesting design point: This isn't really fully traits, because interface members aren't inherited. The interfaces can't be building blocks for classes. For structs, there aren't really any other ways to factor out behavior. Java did this years ago, and the world did not fall apart. # Inheritance? We could consider letting default-implemented interface members be inherited into classes. That would be a big break away from interfaces today, where an implementing class always has to do *something* to get a corresponding public member. It would also probably make it a breaking change to *add* a default implementation to an existing member, which does not seem attractive. We could have a feature where when you specify a base interface you can ask to have its default implemented members inherited. # Breaking name lookup on `this` There's a new kind of breaking change that may arise from adding new members to interfaces, where derived interfaces will get name lookup conflicts in their concrete implementations: ``` c# itf IA { void Foo() {} } itf IB {} itf IC : IA, IB { void Bar() { Foo(); // allowed? What if IB adds Foo()? this.Foo(); // same? different? ((IA)this).Foo(); // would be allowed this(IA).Foo(); // new syntax? } } ``` # Events? Absolutely. They aren't materially different. Any member you can declare in an interface should be able to have default implementations. # Modifiers Virtualness is default in interfaces today. We could allow `sealed` to mean non-virtual if you want to override that. Public is default in interface. Should we allow the default to be explicitly stated? All other accessibilities are potentially interesting as well. `extern` scenario: people really like the idea of a scoped PInvoke. People want it for local functions as well. `static`? Yes Nested types? **Philosophically**, we can either go all (or most of) the way to what classes can do, or do it as sparingly as possible, adding only things that are really needed. Once I can put implementation in an interface, I want all of the other stuff. Moving a method implementation from a class up to an interface shouldn't all of a sudden put a bunch of limitations on how to implement it. Move from "let me have this" to "tell me why I can't have this". # Methods Can I use plain old `base` (without disambiguation)? - Is it of type `object`, and exposes `object` methods? - disallow? - `base` means "the" base interface, if applicable - may even "mesh" the base interfaces. No `base` for now, just for initial simplicity. # Properties Abstract properties and events let you implement just one accessor. Can interface properties do that? Our strawman is "yes". # Overrides - Allow explicitly overriding an interface member that you override? - Allow implicitly overriding ...? ``` c# interface I1 { void M(); } interface I2 : I1 { override void I1.M() { ... } } // "explicit" override interface I3 : I1 { override void M() { ... } } // "implicit" override ``` Allowing the latter means that the `override` keyword is needed, in order to avoid syntactic ambiguity. The latter may override *all* such members? Yes. Direct *and* indirect. There's a bit here about which "class" analogy we apply: the "class inherits class" analogy, or the "class implements interface" analogy. The latter doesn't really apply anyway, so it's more similar to "class inherits class". Therefore we should require the `override` keyword even on `IA.M` explicit member overriding. Properties, can override just one accessor. Tuple names must match, just as between classes. For `dynamic`, same as between classes. # reabstraction Allow an override to remove implementation. `abstract` keyword is optional. # Most specific override There has to be a most specific override, otherwise it's an error. Properties and events, the accessors could in principle be treated independently (though there may be a problem with the Roslyn API). Let's instead say that it happens at the whole property level: there must be a most specific override of the property itself. # static non-virtual members on interfaces Would be useful even without this feature. But a shared helper may also be static. # all accessibility levels? `public` and `private` for sure. Let's investigate what `internal` and `protected` mean. # Existing programs We think that example 3 (in this section of the guided tour) is analogous to 2, and should be allowed. However, how can we help someone who thought they were implementing the interface member, but were writing an unrelated private method? Deciding whether a method implicitly implements an interface method is not just matching by signature. We should accept the behavior now, and then look at pitfalls here later. ================================================ FILE: meetings/2017/LDM-2017-03-28.md ================================================ # C# Language Design Notes for Mar 28, 2017 ## Agenda Design some remaining 7.1 features 1. Fix pattern matching restriction with generics 2. Better best common type # Fix pattern matching restriction with generics Today `x as T` is allowed if 1. there is a reasonable relationship between the left and right type, or 2. one of them is an open type But for the `is` operator we don't have the second. That means that we can't always replace uses of `as` followed by null check with an `is` with a type pattern. Also, the restrictions are supposed to rule out only obviously useless cases, whereas some of these are demonstrably useful. Let's allow it, look again if there are unexpected problems. # Better best common type Best common type should combine a value type with null literal to get a nullable value type. ``` c# b1 ? 1 : b2 ? null : b3 ? 2 : default; // default is 0 b1 ? 1 : b2 ? default : b3 ? 2 : null; // default is null b1 ? 1 : (b2 ? null : (b3 ? 2 : default)); // default is 0 b1 ? 1 : (b2 ? default : (b3 ? 2 : null)); // default is null ``` This is weird, but fine: ``` c# M(1, default, null); // int? ``` Next step is trying to spec it. It's a bit complicated. ``` c# M(myShort, myNullableInt, null); Should continue to infer int? , not short? ``` ================================================ FILE: meetings/2017/LDM-2017-03-29.md ================================================ # C# Language Design Notes for Mar 29, 2017 ## Agenda 1. Nullable scenarios 2. `Span` safety # Nullable scenarios Identify all the scenarios where nullable needs to be right. Philosophical debate about what the feature is. Should it be as strict as possible, or more forgiving? Is it more a type feature or more a helpful warning feature? We've made decisions about where we are on that spectrum. Do we trust that as a starting point, or do we want to reexamine those at this point? Example: We want to track variables with flow analysis. for simple names `x` (local variables, parameters) that has reasonably high fidelity. As we move to `x.y.z` the confidence goes down, but the usefulness goes up. Let's agree right now that we're pretty far from the perfect guarantee. It's not that there is anything fundamentally wrong with that, there's just no way to fit it. Let's make lemonade. We have design choices and we have interesting scenarios. Two kinds: - Here are situations where people get into problems with nulls; look at whether the feature solves it - Understand false positives and negatives too: getting errors that incorrectly flag a "problem", vs not being told about a problem. False alarms are a problem, because they will discourage people from taking on the feature, and from taking the reports seriously. We can't be too strict. Also an issue of writing the libraries: is this sufficiently expressible? Another measure of success: How much does a client of a newly annotated old library have to change in order to accommodate? Example: If we are going to say that you annotate your locals, that might be more consistent with the rest of the language, but might also cause more code to need to be changed. This may be a question of gradual adoption strategies. It could be that you start out consuming nullables (which are the new thing, syntactically), without making unannotated mean "non-null". Generics is always a fun situation. Are they in subtyping relationship? That informs variance. And so on. Next steps: Get some samples going. # `Span` safety `Span` is special: it logically contains a `ref` and a size/range. Can't be allowed to tear, and may reference things on the stack: Therefore can't live on the heap. Also, needs to respect the lifetime of a local to which it has a ref. We'll introduce a notion of "ref-like" types, in anticipation of more of these. Other "ref-like" types already exist (e.g. `TypedReference`), but are pretty obscure and unsafe. We won't let our design influence by them, but might opportunistically unify. Also their implementation in the compiler can serve as a checklist for dealing with the new notion of ref-like types. Declaration: need something in metadata. Old compilers will ignore it. We cannot poison a type with modreq's. We could `Obsolete` the type, and the new compiler could recognize it somehow. For now we shouldn't support language-level declaration of ref-like types. There's a whole set of rules we'd need to figure out for such declarations; instead we are good with just recognizing this in metadata and acting accordingly for now. ref-like types cannot be passed by ref, only by `in`, Therefore they must be readonly structs, so that `this` is not passed by ref. In general, these rules are quite restrictive in certain ways. Let's try it and see if it works in our scenarios. There may be an `unsafe` opt-out, or we may nuance the rules. ================================================ FILE: meetings/2017/LDM-2017-04-05.md ================================================ # C# Language Design Notes for Apr 5, 2017 ## Agenda 1. Non-virtual members in interfaces 2. Inferred tuple element names 3. Tuple element names in generic constraints # Non-virtual members in interfaces Do we *want* to allow non-virtual declarations in interfaces? In practice you probably at least want private methods. But it's likely you'll want internal, and maybe public. What's the syntactic distinction from normal "virtual/abstract" interface members?: ``` c# interface I1 { virtual void M1() { ... } void M2() { ... } // non-virtual } interface I2 { void M1() { ... } sealed void M2() { ... } // non-virtual } interface I3 { virtual void M1() { ... } sealed void M2() { ... } // non-virtual } ``` If you want this for traits based programming, you'd want to have the same things available, when you "port things over" to a trait. Intuitively, though, it feels against the spirit of interfaces. Should an interface be required to put `abstract` in order to allow derived interfaces to override them? No. So it probably also shouldn't require `virtual`. So it's the "virtualness" that's implicit, not specifically the "abstractness". The core motivating scenario is default-ness of implementations. So that is the thing we should design around. Is the copy-paste scenario between interfaces and classes important? It already is very far from possible. For instance, property declarations in interfaces today are syntactically identical to auto-properties in classes today! Requiring a modifier (e.g. `virtual`) on a virtual interface member with a default member, as in `I1` and `I3` above, causes an inconsistency between interface members with and without default implementations. Also, taking unmodified members with bodies to be non-virtual, as in `I1` above, is a bit dangerous: adding a body changes the member from virtual to non-virtual. While an implementing class may still think it is implicitly implementing the member, it is in fact just declaring an unrelated member: ``` c# class C : I1 { public void M2() { ... } // Doesn't implement I1.M2, which isn't virtual! } ``` It is better to require a modifier (e.g. `sealed`) on non-virtual members in interfaces, to clearly distinguish them, as in `I2` and `I3` above. The best combination, then, is illustrated by `I2`, where non-virtual members must be marked, and virtual ones must not (and maybe cannot). Tentatively we'll go forward with the following decisions: - Non-virtualness should be explicitly expressed through `sealed` - We want to allow all modifiers in interfaces, in analogy with classes, but with different defaults - Default accessibility for interface members is public, including for nested types - `private` function members in interfaces are implicitly `sealed`, and `sealed` is not permitted on them. `private` nested *classes* can be `sealed`, and that means `sealed` in the class sense. Absent a good proposal, `partial` is still not allowed on interfaces or their members. # Inferred tuple element names Anonymous objects allow "projection initializers", where the name of a member can be automatically inferred from an expression ending in a name: ``` c# new { X = X, Y }; // infers Y as name of second member ``` Proposal is to do the same for tuple literals, picking up any names in element expressions if the element doesn't explicitly have a name: ``` c# int a = 1; ... var t = (a, b: 2, 3); // (int a, int b, int) ``` Just as with anonymous types we pick up names from expressions that are simple names (`x`), dotted names (`e.x`) and `?.`'ed names (`e?.x`). This is technically a breaking change from C# 7.0, but it is quite esoteric. It is hard to construct an example where you depend on a tuple element *not* having a particular name, but here's one: ``` c# Action y = () => {} var t = (x: x, y) // (int x, Action y) or (int x, Action)? t.y(); // a call of the second component or of an extension method y on the type (int, int)? ``` We think that this is low enough risk that we can introduce the feature, especially if we do it quickly (C# 7.1). A subtlety of the feature is that inferred names should not trigger the warnings around unused names: ``` c# (int, int) t = (x: x, y); // warning for x. No warning for y, because inferred ``` # Tuple names in constraints There are some gnarly issues questions around tuple element names in generic constraints. In general we try to avoid use of different tuple element names across related declarations, but with constraints it is hard. A simple example is ``` c# class C where U : I<(int a, int b)>, I<(int notA, int notB)> // this is currently allowed, and would become an error ``` But more complex examples involve indirect differences. ``` c# class C where U : I<(int a, int b)>, I2 // where I2 implements I<(int notA, int notB)> ``` In the case of type implementations, we prevent those and even check for possible type unifications. But in the case of constraints, we currently don’t check as much (no type unification check). The third level of difficulty is illustrated by: ``` c# class C where U : I<(int a, int b)>, I2 // where I2 implements I<(T notA, T notB)> ``` We think you could never declare a class (in C#) that satisfies the constraints (in C#) in these examples. You could construct things in IL to circumvent, but it's ok for us not to deal with that. So it's probably ok for C# not emit an error here. We do try to emit an error when constraints conflict. But the value is low and it is hard and esoteric and makes the compiler slower. ================================================ FILE: meetings/2017/LDM-2017-04-11.md ================================================ # C# Language Design Notes for Apr 11, 2017 ## Agenda 1. Runtime behavior of ambiguous default implementation # Runtime behavior of ambiguous default implementation The feature is intended to work when a new default-implemented member is added to an interface `I`, even when an implementing class `C` is not recompiled. So the runtime needs to know about default implementations and be able to find them. In the case of overrides, there may be diamond-hierarchy cases where the compiler knows of only one override, but one is added later to another interface. The implementations are now ambiguous, and a recompilation would cause an ambiguity, but it would seem desirable that the runtime should choose "the one the compiler knew about"; that, somehow, that knowledge would be baked in to the compiled class `C`. Starting out with these type declarations in separate assemblies: ``` c# interface I1 { void M() { Impl1 } } interface I2 : I1 { override void M() { Impl2 } } interface I3 : I1 { } class C : I2, I3 { } ``` Everyone's happy, and `C`'s implementation of `M` would unambiguously come from `I2`. Now `I3` is modified and recompiled: ``` c# interface I3 : I1 { override void M() { Impl3 } } ``` What should happen at runtime? When `C` was compiled, it "thought" everything was alright. At runtime it isn't. Can the compilation of `C` bake in a preference based on its understanding of the world at the time of compilation? Should it? It is not obvious how this would work. What if the default implementation `C` depends on is moved, deleted or overridden? Should it just be a "vague" preference in case of ambiguity, to get the runtime on the right track? This seems complicated, fragile and fraught with peril, but ending up with an ambiguity at runtime is also bad. Regardless, there will always be runtime ambiguities; "baking in" preferences would only address a subset. Two open questions: 1. Should we try to help resolve ambiguities by baking in compile time preferences? Unresolved. 2. Should we fail or pick an "arbitrary" implementation in case of inevitable ambiguities at runtime? Unresolved. Bad to error. Bad to run "arbitrary" code. We should look more deeply into what Java does here. There must be accumulated insight already on this topic. ================================================ FILE: meetings/2017/LDM-2017-04-18.md ================================================ # C# Language Design Notes for Apr 18, 2017 *Quote of the Day*: "I don't want to require `MainAsync`. It sounds like mayonnaise-ink!" ## Agenda 1. Default implementations for event accessors in interfaces 2. Reabstraction in a class of default-implemented member 3. `sealed override` with default implementations 4. Use of `sealed` keyword for non-virtual interface members 5. Implementing inaccessible interface members 6. Implicitly implementing non-public interface members 7. Not quite implementing a member 8. asynchronous `Main` # Default implementations for event accessors in interfaces Today, syntactically, either both or neither accessor can have an implementation. Should we allow just one to be specified? Overridden? ## Conclusion If you have only one, you probably have a bug. Let's not allow it for now. # Reabstraction in a class of default-implemented member Should an abstract class be allowed to implicitly implement an interface member with an abstract member, even when the interface member has a default implementation? ## Conclusion Yes, of course. Adding a body to an interface member declaration shouldn't ever break an implementing class. # `sealed override` for default implementations Should it be allowed? would it prevent overrides in *classes* or only in *interfaces*? It seems odd to prevent either. Also, it is weird in connection with diamond inheritance: what if one branch is sealed? ## Conclusion Let's not allowed `sealed` on overrides in interfaces. The only use of `sealed` on interface members is to make them non-virtual in their initial declaration. # Use of `sealed` keyword for non-virtual interface members Some folks find it a weird use of `sealed`, and that they look too much like things that can be implemented in classes. ## Conclusion We think non-virtual members in interfaces are going to be useful, but will come back to the syntax. This is a mental model tripping block. # Implementing inaccessible interface members The way the runtime works today, a class member can happily implement an interface member that isn't accessible! That's not likely to be depended on today (no language will generate that interface), but we need to decide what semantics to have here. We could continue to only have *public* members in interfaces be virtual. But if we want protected, internal and private, we should probably have it so they can only be implemented by classes that can see them. But this means that interfaces can *prevent* other assemblies from implementing them! This may be a nice feature - it allows closed sets of implementations. ## Conclusion This is still open, but our current stake in the ground is we should *allow* non-public virtual interface members, but *disallow* overriding or implementing them in places where they are not accessible. # Implicitly implementing non-public interface members Would we allow non-public interface members to be implemented implicitly? If so, what is required of the accessibility of the implementing method? Some options: * Must be public * Must be the exact same accessibility * Must be at least as accessible ## Conclusion For now, let's simply not allow it. Only public interface members can be implicitly implemented (and only by public members). We can relax as we think through it. # Not quite implementing a member You have a member and you implement an interface. The interface adds a new member with a default implementation, that looks like your method but doesn't *quite* make it an implementation. Bug? Intentional? We can't provide a warning, because it would assume it was a bug. ## Conclusion Can't do anything about this. # asynchronous `Main` We've decided to allow a `Main` method that returns `Task` and `Task`. Whether it's `async` or not is completely optional, and an implementation detail of the method. This feature relies on the pattern of calling `GetAwaiter().GetResult()` on the returned task, and on an expectation that this call blocks until the task is complete. That is the case for the framework's implementations of `Task` and `Task`. What if someone uses an alternative implementation? We can't prevent that. They either know what they are doing, or they are asking for it. What about other awaitable types? Should they be allowed? This would be easy enough to implement; the concern is whether it is reasonable to expect their `GetResult()` method to block? After all, the compiler has not previously relied on this in its use of `GetResult()` on awaitable types, so one would assume that no particular effort has been put into ensuring it in the implementation of those types. If we don't allow other awaitables in `Main` directly, folks can easily work around it just by having an `async Task Main` that awaits it: ``` c# static MyTask MyMain() { ... } static async Task Main() => await MyMain(); // problem solved ``` ## Conclusion Let's still keep the `async` keyword optional. Let's stick with the name `Main` instead of `MainAsync` for now. Let's stick to `Task` and `Task`, but refine the rule: 1. Look for `Main()` or `Main(string[] args)` 2. Does exactly one return `void` or `int`? If one, use that. If more, error. 3. Otherwise, look for `Task` and `Task`, where the return type of `GetAwaiter().GetResult()` is `int` or `void`. ================================================ FILE: meetings/2017/LDM-2017-04-19.md ================================================ # C# Language Design Notes for Apr 19, 2017 *Quote of the day*: "I'm not thrilled with the decision, but it was a constrained call" ## Agenda 1. Improved best common type 2. Diamonds with classes 3. Structs and default implementations 4. Base invocation # Improved best common type ``` c# b ? null : 7 // should be int? not error ``` This is a small, isolated change that leads to a nice improvement. Let's do it! # Diamonds with classes A class implementation of an interface member should always win over a default implementation in an interface, even if it is inherited from a base class. Default implementations are always a fallback only for when the class does not have any implementation of the member at all. # Structs and default implementations It is very hard to use default implementations from structs without boxing. In general, the only way to use interface methods on a struct without copying or boxing is through generics and ref parameters: ``` c# public static void Increment(ref T t) where T : I => t.Increment(); ``` But for default implementations, even that won't necessarily avoid boxing! What is the type of `this` in the default implementation of `I.Increment`? If it's `I`, then `this` is a boxed form of the struct! We can avoid that by having an implicit type parameter, a "this type", which is constrained by `I`. But then, what is the name of that implicit type parameter in the body? If it doesn't have a name, you can't use it in the body. If it does have a name, what is the syntax for that? For inherited members on structs today, they do in fact get boxed, but it is never observable - because the behavior of those three methods (`ToString`, etc.) doesn't mutate, or otherwise reveal the boxing. What can we do? 1. *Forbid structs to make use of default implementations*: Can't do that. Now adding a new interface member with a default implementation would break implementing structs. 2. *Come up with some code gen strategy like implicit this types*: Pushes the problem elsewhere, at a high cost and with a high risk that you box later anyway. 3. *Don't worry about it, and leave it as a wart in the language*: It looks like a cop out, but there really doesn't seem to be a good alternative. C# already has places where structs and interfaces behave weirdly together; this just adds to the pile. ## Conclusion Let's stick with option 3. It would be a breaking change to do 2 later, so we can never change it. # Base invocation What should be the syntax for base invocation? Some candidates: ``` c# I.base.M() base(I).M() base.M() ``` `base(I)` is the winner. It reads like `default(T)`, and seems the best fit. Should these be permitted in classes too, to specify base interface implementations? Yes. Otherwise the diamond problem isn't solved. We could actually expand it to work *on* classes too, to allow a more distant base implementation to be called. Let's decide that later. In an interface, can I say `base(IA)` that isn't a *direct* base interface? Yes. In a class, can I say `base(IA)` that isn't a *direct* base interface? Yes. ================================================ FILE: meetings/2017/LDM-2017-05-16.md ================================================ # C# Language Design Notes for May 16, 2017 ## Agenda 1. Triage C# 7.1 features that didn't make it 2. Look at C# 7.2 features 3. GitHub procedure around new design notes and proposals 4. Triage of championed features # Triage C# 7.1 features that didn't make it ## Private protected Almost there, push to 7.2 for completion. ## Field-targeted attributes This is a bug fix level change, and very useful. Push to 7.2 # Look at C# 7.2 features "Slicing" needs to be split into "ref structs" and "slicing syntax". Push out everything, except - things that are "in theme" - related to refs, spans, etc. - things that are almost done (field-targeted attributes, private protected) Keep everything that's in theme until we can sit with the `Span` folks and prioritize. # GitHub procedure around new design notes and proposals We should use issues as a notification mechanism for when design notes and proposals are completed. Revisions are comments on the same issue. (This has now been adopted for design notes back to Mar 15, 2017.) # Triage of championed features ## CallerArgumentExpression Push to 7.X and start process for getting the attribute in place. ## Leading and trailing digit "separators" ``` c# M(0b_1, 0x_A) ``` Very low additional value. Would probably help some code generators, and some code layout. Might accept a pull request. ## Static delegates Delegates require, generally, 2 allocations, and are crappy for interop. Static delegates are typed `IntPtr`s, essentially. `ValueAction` and `ValueFunc`. Lot of asks from CoreRT, for PInvoke etc. Of course they won't be able to close over anything, so they'd require significant language support. ## Namespace XML doc comments This is hard to take as a community contribution: it's primarily about the IDE behavior. Not all of that may even be open source today. It would also affect the rest of the ecosystem, which would now have to handle it. Need to coordinate with IDE team. ## `??` and `?.` for pointers Probably lo-pri to make this pleasant. But nothing against it. This seems suitable for up-for-grabs. The syntax should probably be `?->`. For double pointers, you're out of options! ## Non-trailing named arguments Helps selectively use parameter names for readability. Would relieve people from bending over backwards to put name-prone parameters last. Also would make it work better with params. Would need some ide work. ================================================ FILE: meetings/2017/LDM-2017-05-17.md ================================================ # C# Language Design Notes for May 17, 2017 ## Agenda More questions about default interface member implementations 1. Conflicting override of default implementations 2. Can the Main entry point method be in an interface? 3. Static constructors in interfaces? 4. Virtual properties with private accessors 5. Does an override introduce a member? 6. Parameter names # Conflicting override of default implementations If at runtimes you have ambiguous overrides: ``` c# interface I1 { void M1(); } interface I2 : I1 { override void M1() { ... } } interface I3 : I1 { override void M1() { ... } } class C : I2, I3 {} // what happens when you load? C x; x.M1(); // what happens when you call? ``` This can happen without compiler complaint through certain orders of compilation. Options: 1. throw on load 2. throw on call (like Java) 3. pick arbitrarily (but consistently) 4. bake in what you meant at compile time 1. always use the baked in one, error if it's gone 2. pick the unique one, fallback to baked-in if unsuccessful Baking in causes a lot to hinge on recompilation. We should leave to the runtime to resolve the virtual call. So not 4. Also, 1 seems too harsh. If you don't call it, why complain? If you call an arbitrary method, that's a little dangerous: you can't reason about what the program does. ## Conclusion Option 2 it is. We can throw. If we realize we were wrong about this later, we can move from there. No bake in. # Can the Main entry point method be in an interface? Yes. No good reason why not. Also, that's the least work. # Static constructors in interfaces? Probably, but let the runtime folks think about this. Reason is static fields. We could allow initializers for static fields, but not an explicit static constructor. Can think about adding later. # Virtual properties with private accessors In classes you can define a virtual property with private accessor. Allowed in interfaces? What does it mean? Probably disallow. # Does an override introduce a member? - Does it introduce parameter names? - Does it introduce a new virtual method that could be implemented independently If the call site sees two separately declared methods, that's already an ambiguity. ``` c# interface I1 { void M1(); } interface I2 { void M1() { ... } } interface I3 : I1, I2 { override void M1() { ... } } I3 x; x.M1(); ``` Should `I3` be allowed? Does it override both? Should `x.M1()`; Explicit override syntax removes the problem, but do we want to force people to always put the interface in front? Java's solution does not help us, because independently declared interface members unify in Java. Xamarin does not expect to need override. They'll just need to know whether there is a default implementation or not. They will need reflection support to query the default methods. It's not an option to depend on order. ## Conclusion `I3` is fine, but does not introduce a new member. So the call to `M1` is ambiguous. The work-around is to cast to one of the interfaces. However, oddly, you *can* call it through `base(I3)`. # Parameter names ``` c# interface I1 { void M1(int a); } interface I2 : I1 { override void M1(int b); } // allowed to change parameter names? I2 x; x.M(a: 3); // allowed? x.M(b: 4); // allowed? ``` The class behavior is to pick the most specific names, but that does mean that introducing an override (with different parameter names) can be a breaking change. But we could issue guidance to not change names in this situation. From experience, people do intentionally change names on override, but others ask for features to prevent them from doing it! Common scenario is implementing a generic instance. Going from `T` to `Dog`. ## Conclusion Let's take the names from the original declaration, deliberately being inconsistent with the class rule for simplicity's sake. New open question: what about `base(I2)` and parameter names? ================================================ FILE: meetings/2017/LDM-2017-05-26.md ================================================ # C# Language Design Notes for May 26, 2017 ## Agenda 1. Native ints # Native ints We would like to supply high-quality native-size integers. The best we have today is `IntPtr`, which lacks most operators and have a few behavioral weaknesses, including a suboptimal `ToString` implementation. Xamarin has introduced user-defined `nint` and `nuint` types for interop, but those can't completely do the trick; for instance, user-defined operators cannot distinguish between checked and unchecked contexts. Options: 1. Improve `IntPtr` with operators 2. Add `nint` and `nuint` as a user defined struct (the Xamarin approach) 3. Add `nint` and `nuint` to the language 1. compile down to `IntPtr` and add an attribute to persist the additional type info in metadata 2. project language-level types to new user defined structs 4. A combo of 1 and 3 `IntPtr` has a few operators today; for instance `+` with `int` (which is checked on 32 bit and unchecked on 64 bit!), and some conversions. The new structs in 3.2 look something like this: ``` c# struct NativeInt { public IntPtr Value; public override string ToString() { ... } } /// etc ``` But operators are implemented by the language, not as user-defined operators. The difference between 3.1 and 3.2 is that with 3.2 at runtime we have different types, so we can have differentiated runtime behavior: `ToString` and reflection can tell them apart. A downside is that operations that take `ref IntPtr` (like `Interlocked.CompareExchange`) wouldn't automatically take `ref nint`. Having the public mutable field would let things still work for people, and we could go through and add `nint` overloads over time to make it better. This should be in mscorlib, but that takes time. Is there anything we can do to mitigate in the meantime? We could ship a nuget package etc, but there's some cost to that, including indefinite maintenance. But some of the people who would benefit from this will be in a terrible spot if we don't provide something. We also need to deal with native float. There is no option to do 3.1 for floats; there is no `IntPtr` equivalent. So that one would need a framework type. However, we could probably live with that `nfloat` struct moving into the frameworks over time - other than Xamarin, which would add it faster for its interop scenarios. With 3.1, if you consume a `nint`-attributed `IntPtr` with an old compiler, would it treat it as an `intPtr`? If that's the case then the code would subtly change behavior on compiler upgrade. Unfortunate! We could perhaps poison `nint` with `ModReq` so that they cannot be consumed by existing compilers, but now `nint` really *is* a different type, and requires separate overloads of methods that take it as a parameter. Another option is to obsolete the user-defined operators on `IntPtr`, to drive people to use `nint` instead. ## Objections to 3.2: - Adoption, where a separate struct would take a while to propagate (we feel we've mostly mitigated this) - We'll emit slightly less efficient and more verbose IL in a couple of cases - Needing new overloads for `nint` where there are `IntPtr` overloads today (or at least a conversion, and new overloads where there are `ref IntPtr` parameters). Objection to 3.1: - No runtime distinction (reflection and `ToString`) - `ToString` happens all the time ## Conclusion We're torn, and evenly balanced on preferring 3.1 vs 3.2. Could maybe be convinced to 3.2 if we can solve how do existing users of `IntPtr` migrate. ================================================ FILE: meetings/2017/LDM-2017-05-31.md ================================================ # C# Language Design Notes for May 31, 2017 ## Agenda 1. Default interface members: overriding or implementing? 2. Downlevel poisoning of ref readonly in signatures 3. Extension methods with ref this and generics 4. Default in operators # Default interface members: overriding or implementing? So far, when a derived interface wants to provide a default implementation for a member declared by a base interface, we have been thinking of it by analogy with overriding of virtual methods in classes, even using the `override` keyword: ``` c# interface I1 { void M1(); } interface I2 : I1 { override void M1() { ... } } ``` This design philosophy forces us to grapple with a lot of stuff from classes that may not be very useful. Implementing on the other hand is different. You don't have to match accessibility, etc. You can implement multiple interface members with one class member. A base class can highjack an implementation from an interface: ``` c# class C1 { public void M1() { ... } } class C2 : C1, I2 { // class wins } ``` Maybe we should try to stay true to the notion of *implementing* when it comes to default interface members. So "overriding" should instead be expressed as an explicit interface member implementation: ``` c# interface I2 : I1 { void I1.M1 { ... } } ``` This seems to have more of an "interface camp" feel to it: it is more similar to what you can do with interface members today, only you can now do it in interfaces. We like this approach! Let's investigate some of its consequences. ## "Base calls" to default interface implementations With the "explicit implementation" approach above, how do you *call* the implementation from another one? Well you have that problem today: you *cannot* directly call an explicit implementation today; not even from within the implementing class itself. However, for default interface implementations that's a significant reduction in expressiveness. You'd have to do so via a non-virtual helper method. So the implementation has to anticipate needing to be reused and factor out to a helper method. For instance, when you do an "implement interface" refactoring, what does it do? Leave out already implemented ones? implement them again with a call to `base` (if that's possible)? With a `throw`? Won't it be common to want to reuse a default implementation, adding "just a little bit more", i.e., calling `base`? Well, that gets into an accessibility game: the accessibility for ultimate use, versus the accessibility for being non-virtually called as `base`. ## Ambiguity of base calls If we allow base calls to default implementations, there are two different kinds of ambiguities. Ambiguity of declaration: ``` c# interface I1 { void M() { ... } } interface I2 { void M() { ... } } interface I3 : I1, I2 { void N() { base.M(); } } // which declaration of M()? ``` Ambiguity of implementation: ``` c# interface I1 { void M(); } interface I2: I1 { void I1.M() { ... } } interface I3: I1 { void I1.M() { ... } } interface I4: I2, I3 { void N() { base.M(); } } // which implementation of M()? ``` You need to get into weird-ish stuff like this: ``` c# base(I3).(I1.M1)(1, 2, 3); // Call I3's implementation of I1's M1 ``` If we want to do something like this, we would also consider allowing *classes* to pick which one they grab the implementation from: even though classes have no ambiguity about which is the most *specific* override, you could potentially allow "reaching around" and grabbing an older one. ## Deimplementation When we were on the `override` plan, we intended to allow "reabstraction", where an interface could `override` a base method to *not* have an implementation. In this new scheme, should we allow "deimplementation" - the ability for an interface to say that an inherited member does *not* have a default implementation, contrary to what a base interface said? The meaning would be "I declare that the default implementation is not useful (or is harmful) to classes implementing me." This would probably be rare. People can implement to throw an exception instead. ## Conclusion Strangely this approach moves the design closer to its intended purpose. It keeps it about implementation, without mixing inheritance into it. It does mean you have to type the interface name every time you want to provide a new implementation for a base interface member. Should we have a shorthand for when the interface name is unambiguous and obvious? No. We haven't needed it for explicit implementation in classes, we probably don't need it now. Let's think about whether we can live entirely without base calls to default implementations, and come back to this when we've mulled it a bit. # Downlevel poisoning of ref readonly in signatures We currently poison with a `modreq` all the places in signatures where unaware compilers could do something unsafe (write into read-only memory) by using a `ref readonly` as if it was a mutable `ref`. `Modreq`s aren't very discerning, so a virtual method cannot even be called by unaware compilers, even when only overriding is unsafe. We accept this degree of granularity as the best possible state of affairs. The only role of the `modreq` is to poison unaware compilers. The actual information about read-only-ness of refs is carried in attributes. Should we reject methods that have the attributes but not the `modreq`? It makes it harder to relax it later. But it protects the contract from manual finagling. So yes, refuse to consume such methods. It's hard for us to add a new modifier. We may have to reuse one. If we had our own, we could avoid using attributes altogether, just make it modopt or modreq depending on whether it is required for safety. Let's keep this idea around, in case we do get to have our own. # Extension methods with ref this and generics Extension methods will be allowed to take value types by `ref` or `ref readonly`. It doesn't make sense for reference types, so those are disallowed. However, what about unconstrained (or interface constrained) type parameters? Should we allow so as to get the benefit when a type argument happens to be a value type? Probably not. - For mutable ref it seems a little dangerous and surprising that an extension method can modify a variable of reference type - For readonly ref, chances are it would lead to much unintended copying, as the readonly-ness would cause the value to get copied whenever you tried to do something useful with it in the body (e.g. based on an interface constraint). ## Conclusion Don't allow. # Default in operators `default` as an operand to unary or binary operators would sometimes work, and sometimes not, depending on whether there happens to be a best operator across all available predefined or user-defined ones for all types: ``` c# var a = default + default; // error var b = default - default; // ok var c = default * default; // ok var d = default / default; // error ``` This feels arbitrary. But worse, it is actually a recipe for future compat disasters. Imagine we added a `-` operator to, say, arrays in the future. Now the second line above would break, because the `int` overload of the pre-defined `-` operator would no longer happen to be best. ## Conclusion Don't allow `default` as an operand to a unary or binary operator. We need to protect the ability to add new operator overloads in the future. ================================================ FILE: meetings/2017/LDM-2017-06-13.md ================================================ # C# Language Design Notes for Jun 13, 2017 ## Agenda 1. Native-size ints 2. Native-size floats # Native-size ints We want `nint` and `nuint` to be part of the language. They will be used in contexts where we care about respecting checked vs unchecked in operator behavior, for example. We have two options for how to represent at runtime: 1. Project to `IntPtr`, track the `nint` or `nuint` "overlay" in metadata for compile-time consumption only 2. Project to new struct types that wrap an `IntPtr` and potentially exhibit different behavior at runtime too Option 2 had some objections attached. Let's go through them: - Adoption/roll out of new types would take time - We could embed these struct types in the generated assembly and use the NoPIA machinery to unify them across assemblies. - Would have a slight cost on IL size. - Probably not a decisive objection - Need new overloads for a few things, such as `Interlocked.CompareExchange` - We could probably live with those rolling out gradually, especially with access to the `IntPtr` of a `nint` or `nuint` as a public field (so it can be passed by `ref` to existing overloads) - There is a bifurcation because the types are really distinct at runtime. There's interop pain between them. - This may be a good thing. Arguably they represent different concepts: `IntPtr` is more of an opaque handle, whereas `nint` and `nuint` are real integers with number semantics. Option 1 has some objections as well: - `ToString` doesn't work right - This could probably be fixed. It is unlikely that the current deficiencies are depended upon - you lose context on box/unbox - That is already the case today with other common types, such as tuples, nullable value types and `dynamic`. Is it really so bad? - Imprecise reflection info - Again, we live with this in many places `String.Format` would exhibit the combination of the first and second problem: passing a `nint` as an argument, it would box and lose context, so `IntPtr.ToString` would get called. Things to ponder regardless of implementation strategy: - **Conversions:** Should there be conversions between `nint`/`nuint` and `IntPtr`? If so, which way (if any) should be implicit? - **Upgrade:** Someone using `IntPtr` today might want to switch to `nint`. This would mostly just work, but would have very subtle changes of behavior here and there. ## Conclusion We are still hung on what to do. We want to more deeply understand objections against Option 1, and understand if mitigations considered would address e.g. Xamarin's concerns. # Native-size floats Float really is a different discussion. Language embedding is less significant: we don't have a notion of checked/unchecked. The main objective would just be to have an alias. ================================================ FILE: meetings/2017/LDM-2017-06-14.md ================================================ # C# Language Design Notes for Jun 14, 2017 ## Agenda Several issues related to default implementations of interface members 1. Virtual properties with private accessors 2. Requiring interfaces to have a most specific implementation of all members 3. Member declaration syntax revisited 4. Base calls # Virtual properties with private accessors Classes (strangely) allow virtual properties with one accessor being private. Should interfaces allow something similar? ## Conclusion No. It is not obvious what it means, or whether it's in any way useful. # Requiring interfaces to have a most specific implementation of all members Should it be an error for an interface to not have a single most specific implementation if each member (even inherited) that has default implementations? ## Conclusion This might be desirable if we were designing the language from scratch, but it would be breaking, since existing compilers wouldn't know to check that. Also, there'd be no way to fix it if they could. So we cannot disallow this. The buck stops with the implementing class, which is the one that has to implement any interface member that doesn't have a unique most specific default implementation. This does allow you to "reabstract" a default-implemented member by injecting a diamond situation and forcing a class level error, but so be it. # Member declaration syntax revisited In a [previous meeting](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-31.md) we changed the syntax around "overriding" of default implementations to match that of explicit implementation in classes today. This topic explores whether we have the right syntax when declaring an interface member and giving it a default implementation *at the same time*. Today the syntax for this and an overriding implementation is simply: ``` c# interface I1 { int M(int i, bool b, string s) { return s?.Length ?? i; } } interface I2 : I1 { int I1.M(int i, bool b, string s) { return b ? i : s?.Length ?? 0; } } ``` Or, if we use expression bodies: ``` c# interface I1 { int M(int i, bool b, string s) => s?.Length ?? i; } interface I2 : I1 { int I1.M(int i, bool b, string s) => b ? i : s?.Length ?? 0; } ``` The declaration syntax in `I1` has both pros and cons. One the one hand: - It is simply what you get if you take today's interface member declaration syntax and add a body - It looks similar to a class member that might implicitly implement an interface member On the other: - It looks exactly like a non-virtual class member, which cannot be overridden/reimplemented by anything else - It's odd why the implementation in `I2` needs to look like an explicit implementation (`I1.M`) when one that's immediately declared does not One could argue that the current syntax should be reserved for the same meaning that it has in classes: a non-virtual member. But either way leads to confusion and surprise for some: - I "simply" add a body to an interface member, and all of a sudden it is no longer implementable by classes or derived interfaces! - I "simply" copy a nonvirtual member from a class to an interface, and all of a sudden it is re-implementable by classes and derived interfaces! We need to decide which side of this conundrum to land on. If we *were* to change the meaning of ``` c# int M(int i, bool b, string s) => s?.Length ?? i; ``` to be "nonvirtual" member what *would* an implementable interface member with a default implementation look like instead? Let's explore some options: ## Proposal 0 Status quo. Non-virtual members are explicitly annotated with `sealed`, which isn't great, but we can live with it. ## Proposal 1 Make a separate declaration that uses the same "explicit implementation" syntax as in the derived classes: ``` c# interface I1 { int M(int i, bool b, string s); // declares the member int I1.M(int i, bool b, string s) => s?.Length ?? i; // provides a default implementation } interface I2 : I1 { int I1.M(int i, bool b, string s) => b ? i : s?.Length ?? 0; // no change } ``` This is probably the most hideous and verbose option we can come up with. But points for consistency! ## Proposal 2 Use a `default` keyword as a modifier to signal that a default implementation is provided: ``` c# interface I1 { default int M(int i, bool b, string s) => s?.Length ?? i; } ``` `default` may or may not be allowed and/or required on a derived implementation as well: ``` c# interface I2 : I1 { default int I1.M(int i, bool b, string s) => b ? i : s?.Length ?? 0; } ``` We don't hate this option, but it may be a bit too much syntax "just for the benefit of first timers". It signals: "I am not really implementing it here; I'm providing a default!" That may allay the sense that interfaces are becoming too much like classes. ## Proposal 3 Same as 2, but with the `virtual` keyword instead: ``` c# interface I1 { virtual int M(int i, bool b, string s) => s?.Length ?? i; } ``` `virtual` may or may not be allowed and/or required on a derived implementation as well: ``` c# interface I2 : I1 { virtual int I1.M(int i, bool b, string s) => b ? i : s?.Length ?? 0; } ``` This feels like we're going back towards virtual methods in classes in a way we don't want to. Also, it anything it should probably then be `override` and not `virtual` on the derived implementation, but now we're just going in circles. ## Additional consideration If we ever want to allow implementable static members in interfaces (that are required to be provided by implementing classes), then the syntactic choices we make here will potentially come back to bite us. ## Conclusion We don't like Option 3, and we hate Option 1. We're not totally averse to Option 2, but for now we'll keep the status quo, which seems to have the least syntax for the most common tasks. # Base calls We pondered briefly whether we could live without the ability to call default implementations directly, just as you cannot call an explicit implementation today. However, we think that we do need to allow it. A particularly strong example is when you have ambiguous default implementations from different base interfaces. How do you pick one if you cannot call it? ## Disambiguation There are two ways in which a base call to a default implementation can be ambiguous: - **Implementation ambiguity:** For a given original declaration, there may be two base interfaces both inheriting that declaration and providing competing implementations for it. Which one did you want to call? - **Declaration ambiguity:** There may be two base interfaces that happen to declare members of the same signature. Which one did you mean? We can think of disambiguating syntax for both: - `base(I1).M`: I want `I1`'s implementation of `M` - `base.(I1.M)`: I want my `base`'s implementation of the `M` that was declared in `I1` - `base(I3).(I1.M)`: I want `I3`'s implementation of the `M` that was declared in `I1` However we think that implementation ambiguity is going to be common, and declaration ambiguity is more esoteric. We are happy to add disambiguation syntax only for implementations for now. ## Aside: Name disambiguation There's an interesting and more general feature waiting for another time in the ability to disambiguate a member *name* by where it's declared, as in `e.(I1.M)`. This could be used to get at hidden members or implemented interface members without casting, etc. But let's save that for another day. ## Syntax As for the implementation disambiguation, we have three proposals on the table: ``` c# base(T) // 1 base // 2 T.base // 3 ``` We don't like 3 at all (even though that's essentially Java's syntax for it). We have some sympathy for 2, but it looks like there's generics involved, and there's not. We like 1 the best. It is reminiscent of other places in the language where a keyword is followed by a type in parentheses: ``` c# typeof(T) default(T) sizeof(T) ``` ## Semantics When you access a base implementation with the syntax `base(T)`, the meaning is "get the implementation of the member from `T`". This should at least work when `T` is an interface (but we are also interested in making it work for classes, letting you skip to an earlier base). It is a compile time error if there is no default implementation of the member in `T`. If there is, that implementation is called directly. At runtime, if the implementation is no longer there, it is probably not worth it to try to walk the graph and find another one: we should just throw at this point. These semantics mean that you can get in trouble if you rearrange your default implementations and don't recompile things that depend on you. So be it. ## Unqualified base `base` *without* a type should also be allowed for default implementations, as long as it is unambiguous. It means the same as `base(T)` where `T` is the interface that has the unique most specific default implementation at the time where the `base` access is compiled. ## Conclusion `base` and `base(T)` will be allowed to access default implementations in interfaces, both from derived interfaces and implementing classes. They are compiled to directly call the implementation in the interface they (implicitly or explicitly) designate. It is worth pondering whether this is all too "static". It is not enlisting the runtime as much as perhaps it could, meaning that more specific default implementations could be ignored if they weren't known when the calling code was compiled. It is, however, equivalent to what we do with base calls in classes today. And it avoids a whole class of problems and error modes, because the compiler hardwires its choice of implementation into the code. ================================================ FILE: meetings/2017/LDM-2017-06-27.md ================================================ # C# Language Design Notes for Jun 27, 2017 *Quotes of the day:* > "Let's support most operators, but not equal and not-equal" > "We'll rename the next version to C# l'eight" ## Agenda 1. User-defined operators in interfaces 2. return/break/continue as expressions # User-defined operators in interfaces ## Conversion operators Let's not allow conversion operators in interfaces, since they are not allowed *on* interfaces today, even when defined elsewhere. ## Equality operators There'd be no way to override `Equals` and `GetHashCode`, so you couldn't do `==` and `!=` in a recommended way. That's an argument for disallowing. What if we *did* allow `Equals` and `GetHashCode` to be overridden in interfaces? That would be a major undertaking, for what seems like modest gains. However, if we allow `<=` and `>=` you can get the effect of `==` anyway, through `&`. This may or may not be an argument for disallowing *more* - or maybe all - operators. Some options: 1. Drop operators across the board 2. Let's not allow `==` and `!=` and see what feedback we get 3. Let's allow `==` and `!=` and see what people use it for ### Conclusion Let's stick with option 2 for now. We'll see from prototype use whether this is an intolerable limitation, but we believe it is a decent place to land. ## Is the remainder worthwhile? Conversion and equality are the most useful operators, in that they apply to any domain, whereas most of the other operators are for more specific, often math-related, purposes. If we don't allow them, is it worth allowing the rest? ### Conclusion Let's assume that it is. Those specific domains are likely still important to express in interfaces. ## Lookup rules for operators We only even look for operator implementations in interfaces if one of the operands has a type that is an interface or a type parameter with a non-empty effective base interface list. ### Unary operators The applicable operators from classes/structs shadow those in interfaces. This matters for constrained type parameters: the effective base class can shadow operators from effective base interfaces. ### Binary operators Here there could be operators declared in interfaces that are more specific than ones implemented in classes. We should look at operators from classes first, in order to avoid breaking changes. Only if there are no applicable user-defined operators from classes will we look in interfaces. If there aren't any there either, we go to built-ins. We hypothesize that there's no case (outside of equality and conversion) where user-defined operators in interfaces shadow predefined operators, but we may be wrong. So that needs to be investigated a bit. ## Shadowing within interfaces If we find an applicable candidate in an interface, that candidate shadows all applicable operators in base interfaces: we stop looking. This is a bit more complicated than in classes, where it's linear, but we can express it right. # return/break/continue as expressions It's been proposed to allow `return`, `break` and `continue` as expressions, the same way we now allow `throw` expressions. There's a bigger discussion of whether we should seek to make C# more expression-oriented, adding a "value" (or at least a classification) for each statement kind, and allowing all or most statements in an expression context. However, this specific proposal does not need to be part of that discussion: we already have `throw` expressions, and the potential value of adding these other *jump-statement*s can be evaluated in that narrower context. There are clearly scenarios that can be expressed more concisely with this feature, but the really compelling ones involve the use of `??` on nullable value types (and presumably on nullable reference types in the future). It provides an elegant way to get at the underlying type without having to more explicitly unpack it: ``` c# int z = x + y ?? return null; // where x and y are of type int? //as opposed to something like if (x is null || y is null) return null; int z = (x + y).Value; // explicit unpacking // or int? nz = x + y; if (nz is null) return null; int z = nz ?? 0; // dummy unpacking // or using patterns if (!(x + y is int z)) return null; ``` The scenarios involving the ternary conditional operator seem less compelling, as they are more easily replaced by a "bouncer" if-statement: ``` c# accumulator += (y != 0) ? x/y : continue; // as opposed to if (y == 0) continue; accumulator += x/y; ``` Yes, it's more concise, but is it more clear? `break` and `continue` differ from `throw` in that the latter just gets out of there, whereas these affect definite assignment more subtly and locally. `return` feels more like `throw`, in that it leaves the whole call context. However, unlike `throw` it is normal control flow. There's something unfortunate about calls like these: ``` c# M(Foo1(), Foo2() ?? break, int.TryParse(s, out int x) ? x * 10 : continue); ``` You start computing arguments and then you decide not to do the call. Wouldn't it have been better (at least more efficient) to decide before starting to compute the arguments? Maybe, but that also leads to more ceremony and less locality of the code. ## Conclusion We are somewhat compelled by the `??` examples. They really do read better than using patterns for nullable things. Let's have someone champion it, and keep it on the radar. ================================================ FILE: meetings/2017/LDM-2017-06-28.md ================================================ # C# Language Design Notes for Jun 28, 2017 ## Agenda 1. Tuple name round-tripping between C# 6.0 and C# 7.0 2. Deconstruction without `ValueTuple` 3. Non-trailing named arguments # Tuple name round-tripping between C# 6.0 and C# 7.0 There are a few unintended breaking changes with tuples that came out through real use cases. Say there's an interface `IUtil`: ``` c# public interface IUtil { void M((int a, int b) x); } ``` I want to implement this interface with code that works both in C# 6.0, but it turns out I can't! In C# 7.0 we force you to use the same tuple element names when you implement an interface member, but of course you can't do that (and aren't being forced to) in C# 6.0, where your only option is to use `ValueTuple<...>` directly. Maybe using the `TupleName` attribute directly in C# 6.0 would help? It is hard to use, but it turns out VS 2015 happily inserts it for you to match the attribute found on the interface member. Little does it know that this attribute is about to become compiler-reserved in C# 7.0, and if you try to compile the C# 6.0 compliant implementation with the attribute in C# 7.0, it still fails, now complaining that you cannot use that attribute in source code! There are no less than two breaking changes here, so we'll address them one at a time: ## Issue 1: C# 6.0 has to implement it without names, but C# 7.0 requires names This will not do. We need to relax the rules in C# 7.0 to allow for a C# 6.0 implementation to continue compiling. A couple of reasonable options: - a tuple type *without* names can always be given when one *with* names is required (but one with different names cannot) - you are permitted to omit required names only if you use the `ValueTuple<...>` syntax, not with tuple syntax The latter is attractive in that it pushes things to a corner, but it does break the fact that `ValueTuple` is exactly equivalent to `(int, string)` in all scenarios. It also has a bit of a problem in establishing whether the tuple syntax *was* used: ``` c# itf I { void M((int a, int b) x); } cls Base { public void M((int, int) y); } // Separate assembly cls Derived, I {} // how does it know which syntax Base used? ``` ### Conclusion Let's go with a strict version of the former option: If a member has *any* tuple element names in it, and is required to match another member (by overriding or implementing), then *all* required tuple element names have to be matched. Only a member declaration with *no* tuple element names in it can override or implement a member in which tuple element names are found, without matching those names. ## Issue 2: VS 2015 Implement Interface will explicitly spit the attributes, but C# 7.0 doesn't allow them This would have been a problem with `dynamic` too, but we never really heard of it. Maybe there was less usage, more of a gap between framework version, or the impact was less because we didn't have round tripping back then. For whatever reason, this never seemed to be a problem before. Options: - Keep the attribute usage an error - Allow it but ignore it - possibly with a warning when tuple syntax is used ### Conclusion Keep it an error, willing to take it up again if necessary. We are unsure that this is really a problem. For the future we want to get better at marking attributes as compiler-only in a well-known way, so that they won't be put in source code even by older compilers. One option is to do that using `Obsolete`; then no compiler will allow them in source, but will be happy to detect or emit them in metadata. # Deconstruction without ValueTuple Deconstructing assignments are expressions, and their value is a tuple. This is natural from a language perspective, even though the result value of assignments is rarely used, and we expect this goes for deconstructing assignments as well. Unfortunately, even in the common case where the assignment occurs as an expression statement (so the resulting tuple is discarded), the compiler currently still requires the associated `ValueTuple<...>` type to be present. That means you have the hassle of importing `ValueTuple` even when you never use a tuple - if you happen to make use of deconstruction. ## Conclusion We'll rewrite the compiler to be more lazy about `ValueTuple<...>`, requiring it only when it actually needs it for code gen. # Non-trailing named arguments It looks like this is making it into C# 7.2. The basic idea is that we allow named arguments *that are in their place, positionally* to be followed by positional arguments. This is mainly for the self-documenting convenience of calling out the meaning of a positional argument where it is non-obvious in the calling context. However, it can be useful for disambiguation of overloads also. ================================================ FILE: meetings/2017/LDM-2017-07-05.md ================================================ # C# Language Design Notes for Jul 5, 2017 ## Agenda Triage of features in the C# 7.2 milestone. They don't all fit: which should be dropped, which should be kept, and which should be pushed out? 1. Static delegates *(8.0)* 2. Native int and IntPtr operators *(7.X)* 3. Field target *(anytime)* 4. Utf8 strings *(8.0)* 5. Slicing *(7.X)* 6. Blittable *(7.2)* 7. Ref structs *(7.2)* 8. Ref readonly *(7.2)* 9. Conditional ref *(7.2)* 10. Ref extensions on structs *(7.2)* 11. Readonly locals and params *(X.X)* 12. ref structs in tuples *(don't)* 13. Overload resolution tie breakers with long tuples *(use underlying generics)* # Static delegates Won't have time, and should probably be in a major release. ## Conclusion 8.0 for now. # Native int and IntPtr operators This won't make 7.2. ## Conclusion Let's try to keep in 7 wave as 7.X (in case we have more point releases) . Probably only one of these will become a feature, but keeping both for now until we decide. # Field target Bug fix, do this anytime. ## Conclusion Make sure it is on the Roslyn backlog. # Utf8 strings There's a bigger story that needs to be worked out here. There needs to be decisions around interpolated strings, formattable strings etc. This feels low on the list, and we don't have enough data to design it yet. ## Conclusion Next major release: 8.0. # Slicing We would want to add a `Range` type, a syntax `3..5` to create a `Range` and then people can write indexers over ranges to implement slicing. We could make it an entirely target-typed thing, where something with a proper constructor could be initialized with a span. Lots to think about. Either way, `Span` APIs would eventually want to make use of this, but would not depend on it from the outset. ## Conclusion This won't make 7.2 but keeping it 7.X to keep thinking about it. # Blittable Small language feature, useful for certain scenarios but not central to 7.2 value proposition. ## Conclusion Let's do it if we get to it. Keep it 7.2, but low pri for that release. # Ref structs Essential to the scenario and also almost done. Still need `stackalloc` rules worked out. We'd prefer to keep that part of the functionality in, but could delay it if necessary. ## Conclusion Stay in 7.2. # Ref readonly Essential to the scenario and pretty much done. ## Conclusion Keep in 7.2. # Conditional ref Not essential but already done. ## Conclusion Keep in 7.2. # Ref extensions on structs Not essential but already done. ## Conclusion Keep in 7.2. # Readonly locals and params Stands on its own. It has some commonality with 7.2 scenarios, but is not essentially tied. Could be done together with ref readonly locals later. Also it has a lot of design work still to be done. ## Conclusion Push to X.X. # ref structs in tuples Tuples can't contain ref structs because those can't be type arguments. ## Conclusion That's a big pandora's box to open, and we won't. # Overload resolution tie breakers with long tuples There's an esoteric difference between whether "specificity" tie breaker should special case long tuple as a flat list, or should work according to the underlying generics, which are nested. ## Conclusion We stick with how it works now, which is the underlying generics. ================================================ FILE: meetings/2017/LDM-2017-07-26.md ================================================ # C# Language Design Notes for Jul 24 and 26, 2017 ## Agenda We started putting a series of stakes in the ground for nullable reference types, based on the evolving strawman proposal [here](https://github.com/dotnet/csharplang/issues/790). We're doing our first implementation of the feature based on this, and can then refine as we learn things from usage. 1. Goals 2. Nullable reference types 3. Rarely-null members # Goals The goals make sense. ## Goal 3: Incrementality Are our existing mechanisms good enough, or is more needed? Currently the proposal has an in-language approach to gradualness, where it mainly comes from the gradual addition of `?`s where nullability is intended, and dealing with the fallout of those one by one. We'll go in on the assumption that this is incremental enough, but trying the feature out may prove us wrong. ## Goal 4: No semantic impact Should this only yield warnings, or should some of them be errors? It should at least be configurable, and we can change our mind about the default settings. If they are errors *in the language*, that affects overload resolution. "Warn as errors" is better because it doesn't affect overload resolution. There may be an opportunity for optimizations based on the compiler's reasoning about nullability; however it would be hampered by all the places where we aren't 100% sure. Worth looking into at some point. ## Goal 5: Library upgrades Should libraries continue to do null checks? Good question. We can't really trust our callers to not pass null. But maybe we can slide more in the direction of just letting the method body fail however it may, if people violate the contract, not checking at every boundary. The Midori experience was that this doesn't make a huge impact to performance, so it might not be worth changing guidelines away from explicit null checks. It's very similar to when we added iterators. Some people will want to have a very high bar, others may be able to relax. We have an accompanying feature proposal (not yet in the strawman) for a short hand null checking syntax for parameters: ``` c# public void M(string name!) { ... } // equivalent to public void M(string name) { if (name is null) throw new ArgumentNullException(nameof(name)); ... } ``` # Nullable reference types The strawman proposes an always-on, non-breaking nullable reference types feature, where a postfix `?` annotation on reference types leads to warnings if values thereof are dereferenced directly or indirectly without (what the compiler recognizes as) a null check. ## Warning on conversion A key point of the proposal is that it mandates a warning on conversion from nullable to not-nullable reference type. This even though there is nothing wrong with having null in not-nullable reference types *until* you opt in to not-null checks. This may seem controversial or overly aggressive, but we ended up approving it for a couple of reasons: - If nullable reference types are to protect their values from being dereferenced even indirectly, they can only do so by warning when you take those values out of the nullable space. - This leads to better incrementality, because you get these warnings piecemeal every time you add a `?`, instead of in a flood when you turn on non-null checks. - These warnings aren't breaking. There's no reason to protect a feature behind a switch if it isn't breaking. ## Tracked variables The strawman allows the null state of dotted chains `x.y.z` to be tracked by the flow analysis. Is this too lax? Imagine an await in the middle. There would absolutely be cases where this would lead us to consider code null safe even when there's a null at runtime. We're assuming people are already checking for null, and are trying to help find where they forgot. We are *not* trying to add new idioms and features, we are trying to lower the bar for getting people to where the warnings they get are useful. Stake in the ground: we will allow dotted chains. We may want to offer suggestions here, or a knob of aggression. ## Local variable declarations The strawman proposes that we make a sort of exception for local variable declarations. There'll be a lot of local variable declarations in existing code using an explicit type (which in existing code never has a `?` on it). Their use of it may be completely null safe, but when something they get assigned gets a nullable annotations, those assignments would still generate a warning. The concern is that such warnings will drown out warnings about actual null-unsafe code: ``` c# void M(string s); M(p.MiddleName); string s = p.MiddleName; // warning here M(s); // but the problem is really here ``` The strawman therefore proposes to *infer* the nullability of a local variable *even* when it is declared with an explicit type. We ended up not agreeing to this. There's a worry about whether people understand the feature if type declarations mean different things in locals versus members. The proposal is too preemptive. We're assuming people will be put off by warnings on this, but is it that bad? The cure may be worse than the disease. When people do not use `var` it is because they *want* to be explicit about the type, and inferring part of it anyway rubs right against that. Instead we will try to mitigate the "drowning out" by having "fix all" options in the IDE, etc. ## Cross-assembly Do we need per-assembly granularity? Concern: heading towards too much complexity. Stake in the ground: - Assemblies opt in with an attribute to having annotations. By default (no attribute) they don't. By default, however, the new compiler will put the attribute. - You can opt out per assembly if you don't want their annotations heeded. # Rarely-null members The compiler code base has a property (`BoundExpression.Type`) that's non-null in 95% of the cases. In a few places under very specific and well-defined circumstances it is allowed to be null. What's the guidance for this kind of property? For this kind of member you want the burden to be put on the few, not the many. It should be declared as not null, and the few places that will use it as null anyway should explicitly circumvent the annotation. Those places are no worse off than today, and are in fact helped a little bit by warnings reminding them to circumvent the non-nullness (e.g. with a `!` operator). ================================================ FILE: meetings/2017/LDM-2017-08-07.md ================================================ # C# Language Design Notes for Aug 7, 2017 ## Agenda We continued refining the nullable reference types feature set with the aim of producing a public prototype for experimentation and learning. 1. Warnings 2. Local variables revisited 3. Opt-in mechanisms # Warnings The nullable features lead to three distinct kinds of warnings: 1. *Direct dereference* of nullable values 2. *Indirect dereference* of nullable values - conversion to not-nullable 3. *Not-nullable is non-null* - Conversion of null value to not-nullable type Examples: ``` c# string s = ...; string? n = ...; var l = n.Length; // Warning 1: Direct dereference s = n; // Warning 2: Indirect dereference s = null; // Warning 3: null conversion ``` The design decisions in the following influence which of these warnings need to be on by default. There are two dual viewpoints on the value of these warnings: - Warning on assignment of null is the most important part of the feature, because that's how null values get in there in the first place. (This is true on API boundaries) - Warning of dereference is the most important part of the feature, because that's what protects you from null reference exceptions. (This is true in code bodies) The fact is that both kinds are important, and go hand in hand in protecting against null reference exceptions. # Local variables revisited The current plan of record is that nullability annotations (`string` vs `string?`) matter just the same in local variable declarations as elsewhere. ``` c# class C { public static string S { get; } // won't be null public static string? N { get; } // might be null public void M() { int l; string s; // won't be null s = C.S; // ok s = C.N; // warning 2: Indirect dereference s = null; // warning 3: null conversion l = s.Length; // ok l = (s != null) ? s.length : 0; // ok string? n; // might be null n = C.S; // ok n = C.N; // ok n = null; // ok l = n.Length; // warning 1: Direct dereference l = (n != null) ? n.length : 0; // ok } } ``` With this approach, like everywhere else, unannotated local variables are non-null, and are protected by warnings from having null assigned to them, as where warning 2 and 3 are given above. `?`-annotated locals on the other hand are subjected to a flow-analysis, and if this shows them to potentially be null at a particular point in the code, a warning is given on dereference, as with warning 1 above. This causes a problem with existing code. Since the `?` annotation wasn't previously available in C#, many local variables that *are* supposed to be null will not have it in their declaration. Their subsequent use may be completely safe (i.e. have null checks everywhere necessary), but with the advent of nullability checking we'd still be bothering the developer with warning 2 (as soon as `C.N` has gotten a `?` of its own) and 3 above. An alternative proposal is to make all locals (of reference type) nullable, regardless of their annotation. There'd be no notion of preventing locals from assuming a null value. Instead, they would all be uniformly protected by flow analysis. Thus, the `s` part of the above code would act as follows: ``` c# string s; // might be null s = C.S; // ok s = C.N; // ok s = null; // ok l = s.Length; // warning 1: Direct dereference l = (s != null) ? s.length : 0; // ok ``` That is, identical to the `n` code above. The upside of this approach is that you would get no "cosmetic" warnings when the nullable feature is turned on and used; i.e. no flood of warnings telling you to add `?` to a bunch of perfectly safe locals. The downside of course is that locals would be treated completely differently from all other declarations in the language. It might be very confusing to read and maintain the code. Unlike the current plan, this would require all of the warnings 1-3 to be opt-in, as there would be nullable things in code even without any explicit `?`s. The current plan at least has a hope of letting warnings 1-2 be always on for source code (since only new code introduces nullable types). This may or may not be important. ## Conclusion We do recognize the problem that the alternative approach sets out to solve. However, we believe that locals should be consistent with other declarations, and that code is better off getting fixed up to reflect nullability in local variable declarations. Explicitly typed locals should declare their intent the same way other declarations do. There are mitigations: - People who use `var` won't have this problem, as the nullability flows with the type. - We can do a lot with "fix all" style tooling to help you quickly fix all local declarations that e.g. are initialized with a nullable expression. Also, if the "cosmetic" warnings get too noisy during upgrades to use the feature, there may be some version of the alternative approach available as an intermediate setting. Let's get a public prototype out. It is fine (maybe actually great) if it's configurable, but should have a default according to where the LDM is trending. It would be nice to have some of the tooling as well ("fix all"), because that's part of the experience we are comparing. # Opt-in mechanisms There are various parts of the nullable reference types feature set that lead to new warnings on existing code: - Assignment of `null` to unannotated reference types is perfectly allowed today: `string s = null;` - Libraries with `?` annotations (which would be compiler-encoded into attributes in the assembly) may be used via older compilers that don't recognize the annotations, then all of a sudden would take effect when upgrading to C# 8.0. In addition, libraries should be encouraged to add annotations to new versions, and upgrading to those versions would yield new warnings. On the whole, all these new warnings are a good thing, and in a sense the purpose of the feature. However, there needs to be *some* way to avoid them, so that they don't constitute a breaking change that requires existing code to be edited in order to get clean. We've been discussing the various upgrade scenarios and possible kinds and granularities of opt-in mechanisms to help the developers achieve a smooth upgrade at their own pace. However, these mechanisms come with their own complexities, and we need to decide which ones are worth that. ## Opt-in for all new nullable warnings This is the notion that there's one big switch on the compiler. If it's on, you get all nullable warnings on your code, if its off you get none. You can leave it off forever if you're just in maintenance mode. You can repeatedly turn it on, fix up some stuff, then turn it back off until your code is properly null-annotated and your null-reference-exception bugs have been fixed. TypeScript took this approach when adding nullable types and the accompanying checks. When on, it means that all existing libraries are seen as non-nullable on all input and output, regardless of whether that's what they intended. This sounds quite unfortunate on the face of it, but has actually proven a lot less of a problem than anticipated: preventing people from passing null to an API is most often the right thing, occasionally unnecessary but benign, and rarely actually limiting to the use of the API. In those cases you can work around it - in C# that would be with use of the postfix `!` operator to shut up the warning. So there's at least an argument to be made that this is "good enough", and it has a lot going for it in terms of simplicity! ## Per-reference opt-in This approach lets the client of a library decide whether to "see" its nullability warnings or not. This might be useful when - A new version of a library adds annotations, but the client isn't ready to deal with them yet - The client upgrades to C# 8.0 and discovers annotations in libraries they were already using, but aren't ready to deal with the warnings yet - The library has not been annotated, and the client experience of treating the library as all non-null is too frustrating This is expected to come with heavy tooling costs, because we need to wire through UI to turn null warnings on and off for each referenced assembly, add support in config files, etc. But it gives the client great control over what they would like to be bothered with right now. ## Library self-opt-in This approach lets the library express, through attributes, whether to consider its unannotated types non-nullable. The default would be no, so that an existing nullability-unaware library would continue working as it does today, i.e. lead to no warnings. We sometimes call this option "URTANN" because the meaning of the attribute is "**U**nannotated **R**eference **T**ypes **A**re **N**on-**N**ullable". The attribute could be used at the assembly level, but you can also imagine making it more granular, using it to mark parts of the code that "have been annotated". The client's compiler would then emit warnings only when nulls are passed to the URTANN-marked unannotated parameters. In this way, the approach becomes a poor man's version of the feature design that has separate syntaxes for "non-null" (`string!`) and "oblivious" (`string`). We decided not to go that syntactic route (for good reasons described elsewhere!), but this would allow close-to-similar expressiveness on API boundaries using an extralingual mechanism (attributes) rather than syntax. A problem with this approach is that it's not clear what code means. Am I currently under URTANN? I'd have to look around and make sure. Also, if highly granular application is allowed, it may be tempting to leave libraries half-annotated in some complicated pattern. Another issue is that this would make it impossible for a client to interpret existing (unannotated) libraries as all-non-null, even when that would be the best thing to do. But it does provide a potentially very granular mechanism for a library to express its own intent, nullability-wise. ## Conclusion We're imagining all kinds of ways the user could get screwed by this feature, and coming up with opt-ins/opt-outs to mitigate. But we don't actually know if it's all that bad, and if those mechanisms are worth the complexity that they add. What we'll do for now is to have only "big switch" feature opt-in in the prototype. We'll use that to learn what scenarios are painful enough that we should reach back into our catalogue of opt-in ideas and try one out. ================================================ FILE: meetings/2017/LDM-2017-08-09.md ================================================ # C# Language Design Notes for Aug 9, 2017 ## Agenda We discussed how nullable reference types should work in a number of different situations. 1. Default expressions 2. Array creation 3. Struct fields 4. Unconstrained type parameters # Default expressions There's an argument to be made that `default(string)` should have the type `string?`. After all, we know it is going to produce a null value! On the other hand, there's also an argument that `default(T)` should produce a `T` for any `T`, and even that `default(string)` is just bad practice, that should be warned on. After all, it's very much like saying `(string)null`, which *would* get a warning. ## Conclusion In keeping with the resolution to the local variables question from [Monday's meeting](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-08-07.md), we hold to an emerging principle that *"the type you say is the type you get"*. That means that `default(string)` should have the type `string`. We should probably also warn on it, since we *are* producing a null value of a non-nullable type. The fix for the warning is to put a `?` on the type (if you want it nullable), or use a `!` (if you don't). It is not clear that `default(string)!` would actually silence the warning. After all it is the `default(string)` expression itself that causes the warning, not a conversion of it. For `!` to work here, we would probably need to special case it. For the new target-typed version of the `default` expression, it would simply work the same way as a `null` literal: if the target type is a nonnullable reference type we'll warn, unless there's a trailing `!`. # Array creation Similar to default expressions there's an argument (but weaker) that `new string[10]` should have the type `string?[]`. After all, it produces an array full of nulls! On the other hand there's also an argument that `new T[10]` should continue to produce a `T[]` for any `T`, and maybe even (somewhat more weakly) that `new string[10]` is just bad practice that should be warned on. ## Conclusion In keeping with the resolution for default expressions above, array creation expressions should have the type they say they create: `new string[10]` creates a `string[]`. It's more open whether it should produce a warning. If it doesn't, then arrays leave a gaping hope in the "null protection" story: you can say `(new string[10])[0].Length` and get a null reference exception without a warning. There is no way that we can establish reliably that a given new `string[]` is initialized to all non-nulls, and it is not clear that this would always be desirable. Many safe uses of `string[]` would simply keep track of which elements have been assigned (with non-null strings) at a given time, and only allow access to those. If we do warn on array creation, that's going to hit every single array creation expression today, except when the element type is a value type. That seems harsh! On the other hand, it gives the developer *somewhere* to realize that there's danger, and that they may want to consider an array with nullable elements. How to silence the warning if not? Maybe you can apply `!` to the array creation expression. Again, this is a more general version of `!` that applies not just to the nullability of the value itself (the array), but also types that it is constructed from. Thus, `new string[10]!` would suppress the warning. We are going to adopt the warning for now in the prototype, since we believe we'll learn more from it. However, we are not very confident that this warning will stay on by default. It may also be one of those warnings that's better served by a standalone analyzer for folks who want to be stricter (maybe combined with some attempts to track initialization), or at least have its own opt-in in the compiler. # Struct fields We've been saying that we want to warn when non-nullable reference fields are not initialized by a constructor. ``` c# class C { string s1; string s2 = "Hello"; string s3; // warning: not initialized to non-null value public C(string s) => s1 = s; } ``` However, all structs can occur uninitialized, with default values in all fields. Does that mean it should be a warning for a struct to have *any* fields of non-nullable reference type? ``` c# struct S { public string s; // warn anyway? public S(string s) => this.s = s; } S[] a = ...; // array of uninitialized S's var l = a[0].s.Length; // null reference exception ``` This would lead to a *lot* of warnings in existing source code! It feels similar to the array warning, in that it would invalidate the *only* way of doing things in pre-C# 8.0 code. Unlike the array situation, however, there is no way to silence the warning with a `!` to say "I know what I'm doing!" We did not decide what to do here. # Unconstrained generics Unconstrained type parameters should be allowed to take nullable type arguments. Even though we've previously said that "unconstrained" is the same as "constrained to `object`", we should now say that it's the same as "constrained to `object?`". This leads to type parameters `T` where *we don't know* if they are nullable reference types or not. Thus, we have to exercise caution and apply warnings "from both sides": on the one hand they may be null, so we should warn on unguarded reference. On the other, they may *not* be nullable, so we should warn on things that might make them null. Of course, you cannot assign `null` to an unconstrained type parameter today (it might be instantiated with a value type), but there are still default expressions: ``` c# T M() { return default(T); // warning return default; // warning } ``` Now this is a problem: default expressions were added to the language *specifically* so you could use them in a generic setting! And now we would say you cannot use them? That seems harsh! Of course you can silence them every time you use them, but that's a nuisance. *Not* having a warning would be a hole. We could consider it anyway (as we might for `new string[]`), and just say that this sacrifices safety for convenience. Another option is to come up with a new type constructor over T that means "nullable if reference type". We could overload the postfix `?` if it's not too confusing. so `T?` on an unconstrained generic type would mean: - `V` if `T` is a value type `V` (either nullable or nonnullable) - `N` if `T` is a nullable reference type `N` - `C?` if `T` is a nonnullable reference type `C` It's a little subtle, and has some weird consequences, especially if we use the `T?` syntax, since it wouldn't *always* be a nullable type: ``` c# void M() { T? t = null; // would be an error! } ``` Also `T?` would mean different things depending on whether `T` has a `struct` constraint or not: ``` c# T? M1(T t) => t; T? M2(T t) where T : struct => t; var i1 = M1(1); // 'int' var i2 = M2(2); // 'int?' ``` This is quite confusing, but might just speak to having another syntax than `T?`. On the other hand, that's *yet* another syntax, and extra complexity in the language. It's worth noting that `T?` would be useful as the return type of `FirstOrDefault` and its brethren among the query operators, which otherwise wouldn't have a good way of expressing their signature. That would at least address *one* of a handful of patterns that aren't well served by nullable reference types as currently proposed. We didn't reach a verdict on this topic. ================================================ FILE: meetings/2017/LDM-2017-08-14.md ================================================ # C# Language Design Notes for Aug 14, 2017 ## Agenda We looked at the interaction between generics and nullable reference types 1. Unconstrained type parameters 2. Nullable constraints 3. Conversions between constructed types # Unconstrained type parameters Unconstrained type parameters should allow nullable reference types as type arguments. One way to look at it is that the default constraint is no longer `object` but `object?`. This is important, so that existing unconstrained generic types work with nullable reference types; e.g. `List`. This means that the body of a generic type or method has to deal with type parameters that can be instantiated with both nullable and nonnullable reference types. To be safe, it must impose both nullable and non-nullable restrictions: ``` c# var s = t.ToString(); // warning: dereference without null check T t = default(T); // warning: may be creating null value of non-nullable ref type ``` ## Dereferencing The rules around dereferencing values of unconstrained generic type should be no different than for nullable reference types: The compiler needs to see you check for null, or else you get a warning. This is not likely to happen a lot, as there aren't that many members available on unconstrained type parameters; only the `object` ones. ## Default expressions `default(T)` is of type `T`, and in and of itself yields a warning, because it may create a null value of a non-nullable type. This is just like `default(string)`, as per previous meeting's decisions. How can you fix this warning? Well there are no super good options. After all, you are writing code that will create a null value of a non-nullable type. We should make sure that e.g. the `!` operator is capable of silencing the warning: ``` c# T t = default(T)!; // if that's allowed, or some version of it T t = default!; // if that's allowed ``` ## Defaultable types Another option is to introduce a notion of "defaultable types" that can be applied to an unconstrained type parameter. For all type arguments it means the type itself, except for non-nullable reference types, where it is the nullable counterpart. We may want to overload the `?` syntax for it: ``` c# T? t = default(T?); // if that's allowed ``` With the decisions above, this is more of a "side feature", and doesn't impose itself on users who don't ask for it. This may be important, as it is on the complex side. The previous notes had examples of some confusing aspects of this type constructor. This feature would also be useful to express patterns of the `FirstOrDefault` ilk: ``` c# T? FirstOrDefault(this IEnumerable src) ``` Similarly, we can imagine someone out there who wants to express occasional APIs that take null as an argument, even when their type argument does not allow it. You may have a more local contract that allows null even when the overall type or generic context does not. Let's embrace `T?` in the prototype and look out for confusion, usage, implementation issues, etc. Somebody will need to work on type inference rules in the presence of `T?` in signatures. ## TryGet If we embrace defaultable `T?`, it would technically be a correct type for the out parameter of `TryGet` methods: ``` c# bool TryGet(out T? value); // correct, but weak ``` It's probably not useful, though. Consumers only very occasionally look at the output when the result is fall. The majority, instead, would be annoyed that they cannot assume the value is non-null when they checked the bool and found it to be true. Let's put off decisions around this for a bit. There should probably be a special mechanism for this scenario. # Nullable constraints Regardless of whether we allow nullable reference constraints explicitly, they will exist anyway: - unconstrained type parameters are like having the `object?` constraint - inherited constraints on overrides may implicitly be nullable So we might as well embrace nullable reference types as constraint. The rule is that if any constraint is non-nullable, then instantiations with nullable reference types yield warnings. ## object as constraint Currently `object` is disallowed as an explicit constraint, since it is implied. We should start allowing `object` as a constraint. ## The class constraint Should the `class` constraint allow type arguments that are nullable reference types? If yes, how do you ask for only the non-nullable ones? If no, how do you allow the nullable ones? It seems we have a couple of syntactic options: 1. `class` allows nullable, `class, object` does not 2. `class?` allows nullable, `class` does not 3. `class` allows nullable, `class!` does not The 3rd option adds new syntax that isn't warranted. The 1st option adds no new syntax, but it is tedious to specify a non-nullable reference constraint. The 2nd option seems most in line with the general direction. However, there is a slight concern that people today might be using the `class` constraint *specifically* so that they can use `null`. All of those will have to add `?` to their `class` constraints. We will still go with option 2 on general principle. # Conversions between constructed types There's an identity conversion between constructed types that differ only in the nullness of their type arguments. But sometimes there's a warning. When? How do you get rid of it? ``` c# Dictionary d = new Dictionary(); // warning in both directions ``` Essentially the rule is as follows: - Generally warn on non-matching nullability in both directions. - For covariant type parameters, don't warn going from `T` to `T?` - For contravariant type parameters, don't warn going from `T?` to `T` We would like the `!` operator to be able to silence this, when there is an expression. An explicit cast should also work, but may "do too much". This may be quite complex to implement in current compiler, but we think it is important and do want to push on it. ================================================ FILE: meetings/2017/LDM-2017-08-16.md ================================================ # C# Language Design Notes for Aug 16, 2017 *Quote of the day:* > "It's an open question whether we go out with a bang`!`" ## Agenda 1. The null-forgiving operator # The null-forgiving operator How exactly does the null-forgiving post-fix `!` operator work? Proposal: 1. *Target typed*: `e!` implicitly converts to `T` if `e` does, but without nullability warnings - `string s = null!;` - `string s = default!` - `string s = GetNameOrNull()!;` - `List l = GetList()!;` - `List l = GetList()!;` 2. *Inherent type*: if the type of `e` is a nullable reference type `T?`, then the inherent type of `e!` is `T` - `var s = GetNameOrNull()!;` - `GetNameOrNull()!.Length;` 3. *Default expressions*: if `T` is a non-nullable reference type, then `default(T)!` suppresses the warning normally given by `default(T)` For 2, an alternative is to have a dedicated `!.` and `![...]` operator, cousins of `?.` and `?[...]`. Then you wouldn't get to factor out to a local with `var`, though. 3 is a bit of a corner case. Most people would choose to just rewrite it to something else - there are plenty of options. But `default(T)` is a good strategy for code generators, so probably worth keeping the ability to silence that warning. We could generalize `!` to silencing all nullability warnings even in subexpressions. This seems ill-motivated, though, and there's no particular expectation that you want silencing in subexpressions at the same time you want it on the overall expression. If `!` is applied in a place that yields no nullability warnings, does that lead to a warning? No. We don't want to create a new source of warnings caused by a warning-suppressing operator! There is a legit scenario, which is to clean up superfluous "!"s when a depended-upon API gets properly annotated. But this seems more the province of analyzers or similar tools. We can make `!!` an error. If you really want two consecutive bangs (we don't believe there's *any* scenario, other than swearing) you can parenthesize: `(e!)!`. An alternative is to make the type of `e!` oblivious, if we choose to embrace a notion of oblivious. That's attractive in that it makes a type for "something that doesn't yield warnings", but it's also viral - could lead to many things not being checked. It's an option to be considered in future. ## Conclusion Follow the proposal. Make `!!` an error. ================================================ FILE: meetings/2017/LDM-2017-08-21.md ================================================ # C# Language Design for Aug 21, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Type classes Multiple implementations (ints and groups) - You can explicitly provide the type argument for an implicit type parameter - (but it might get messy) Need explicit instance - Nothing fundamental preventing a more structural approach - Two levels of inference possible: - a: explicit instance, infer members - b: implicit instance even To bridge to existing interface-based abstractions, you can just provide a very general, generic instance Need an implicit type parameter but don't use it. Maybe a bit too magical. Might be better to require dotting off of the implicit type parameter. For operators that would be nice, though. Instance members? need some syntax like extension methods, maybe, or like explicit interface implementation. Concepts can be for more than one type, so they are not always tied to a single domain type. This may be a step too far, but it does have real value: Graph algorithms that have both Node and Edge types. Main competitor, conceptually, would be something that allows for interfaces to play the role of concepts. That comes with challenges of its own, and lots of limitations. But that sort of the thing you have to justify why you're not. Could you use this to make the environment of a lambda a struct? Combined with closures as structs, passed by ref. ================================================ FILE: meetings/2017/LDM-2017-08-23.md ================================================ # C# Language Design Notes for Aug 23, 2017 ## Agenda We discussed various aspects of nullable reference types 1. How does flow analysis silence the warning 2. Problems with dotted names 3. Type inference 4. Structs with fields of non-nullable type # How does flow analysis silence the warning What exactly is the mechanism by which a nullable variable can be dereferenced without warning, when it's known not to be null? ``` c# void M(string? s, T t) { if (s != null) WriteLine(s.Length); // How is the warning silenced? if (t != null) WriteLine(t.ToString()); // How is the warning silenced? } ``` So far we've said that when a nullable variable is known to not be null, it's *value* is simply considered to be of the underlying nonnullable type. So for `s` in the above example, inside the `if` where it is tested, its value is of type `string`. Thus, the dereference does not earn a warning. However, this doesn't immediately work for type parameters. In the example above, we don't *know* that `T` is a nullable reference type, we just have to assume it. Therefore, it does not *have* an underlying nonnullable type. We could invent one, say `T!`, that means "nonnull `T` if `T` was nullable", but it starts to get complex. An alternative mechanism is to say that the type of a variable and its value *does not change*. The null state tracking does not work through changing the type, but simply by directly silencing null warnings on "dangerous" operations. This works for both `s` and `t` above. With the new proposal, you can better imagine separating out the null warnings to an analyzer, because the type system understanding of the `?` would be logically separated from the flow analysis. IntelliSense will be a challenge: ``` c# void M(string s) => ...; string? s = "Hello"; M(s); // Does IntelliSense confuse you here if the type of 's' is shown as 'string?' ? ``` But there's non-trivial experience work in the IDE no matter what we do. ## Impact on type inference ``` c# string? n = "Hello"; var s = n; // 'string' or 'string?' ? ``` If null state affects the type, then `s` above is of type `string`, because `n` is known to be non-null at the point of assignment. If not, then it is `string?` (but currently known not to be null). This also affects which type is contributed to generic type inference: ``` c# List M(T t) => new List{ t }; void N(string? s) { if (s != null) WriteLine(M(s)[0].Length); // 1 if (s != null) { var l = M(s); l.Add(null); } // 2 } ``` If the type of `s` changes to `string` in the non-null context, then the calls to `M` infer `T` to be `string`, and return `List`. Thus, `//1` is fine, but `//2` yields a warning that `null` is being passed to a non-null type. Conversely, if the type of `s` remains `string?` in a non-null context, then the calls to `M` infer `T` to be `string?`, and return `List`. Thus, `//2` is fine, but `//1` yields a warning about a possible null dereference. ## Impact on `!` operator The currently proposed `!` operator changes the type of a nullable value to a non-nullable one. The deeper philosophy behind this, though, is simply that `!` should do the same to the value of a variable (that is not target typed) as a non-null null-state would have done. ``` c# string? n = GetStringOrNull(); var l = n!.Length; // why no warning? var s = n!; ``` With the current proposal, `n!` would be of type `string`, and there'd be no warning on the dereference because of that. With the new proposal, `n!` would be of type `string?`, but the `!` would itself silence the warning on dereference. In a way the new proposal unifies the target-typed and non-target-typed meanings of `!`. It is never about changing the type of the expression, just about silencing null warnings. We need to get more specific about exactly what it means that it "silences the warnings". ## Conclusion Let's roll with the new approach. As always, we'll keep an eye on whether that leads to a good experience, and are willing to revisit. # Dotted names problems There are different approaches with different safeties: - Don't track dotted names at all (safest): this pushes you to introduce a local for every null test of every field or property - Track dotted names but invalidate when prefix is "manipulated" (passed or called a method on): doesn't catch aliasing or indirect mutation - Track dotted names and assume it's still valid no matter how prefix is manipulated: bigger risk that it's wrong The problem is that people have code today that checks on dotted names. Should we have a dial? Otherwise we need to decide how we weigh safety vs convenience. ## Conclusion In the prototype, since dotted names aren't implemented, let's try the more restrictive approach. People will let us know where this is too painful. # Type inference How are nullable reference types inferred? Proposal: - Consider nullness an orthogonal aspect to the rest of the type being inferred - If any contributing type is nullable, that should contribute nullness to the inference - A `null` literal expression should contribute nullness to the inference, even though it doesn't otherwise contribute to the type We should consider this for nullable value types as well. # Structs with fields of non-nullable type Structs can be created without going through a declared constructor, all fields being set to their default value. If those fields are of non-nullable reference type, their default value will still be null! It seems we can chase this in three ways: 1. Not at all. We just aren't that ambitious. 2. We warn on all fields of structs that have non-nullable reference types. That's a *lot*! How do you "fix" it? Make them nullable? No version of the `!` operator works here, since the whole point is you don't control initialization from user code. 3. We warn whenever a struct that has such fields is created as a default value. In other words, we treat the type the same as a non-null reference type, recursively. (And we warn on passing for a type parameter constrained by `new()` or `struct`?) ## Conclusion The options to handle are painful. No conclusion. ================================================ FILE: meetings/2017/LDM-2017-08-28.md ================================================ # C# Language Design Notes for Aug 28, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda # Ref-like types safety rules https://github.com/dotnet/csharplang/blob/master/proposals/span-safety.md#draft-language-specification The need for ref-like types quickly arises, when you need to flow more than one span, etc. The fact that span is array like is not very interesting for the safety rules. The interesting thing is that it embeds a reference. Indexing is not a core concern in the general case. Once you can embed refs in struct, the question: How can you assign them? With assignment, you can now assign to ref parameters of ref-like structs, so every assignment is a potential return. "Let's not refer to local data" does not work here: - we *want* the feature to refer to local data. stackalloc, etc. - lightweight params, etc. - we want to protect our future ability to allow this even if we could live with it now So: We need to allow local data inside ref-like structs. We tie a scope to variables holding ref-like structs. That scope is based on what the variable is initialized with. We call this the "escape level". Essentially, on the assignment we check that the assigned-to variable's scope is no wider than the assigned value's scope. The rules assume that everyone plays by them. Special craziness for multiple arguments, some which are refs, some which are refs to ref-like: ``` c# void M(ref Span x, ref int y) { // One of two things must be true about this: // 1. This is an error // 2. The caller has guaranteed this is safe x = new Span(ref y); } ``` We need to do this on the caller, who knows most about the situation. The caller therefore has to assume that there is cross-assignment in the callee. Q: Could you track the escape level during flow analysis, changing it locally? It's possible we could, but it seems extremely complex. We propose strict rules now; a flow analysis later would be more permissive, if we can figure it out. That's an important property! "ref safe to escape" is how far you can escape the variable. "safe to escape" is how far you can escape a value. Only relevant if that value is a ref-like struct that can contain a ref. Quality of error messages: We'll do a decent job, but a) it rarely gets complicated/cascading, b) we can improve them later if they turn out not to be good enough. Rust has error messages about this kind of thing that are just stellar, but this is bread-and-butter code in Rust, whereas it's exceptional here. Defer: Should uninitialized ref-struct locals be allowed? Defer: Should default values of ref-struct types be allowed? Methods can return by-ref, ref-like and by-ref ref-like! We say that by-ref ref-like parameters are not returnable! Open issue with ref dynamic: Treat them like in parameters, in how we deal with compiler-generated temps. Language restrictions: - Local functions - it's a shame that we can't factor out this stuff to local functions, because of the closure rule. But we can fix it later if necessary. ================================================ FILE: meetings/2017/LDM-2017-08-30.md ================================================ # C# Language Design Notes for Aug 30, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # IAsyncDisposable `IAsyncDisposable` is sort of required, due to the way we translate `foreach`. Also it's useful in a lot of situations. Don't take a `CancellationToken`, since when you cancel an operation you don't want to also cancel the clean-up. Contract should be similar to `Dispose`, where you can call multiple times (sequentially) without problem. If an enumerator (for instance) implements both sync and async, the client is only required to call one of them. The expansion of foreach will prefer sync or async naturally through the expansion. Does the CT given to an enumerator apply during disposal? That would need to be up to the implementation. Standard guidance would be not to cancel any of what the disposal does. Advanced implementors might use the fact that the enumerator was canceled to skip some work. # Alternative IAsyncEnumerable pattern There's an alternative that does "explicit chunking", by asynchronously yielding synchronous enumerables, to be consumed in a nested loop. Tests show that this is a lot more efficient than the simple design. We are cautiously leaning in this direction. As long as we can work out iterators, which are not fully explored. # ConfigureAwait Can use extension methods on IAE # foreach Proposal to support foreach over enumera_tors_, not just enunera_bles_. Should enumerators be first class currency? There's a path we can take where everything is still enumerables, and you can get enumerables from other enumerables representing e.g. something with a CT, a certain starting point, etc. Downsides: If you're just foreaching over an enumerator, is it your responsibility to dispose it? Also, should LINQ then be implemented over those as well? Let's not open that Pandora's box for now. Let's stick with enumerable. Consider a ToEnumerable on enumerators. ## Syntax options Need to think about it in connection with `using` also. We'll stick with `foreach await ( ... )` for now. ## pattern-based Similar to today. We get that one layer of optimization. There's more wrapping here (MoveNextAsync, WithCancellation), so we quickly still end up with interface dispatch. # iterators Same as current iterators, except with an `async` keyword, and IAsyncEnumerable/tor return type. Needs to feel exactly like putting iterators and async methods together. Could consider custom builders for this. Skip for now. If we do the fancier version of IAE we need to find out how to compile. Need to spec the contract completely and then follow that. (Probably some sequences of calls would be unspecified). ## Cancellation There are two competing models around this. 1. Take CT into GetEnumerator, and find a way to syntactically expose it in an iterator body 2. Don't pass CT's into the enumerator at all; pass them to iterator methods We actually want to start out with 2, and see if that gets us into trouble # LINQ There are 200 overloads on enumerable, and most would need to be duplicated on IAE. Then, mixing sync and async would add another axis of this. Ix has an implementation of these already, and they would adjust to what we decide. If you have that, query expressions would work to some degree, when the bodies are sync. However, we don't currently allow `await` in query clauses, because the lambdas we target don't have the `async` modifier. Fine to not do it now. At some point we would want to deeply investigate how to get those awaits in. ================================================ FILE: meetings/2017/LDM-2017-09-25.md ================================================ # C# Language Design Notes for Sep 25. 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Ref readonly locals We have nowhere to put a ref readonly result. Ref readonly locals are like ref locals, except that they don't allow mutation of the ref'ed variable. ``` var x = a[1]; ref readonly var r = ref a[1]; ``` We could have `var` infer the `readonly` as well. It wouldn't be breaking to add later. Why do we allow the silent copying of readonly struct values in these new scenarios? Do we like that? No, but for consistency. People will need to use analyzers already to catch the existing cases. Those analyzers should just have this feature in there as well. We agree that this is a reasonable feature to have, and the design is right. Like ref locals, these aren't currently reassignable, but there's no dependence on that. We could change it later. There's then technically room for an extra `readonly` in front. For ``` c# MyRefTaker(42); MyRefTaker(ref MyRefReturner()); ref readonly int r = 42; ref readonly int r = ref MyRefReturner(); b ? 42 : MyRefReturner() return ref r; ref readonly int x = a[1]; ``` Discussion about whether the implicitness is a good thing. There are other options: require "ref" in arguments, require "in" in arguments. We still want the implicit ref in parameters. For operators, during overload resolution we ignore the refness. ## Conclusion Let's flip to requiring `ref` in argument position. `ref 42` and `ref x+y` etc are allowed. (Other small decisions) ================================================ FILE: meetings/2017/LDM-2017-09-27.md ================================================ # C# Language Design Notes for Sep 27, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** QOTD: `in int i = in init(in it);` We changed our minds to allow and require explicit `ref` for `ref readonly` arguments. ``` c# const int x = 42; foo(ref x); ``` In the compiler, the order of things change, which is the most churning fix. Technically not hard to do. ## Concern 1 Is 'ref' the right keyword? Putting `ref` signals to the caller that mutation may be happening. You need to understand what you are passing it *to* in order to know whether it's safe from mutation. In other cases, like `return ref x` or `ref readonly r = ref x` the `readonly` is nearby, and it doesn't cause the same concern. ## Concern 2 Also, the `ref` in front of rvalues feels wrong. ``` c# Foo(ref 42); Foo(ref await FooAsync()); ``` There's an argument that `ref` is about *how* the value is passed, not about the guarantees of the callee. On the other hand we use both `out` and `ref`, where the only difference is the guarantee. Compromise position: Use keyword to pass by ref, no keyword to pass "by value" (really by creating a new local and putting the value in). Warn (or error) when an lvalue is being passed without the keyword, that could have been passed by ref. * Is it important to be able to see in the code if something is passed by ref? * Is it important to be able to see in the code if something is copied? * Is it important that the parameter passing mode reflects the contract? * Is it important that the same keyword is used for passing and returning? Other compromise: - No modifier: Do you best - Modifier: Require ref, don't copy The keyword would be `in`. Should it then also be `in` in parameter declarations? A danger is that people misunderstand it for "being explicit" about value parameters, whereas `ref readonly` leaves no such room for interpretation. On the other hand, this may be a case where we'd overoptimize for newcomers to the feature, leaving too much syntax for too little benefit. A bit of education, maybe some warnings, maybe analyzers... ## Conclusion - At the parameter declaration site, use `in`. `ref readonly` is no longer allowed. - At the call site, `in` is optional. If it is used, the argument has to an lvalue that can be passed directly by ref. If not, then the compiler does its best: copy only if necessary. No change to `ref readonly` return signature, or to `return ref`. No change to `ref readonly int r = ref ...` We would consider allowing `ref readonly int r = 42;` in the future, but it is only useful once we have ref reassignment. We could consider a warning for `in` parameters of small types (reference types and built-in value types, maybe), but sometimes you do need that. Better left to an analyzer. What about ambiguity: ``` c# M(in int x){} M(int x){} M(42); // how do you resolve in direction of value ``` We could deal with this through a tie breaker in overload resolution, but it's probably not worth it. If you have an API like that, there's going to be a method overload you cannot call. We could always add it later. This scenario isn't entirely esoteric: if I want to update my API from value-passing to in-passing, then either: - I add an overload. Then all existing call sites are broken on recompilation. - I replace the current overload. The all existing assemblies are broken until recompilation. Instead, you can add an optional parameter, change parameter names etc. to give another means of distinguishing. Delegate conversion: No contravariance in the type of parameters, unlike value parameters. THis is because the CLR doesn't know about it. It's similar to the restriction on out parameters. Conditional: If one of the branches is readonly, the whole thing is readonly. That means that calling a mutating member on it would mutate a copy, *regardless* of whether the actual branch chosen was readonly or not: ``` c# void M(bool b, in x, ref y) { (b ? ref x : ref y).M(); // M is always called on a copy, even if b is false } ``` # ref structs ## Syntax Partial has to keep being the last modifier; ref can for now only be right before `struct` or `partial struct`. Long term we want `ref` to float freely as a modifier. Just may not get to do the work now. ================================================ FILE: meetings/2017/LDM-2017-10-02.md ================================================ # Milestone philosophy ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** 7.3 is a bucket for next steps with pattern matching. Non-exhaustive list - recursive patterns - non-null patterns - switch expression - negated if-condition 8.0 is for major language features # Discussion on how we get input We should solicit problems, not just solutions # Triage ## 945 Could make it always prefer by-value as a tie-breaker. ## 933 Motivating scenarios: 1. Hold on to the content variable in linked list elements 2. assign one as a default and have an if overwrite it with another Syntax! Should we be putting `ref` in front of the LHS, or just the RHS? ``` c# ref r = ref v; // or r = ref v; ``` Not requiring ref lets us: - Be more terse - Work as an expression (because no expression starts with ref) Requiring makes it syntactically clear whether you are assigning to `r` itself (in the ref space) or to the variable currently pointed to by `r` (in the value space). Also, what the hell does code mean if e.g. a ref-reassigning expression occurs as a ref or out argument? ``` c# M(out r = ref v); //What? ``` We'd just recommend parenthesizing the assignment, like we recommend everywhere else assignments are used as expressions. There's a limit which is that there's no proper default, so we'd still always require initialization, picking up the lifetime from the initializer. This is a bit painful when you want it to have global lifetime (no good default to provide). We should instead allow you to not have an initializer. We do definite assignment analysis. It has global lifetime. ``` c# ref readonly tmp = ref Get(); M(in tmp); ``` Annoying that there's sort of three different ways to talk about a `ref readonly`: `ref readonly`, `ref` and `in`. Should we switch parameter to `ref readonly`? Allow choice. No: Let's keep having only one way of doing it, and let's have that way be consistent with what you say at the call site. ================================================ FILE: meetings/2017/LDM-2017-10-04.md ================================================ # C# Language Design Review, Oct 4, 2017 *Quote of the Day:* > "You don't get to use this with your grandfather's Oldsmobile" ## Agenda We looked at nullable reference types with the reviewers, Anders Hejlsberg and Kevin Pilch. 1. Overall philosophy 2. Switches 3. Libraries 4. Dotted names 5. Type narrowing 6. The dammit operator 7. Array covariance 8. Null warnings 9. Special methods 10. Conclusion # Overall philosophy Think of this feature as a linting tool, an analyzer. It will help you find many bugs, but it will not guarantee anything. It is important that it does not lead to too much inconvenience, and does not yell too much over existing code. Too much whining at programmers makes them turn the feature off. First appearances are everything. We should not think of the feature as dialable, with multiple switches or settings. We should design our way to the best balance, and stick to it. One switch: On or off. Don't worry too much about unannotated libraries. Push on the library owners to get annotations, and live with the lack of them until that happens. In many cases the feature will still be useful even on unannotated libraries, because the default of assuming non-null is often going to be correct. # Switches Have just one on/off switch for the warnings. The annotations should be allowed regardless of whether the warnings are on or off. New projects should have it on by default, existing projects probably off. # Libraries We should push to get our libraries upgraded to have annotations. Since it's only about adding attributes, it's possible we can do something with reference assemblies, leaving the actual binaries untouched. Even if some libraries aren't upgraded, or not at the same pace, it's not a disaster, and we shouldn't hold up the feature waiting for a sufficient amount of libraries to be ready. Instead, use the availability and (hopefully) popularity of the feature to drive libraries to annotate. # Dotted names The flow analysis should track dotted names. We have experience from TypeScript, and customers there would definitely complain if we didn't. We also know that it is common in existing code bases to check a dotted name (e.g. a property) for null, then dereference it. In particular, of course, "dotted" names with an implicit `this.` are common. It would be a disaster for existing code not to track the null state of dotted names. If we do, what does it take to invalidate assumptions about a dotted name? In principle, any intervening call can cause a property to change. Even another thread could do that! But assuming the worst on this is just going to lead to a lot of pain. We have to be lenient, and assume that dotted names remain the same unless something in the dotted chain is assigned to, or maybe passed by out or ref. There's going to be a type of subtle bug that we won't catch as a result of this lenience. But the price of catching it is too high in most cases, in terms of the amount of perfectly safe existing code that it would flag. # Type narrowing We are currently tracking nullness of variables separately from the type system. Even when a nullable variable is known not to be null at a given place in the source code, it's *type* is still nullable, and that's what we feed into e.g. type inference around it. This allows us to handle things that are *not* necessarily nullable, such as type parameters. However, when we *do* know the type is nullable and not null, it feels like we're throwing away useful information not to narrow the type. We should consider a hybrid, where we narrow the type to non-nullable when we can. Similarly for use of the `!` (dammit) operator. Yes it should silence warnings on conversions and dereferencing. But it should also narrow the type of the expression to non-nullable when possible. # The dammit operator TypeScript also has this, and it is often a nuisance that it doesn't "stick" - it applies only locally to the expression it is used on. It might be nice with some sort of assertion that sticks throughout the scope of the variable. We should consider it. It's a little suspicious to use the same operator for silencing warnings and narrowing the type, but probably better than having two different ones. Casts aren't good for suppressing warnings, because they also imply a runtime check (which we may not want, since the suppression of the warning may be because you *want* to allow a null). # Array covariance We currently treat arrays as invariant with respect to element nullability. ``` c# arrayOfNullable = arrayOfNonNullable; // warning ``` But arrays are covariant, albeit unsafely. We should consider applying the same covariance to nullability, for consistency. I.e., we would allow the above code without warning. That is *even though* a null check won't be part of the runtime type check that arrays do on write. The alternative is worse. # Null warnings we should warn on the majority of cases where a null value makes it into a nonnullable variable. However, there are cases where it simply gets too harsh on existing code. Places where we should warn: - constructors that don't initialize fields of nonnullable reference type - converting a null literal to a nonnullable reference type - passing or assigning a nullable reference value to a non-nullable reference type - `default(T)` expressions when `T` is a nonnullable reference type The last one could also yield a `T?` and no warning, but that would just lead to other warnings further down the line. Besides, default isn't used very much on reference types. On the other hand we should *not* warn on array creation expressions, even though they create a whole array of forbidden null values: ``` c# var array = new string[10]; ``` These are numerous in current code, and very often they are fine. Besides, the code one would have to write instead is quite unappetizing! We *could* maybe find special cases, where we could warn, e.g. if a newly created array of nonnullable element type is read from without ever having been written to at all. # Special methods Some methods have a special relationship with null: if `string.IsNullOrEmpty` returns false, then the argument was not null. If a `TryGet` method returns false, then the resulting out parameter may be null. TypeScript has a notion of user-defined predicates, which are methods that claim to establish membership of a given type for a given value. We may try to think along similar lines, and consider whether methods can somehow convey extra knowledge about nullability. # Conclusion This is going to be a great feature, and people will love it. Have a list of known holes, and make clear that there's no guarantee. We're not going to get everything in the world. It's not possible! And even some of the possible stuff is too inconvenient. There are only so many greenfield projects in this very established world. Don't be discouraged by low adoption in the beginning. These things take time. ================================================ FILE: meetings/2017/LDM-2017-10-09.md ================================================ # C# Language Design Notes for Oct 9, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # 882 Negated if or negative patterns Three approaches 1. bang outside if condition (then should I do that on while etc, too) `if !(o is int i)` 2. negative patterns (but not very useful recursively) `not int i` 3. `is not` as an expression operator # 867 Avoid some statement cliffs... Put it in X.X with a note to consider again when we have match expressions # 414 There's a "there" there. We think this should be addressed, and will keep the championing issue to represent it. However, it should be different: 1. It should not be strongly tied to the `Dictionary` type, but be target typed 2. We should look at initializing immutable objects (also for object and collection initializers) 3. We already have index initializers. Are they good enough? # 973 Declaration expressions Last time, we had two issues: 1. Weren't ready to commit to scoping rules 2. Weren't sure that we could get decent error recovery on syntax 1 is dealt with. 2 was more that it was hard to show intellisense because more things were legal Scenario is introduce a local variable in expressions without having to use trivial pattern matching. Also ref. We feel like we need to spend more time with it to judge its value. 8.0 for now to trigger that discussion. # 881 and 33 Fits with nullable in 8.0 # 185 Settle this in the 7.3 timeframe # 187 Blittable # 435 # 287 # 32 # 125 Missing, but not much ask for it # 111 We would want to deal with single parameters. A problem is that discards do not shadow today, whereas identifiers do. We may want to change that. # 191 Need more motivation # 190 Some open design discussions # ================================================ FILE: meetings/2017/LDM-2017-10-11.md ================================================ # C# Language Design Notes for Oct 11, 2017 ## Agenda We looked at the Oct 4 design review feedback for nullable reference types, and considered how to react to it. 1. Philosophy 2. Switches 3. Dotted names 4. Type narrowing 5. Dammit operator type narrowing 6. Dammit operator stickiness 7. Array covariance 8. Null warnings # Philosophy Feedback: We should view this as a linting tool. We have to make most existing code pass muster, and it's ok with a little more complexity and forgiveness in the rules to achieve that. ## Conclusion We agree with this general philosophy, and it is helpful to apply it to specific decisions. # Switches Feedback: Don't have many switches and levers. Just "on" or "off". "Off" would suppress the warnings, but the `?` syntax would still be allowed. Make the hard decisions once and for all at the language level, rather than leave people with too many options. ## Conclusion This is a good philosophy. We do think that there's possibly room for an "Xtreme" mode as well, for people that care more about catching more cases regardless of inconvenience. # Dotted names Feedback: We should track dotted names, and be very forgiving about what invalidates the null state. Otherwise it violates the general philosophy and complains about too much existing code. Things that *would* invalidate non-null-ness of a dotted chain is assigning to (or maybe passing to an out or ref parameter) the variable itself or any mutable prefix. ## Conclusion Agree! If we adopt an Xtreme mode, this is probably one of the places where it would be harsher. # Type narrowing Feedback: when a variable of a nullable reference type (e.g. `string?`) is known to not be null, we should consider its value to be of the narrower type `string`. ``` void M(string? n) { if (n == null) return; var s = n; // s is string, not string? var l = s.Length; // ok n = null; // ok s = null; // warning } ``` We previously abandoned this approach, because it doesn't work for e.g. type parameters, where we don't know if they are nullable reference types or not, and don't necessarily have an underlying non-nullable type to narrow *to*. ## Conclusion This is one of those places where we should forego simplicity for friendliness. We should adopt a hybrid approach: When a type is a known nullable reference type, then having a non-null state *should* narrow its type. When it is a type parameter that might be instantiated with nullable reference types, then we can't narrow it, and should just keep track of its null state. # Dammit operator type narrowing Feedback: The dammit operator should also narrow the type of a nullable reference to be nonnullable. ``` c# void M(string? n) { var s = n!; // s is string, not string? var l = s.Length; // ok } T[] MakeArray(T v) { return new T[] { v }; } void M(string? n) { var a = MakeArray(n!); // a is string[], not string?[] var l = a[0].Length; // ok } ``` ## Conclusion `! should keep its warning suppression, but also narrow the outermost type when it can, to match the new behavior when null-state is non-null. There's hesitation because of the muddiness of using `!` for two different things. But we don't have a better idea. Explicit casts are quite verbose. We may need to revisit later, but for now, `!` suppresses warnings *and* de-nullifies the type when it can. # Dammit operator stickiness Feedback: The dammit operator lacks "stickiness" - you have to keep applying it (or introduce another local). One idea is to maybe have some top-level "assertion" that would declare a thing not-null for the whole scope. Another idea is to have `s!` influence null state for flow analysis, staying "valid" for as long as a non-null state would have. ``` c# string? s = ... if (s != null && s.Length == 5) ... // It flows here M(s!, s); // why not here? ``` It would sort of make `s!` mean the same as `s = s!`. There are also cases where you *wouldn't* want it to be sticky, e.g. when you are using `!` to shut up a specific unannotated API that is lacking a `?`. Here you don't use `!` in the meaning of "this is really not null", but to the effect of "I am actually fine passing a null here". That meaning shouldn't really be contagious to subsequent lines. ## Conclusion We don't know what, if anything, to do here. For now we'll leave it as is. # Array covariance Feedback: Arrays are (unsafely) covariant over reference types, and for consistency we should also make them covariant over nullability. ## Conclusion This is probably right. We'll think about this more, but let's go with covariance for now. # Null warnings Feedback: Fine to warn in most places where a null value is given a nonnullable type, but we should beware of a "sea of warnings" effect. Specifically, we shouldn't warn on array creation, as in `new string[10]`, even though it creates a whole array of undesired nulls, because it is so common in current code, and couldn't have been done "safely" before. ## Conclusion We agree. Xtreme mode, if we adopt that, may warn on array creation, though. ================================================ FILE: meetings/2017/LDM-2017-10-16.md ================================================ # C# Language Design Notes for Oct 16, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** "Being on the same floor as SPJ is a good way to shake out difficult corner cases" 1. LINQ 2. Shapes # Applying to LINQ Mixed results. Looking for perf, and ways to do more selective specialization. ## Sum Specialized to some numeric types, but not all. 500 lines of code. In those, the loop is unspecialized too. One page of code! A new design dimension: should I use interfaces or concepts: pay for abstraction or pay for specialization Generic soup: this may be a superficial design issue, or even tooling issue. For instance, the `AssociatedType`s, other languages allow them to be retrieved by dot notation. Jeremy Siek paper "Associated Types and ...". `TColl.TEnum` etc. From experience, abstracting over enumerators tends to need associated types or higher-kinded types. Shouldn't be too discouraged by being smoked by LINQOptimizer. That one optimizes big queries, but what keeps people away from LINQ is more the death by a thousand paper cuts of using LINQ all the time. Roslyn avoids things that allocate, which today means not using LINQ. THis could be the thing that would allow it to. ## Select The return type is associated. The problem is that adding new instances can change the return type from afar, upsetting the consuming code. Improves by 2/3rds when the array specialization is used. ## SelectMany The generic type inference gets very messy here. It shows that concept inference needs to be interleaved with type inference in a way that we are still only loosely grasping. This is a place where LINQ allocates a lot, whereas this allocates hardly anything. We go at .75 the time even unspecialized. Also, the specialized version is twice as fast as the unspecialized. It shows that if you open up for specializations to be plopped in, there's quite a lot to gain. ## Conclusion Some promise on optimization. Pinches of salt here and there. The approach definitely seems to have promise. More tests to do. # Shapes Concepts can tie in to the richness of expression in C# around different kinds of operations (operators, conversions, constructors...) Interesting to consider whether there's more of a specialization relationship between concepts and instances, rather than a type/instance relationship. If this went further, there's a very large laundry list. # Conclusion We *really* would like to be able to do the post-hoc implementation of concepts. This ================================================ FILE: meetings/2017/LDM-2017-10-18.md ================================================ # C# Language Design Notes for Oct 18, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda # 189 ``` c# from s in strings let (b, i) = (int.TryParse(s, out var x), x) where b select i ``` Problem is that out vars also aren't yet allowed. We don't allow declaration expressions in 1. queries 2. constructor initializers 3. field/property initializers All of these were because we didn't settle the scope question. Time to do that. Let's put these as issues. And let's put those issues as 7.3. And let's put this one as 7.3. # 100 The difficulty of this depends on the level of ambition. It has to play in to applicability of overloads this is passed to. If we allow member initializers, then we need to bind those ``` c# M(new { X = 7, Y = new { A = "Hello" } }); x = new () { Y = { e1, e2 } }; ``` If we're lucky, this is just about whether the conversion exists or not. Would there be a way that this would even influence generic type inference? ``` c# M(() => new (1)); ``` We'd need to think about this. It may be that there's a subset of the feature that's simpler. We would need that subset to not preclude going further later. Spooky action at a distance ``` c# M(Foo) M(Goo) M(new (1)) ``` Goo has a constructor that takes an int. Adding such a constructor to Foo will break the code. Now, adding a constructor is equivalent to adding a conversion in terms of the breaks it can entail. May also need more betterness rules. It could be that it's better to limit the feature so it does not participate in type inference and betterness, and can always be checked as a conversion. It might even be worth considering it only specifically to where one target type is known (so no overload applicability). For now, let's put it in 8.0. We don't believe it's going to make 7.3, but that makes us still consider it for design time. # 179 For people who care about perf, they already need 3 or 4 overloads, and this would be yet another overload people will yell at them to add. For no parameters today, you no longer need an overload to avoid allocation, because we now use `Array.Empty`. `params IEnumerable` would be even less performant than the array one, because enumeration allocates. The point of it more is that if I want to take an `IEnumerable` anyway, then it's a convenience to add params and take the arguments individually. Not important enough to prioritize time for the design soon. Let's make this X.X. # How to continue Scan 7.X for remaining 7.3 items and ignore the rest for a bit. ================================================ FILE: meetings/2017/LDM-2017-10-25.md ================================================ # C# Language Design Notes for Oct 25, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## #98 ## 34 and 35 34 should bot be concurrent safe, just like the other compound assignment. Bundle with 8.0, but could push out. Seems to align with nullable reference types # 32 Reconcile 32 with 1020 Criteria: - Loose ends - External expectation # ref as iteration variable Not currently allowed, should probably have a proposal (Andy) # 185 keep in 3 to prioritize # 45 Push out to 8.0 for realism, but still prioritize design time # 933, 1046, and uninitialized ref local These should happen together in 7.3 # 111 Punt to 8.X # 1020 946 945 keep # 882 pattern-related, goto 8.0 # 435 Keep in 7.3, see if we can settle design # 190 Relatively obvious design, with some gnarly bits (dynamic, conversion) Usability gap with tuples let's keep it. # 189 Let is more important than from. It lets you use out variables ``` c# from s in strings let t = (b: int.TryParse(out var n), n) where t.b select t.n ``` Could be ``` c# from s in strings let (b, i) = (int.TryParse(out var n), n) where b select i ``` There's a bit of design work, especially if we also want the from clause. We could allow out vars but not the deconstruction, and it would still be useful. Could save dec for later. It's actually orthogonal. Action: Carve out deconstruction, push to 8.X # 187 On the brink, but keeping for now; need to be convinced of value # 185 Keep pushing on it, got to get the train going on `Range` `x..y` does new Range(x, y) or Range.Create(x, y) Consider whether it should be a new kind of operator instead. # 104 Micro-feature: Just allow `System.Enum` as a constraint Mini-feature: allow `enum` as a constraint, translate to `System.Enum, struct` Keep this in, but it is very cuttable. # 98 It's a zero-conceptual-overhead feature. Original designers left in space between meanings for a purpose. But that's not so compelling to us anymore. Because it touches overload resolution, it might be better aligned with a .0 release. But we're not compelled by that. Let's keep it, but again, it's cuttable. ================================================ FILE: meetings/2017/LDM-2017-11-06.md ================================================ # C# Language Design Notes for Nov 6, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Roslyn 20870 Protecting the client from unintended dependencies. But also protects from servicing. Today people going through reflection *know* they're being bad. Would this give enough sense that they are doing something special. It would make consumers lazy about contacting the API owner about things they need exposed. It would be an arms race - we would want the `IgnoreIgnore...` attribute to *really* protect things. People will still have expectations about dependencies even if it was "their own fault" by using this attribute. ## Conclusion Too risky/fishy in too many ways. # Roslyn 17310 There is no good language level solution right now. This is better addressed with an analyzer, which can know specifically about SpinLock (for instance). In time, when readonly struct declarations are added, as well as maybe the ability to declare individual struct members as readonly, *then* maybe we could start warn. ## Conclusion Not at the language level # Roslyn 20450 We have sympathy. It feels like a corner that's cut. But it's quite expensive to implement, and has semantic dark corners (`List<>.First.Foo`). ## Conclusion Not now. # Roslyn 20015 When default-expressions are constant (according to the language) this is not interesting expressiveness - there's a literal you can use. When they are *not* it gets a bit more interesting - you might want to check that your custom struct is zero-initialized. But you can do that with equality. Even in recursive scenarios, you can just `var`-pattern it and check in `when` or `&&`. Additionally there is some concern about the target type being clear enough for the `default` expression. ## Conclusion No. ================================================ FILE: meetings/2017/LDM-2017-11-08.md ================================================ # C# Language Design Notes for Nov 8, 2017 ## Agenda We went over the status of the prototype for nullable reference types, to address outstanding questions and make any last minute calls before release. 1. Constructors 2. Dotted names 3. Default expressions 4. Should we track null state for nonnullable ref types? 5. Inferred types for method type inference 6. Inferred nullability in hover tips 7. Smaller things not yet done 8. Unconstrained generics 9. Other issues # Constructors Currently the prototype warns on a constructor that doesn't directly initialize all fields, even if it has a `this(...)` constructor initializer. It should exempt such constructors completely, since it will have required the called constructors to fully initialize. We may consider a more nifty analysis than that later, at least for private constructors, but it doesn't seem high priority for the prototype. It's a warning per constructor. If it's on the implicit (default) constructor it goes on the class name. That's good. We don't warn for the default constructor on structs. It's not really actionable, we think, since people can't write their own default constructor or add initializers to fields of structs. But this is worth revisiting later, probably in the context of Xtreme mode. # Dotted names Now work in the prototype, modulo a few bugs. (Not fully handling reassignment). # Default expressions Should `default(string)` be of type `string?` or should it yield a warning *in and of itself*, and be of the type `string` (the type it states). This is a question that's related to whether `string s = null` as a local declaration yields a warning. We're going to leave the current implementation, which makes `default(string)` be a `string?`. Similar with `(string)null`. It's type is `string?`. # Should we track null state for nonnullable ref types? Not now. Worth thinking about for later, as a stop gap. # Inferred types for method type inference In the current implementation, the best common type and method type inference don't pick up *inferred* nullability, but only *declared* nullability. ``` c# void M(string? s) { if (s == null) return; var a = new[] { s }; // string?[], should be string[] } ``` We can live with that as a known issue in the prototype. # Inferred nullability in hover tips Currently shows declared nullability. Relatively big work item, won't be fixed in prototype. So we need to set expectations. # Smaller things not yet done Some warnings not given. That's alright, we'll give more in the future. New constraints (`class?`, `object`) aren't added. That's not blocking. Variance: we'll deal with it when we get there. # Unconstrained generics Need to revisit to see if the weirdness we do is the right weirdness. Based on design review feedback, it is ok if what we do is not consistent with the rest of the language; the higher order bit is that it is helpful, intuitive and not obnoxious. # Other issues - No switch in the prototype; warnings are always on. We need to design the command line switch. - Annotations for TryGet etc. still need to be designed. - How to update BCL with annotations. (Automatically?) ================================================ FILE: meetings/2017/LDM-2017-11-20.md ================================================ # C# Language Design Notes for Nov 20, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda # Recursive pattern matching ## Grammar The grammar splits out to a couple of cases so that the right things are optional etc. directly in the grammar. That separation is also there in the representation of the implementation, currently. An alternative is to have a single production and call it out in prose. There are advantages to having a separated representation, in that your code can make more assumptions about what's there or not. ## Names in deconstruction pattern What is the utility? Current proposal it's only to guarantee that you get the thing you say. It does not allow reordering or leaving out elements. This pattern should by and large work like deconstruction. But it's plausible to have names here, even if we don't in deconstruction; the argument would be a symmetry with constructors, which are sometimes used with names and optional arguments. ## Should the identifier be restricted from being a discard? Since it can be left out completely? No, it's probably good to allow the discard. For refactoring etc. ## Matching via ITuple It's "too likely" that the compiler would consider that a thing *may* implement `ITuple`. We'll restrict to static types `object`, `ITuple` and any type that derives from `ITuple` and has no deconstructors. `dynamic` is treated just like `object`. We don't go looking for deconstructors dynamically. ## Syntactic ambiguity around parenthesized expression Should we even have single-element deconstruction patterns? Could require some other element to disambiguate, e.g. `(2) _`. This would raise the cost of adding single-element tuples and deconstruction in the future, at least if they have a syntax *other* than parenthesized expressions (e.g., `(x,)`). ``` c# switch(my1DPoint) case 1DPoint(0): ... case 1DPoint(var x): ... ``` Compromise position: Allow a single one only if there is a type in front. It gives the obvious symmetry with a single-element constructor, without restricting the design space for future single-element tuples or pattern grouping constructs. ## Cast ambiguity Now went away. It's a cast, or an error. ## Short discard Yes, allow `_` as a pattern in and of itself. It would not be allowed at the top level in an `is` expression. (That is allowed today, and designates the type `_`). It actually means something different than `default` in a switch, because it gets an error if there are no cases left. That seems useful. So: allow it everywhere except at the top level in an is-expression. ## Colon or is? ================================================ FILE: meetings/2017/LDM-2017-11-27.md ================================================ # C# Language Design Notes for Nov 27, 2017 ## Agenda We went over the feedback on the nullable reference types prototype, and discussed how to address the top issues that people had found using the feature on their own source code. 1. Interacting with existing, unannotated APIs 2. Accommodating alternative initialization patterns 3. Tracking nullable value types 4. Tracking dotted names 5. Special methods 6. Filtering out nulls # Interacting with existing, unannotated APIs The most pressing problem for people using the prototype, is when existing, unannotated APIs are treated as all non-nullable. While you can live with that, there ends up being too many places where you have to use `!` to silence warnings. Long term, of course the solution is for these APIs to evolve and to *get* annotations. That presents its own challenges: do people have to wait for them to update in place? Can we have a system of on-the-side annotations, either through reference assemblies or otherwise? Short term, though, it seems that we should probably distinguish "legacy" APIs, and simply not warn based on their signatures. The way to recognize them is to bake an attribute into *new* assemblies - then *old* ones are simply the ones without that attribute. There's design work to decide what exactly it means to "not warn on legacy signatures": do they represent a third type state ("between" nullable and non-nullable)? Does it travel with type inference? Etc. # Accommodating alternative initialization patterns The prototype warns when constructors do not initialize all non-nullable fields. However, this is too harsh for many usage patterns, where fields may be initialized by: - Initialization helpers called from constructors - Factory methods that call constructors - Object initializers, by convention - Set-up/tear-down methods in test frameworks - Reflection We can try to do something more fancy to track initialization of fields through at least some of these. At the end of the day, there will be initialization that we just don't recognize, so there should also be a way to opt out of these warnings. # Tracking dotted names The prototype ended up not supporting the tracking of null-state for dotted names, and that was definitely felt by several prototype users, which goes to show that we do indeed need this functionality, as we suspected. # Tracking nullable value types There's some desire to have the same tracking of null-state for nullable *value* types. Not only could we allow you to dot through to the members of the underlying type (with some finagling to avoid breaking changes), but this would also be helpful when boxing the nullable value type. # Special methods Certain scenarios came up again and again, where utility methods or specific method patterns have special behavior regarding null. We need to design a general approach to this, where certain attributes on these methods can change their nullability behavior. Examples: - `String.IsNullOrEmpty(s)`: `s` is not-null when method returns false - `TryGet(out T x)`: `x` may-be-null when method returns false (even if `T` is non-nullable) - `FirstOrDefault()`: result may-be-null (even if element type is non-nullable) There's design work needed. # Filtering out nulls In this query it would be really good to know that the result is of non-null element type: ``` c# var query = from s in nullableStrings where s != null select s; ``` For query expressions we can maybe deal with this in the language. For the method syntax, it does not seem viable: ``` c# var query = nullableStrings.Where(s => s != null); ``` How would we know that the result is filtered by the lambda provided? It's more likely that we can make a specialized `WhereNotNull` query method for this purpose. ================================================ FILE: meetings/2017/LDM-2017-11-29.md ================================================ # C# Language Design Notes for Nov 29, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Match expression Current proposal: - Infix vs prefix `switch` - `switch` vs `match` - curlies or parens for body - `case` or not - arrows? - discards or defaults? - also need to remember when clauses If it's too similar to switch statements, then it sets certain expectations. If it's too different, it's not utilizing existing intuition. Should it be a verb/command? Not many expression keywords are that (`select` is an exception, though). `case` is heavyweight, but helps visually separate the issues. ``` c# state = (state, action) switch ( (DoorState.Closed, Action.Open) => DoorState.Opened, (DoorState.Opened, Action.Close) => DoorState.Closed, (DoorState.Closed, Action.Lock) => DoorState.Locked, (DoorState.Locked, Action.Unlock) => DoorState.Closed, _ => state); state = match (state, action) { (DoorState.Closed, Action.Open) => DoorState.Opened, (DoorState.Opened, Action.Close) => DoorState.Closed, (DoorState.Closed, Action.Lock) => DoorState.Locked, (DoorState.Locked, Action.Unlock) => DoorState.Closed, _ => state }; state = switch (state, action) { case (DoorState.Closed, Action.Open): DoorState.Opened case (DoorState.Opened, Action.Close): DoorState.Closed case (DoorState.Closed, Action.Lock): DoorState.Locked case (DoorState.Locked, Action.Unlock): DoorState.Closed case _: state }; ``` The last one is subject to ambiguity-like situations between expression and statement `switch`. We should also consider nesting of match expressions. No matter what syntax we choose, we'll get requests for doing more things in expressions. We can live with that. Parens look too much like a list of *expressions*. Arrows make it look like lambda expressions. ## Decisions We agree that we will not use the keyword `default`. You can use `_`, and in the rare case where that's defined, you can use `var _`. We like curly braces for the grouping. The rest is up in the air. We'll stay with the first version for now in the prototype, other than the curly braces. # Where and when can identifier appear? # Syntax for property patterns Not urgent ``` c# ``` ================================================ FILE: meetings/2017/LDM-2017-12-04.md ================================================ # C# Language Design Notes for Dec 4, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** QOTD: "Days since working around the JIT: 0" QOTD: "What's wrong with dangerous?" QOTD: "If we disallowed `Dangerous` in method names, Dangerous Dave wouldn't compile!" # Foreach for Span and ReadOnlySpan `Span` and `ReadOnlySpan` implement the foreach pattern with `GetEnumerator()` etc., even though they don't implement the `IEnumerable` interface. (Interesting aside, the `Current` property is ref-returning, which the compiler is quite happy with.) But it's faster to iterate them like an array, by using the `Length` and the indexer. We want to allow that for spans as well. We would keep the enumerable around, which also allows write access to the elements of a `Span`. Interesting to consider opening up for a new `Length`/indexer `foreach` pattern, that's opted into e.g. with an attribute. ## Conclusion Definitely allow it as an optimization. The general case; not now, and maybe not ever. Better to limit it to compiler-known types, that are guaranteed to have equivalent semantics. # In vs value parameter ambiguity If you have overloads ``` c# void M(in DateTime d) { } void M(DateTime d) {} M(myDT); / ambiguity ``` Options in tie breaker: 1. Choose based on LValue vs RValue 2. val preferred 3. Do nothing How would you legitimately end up in this situation: - Want to "change" to `in`, but must keep value for binary compat - But now you have source break We currently do 3. We should do 2. 1 is too arbitrary. People might want to move existing calls to the `in` version, and we won't be helping them. But that's an analyzer. Problem with operators. They can't use `in` when applied, so can't move away from val behavior. Oh well. ## Conclusion Go with 2. Roll out as bug fix. # Pattern-based fixed Lots of types (especially new ones, like `Span`, `Memory` etc., but also `string`) would benefit from being `fixed`. `Span` currently has a `DangerousGetPinnableReference` method for this. We can make this a pattern. It's a thin layer of syntactic sugar. ## Conclusion Let's do it. Extension methods are allowed (as always when we add new patterns). Let's aim for 7.3, but not super high pri. # this ref vs ref this We allow `ref this` but not the other way around. That's wrong! We can't disallow `ref this`, but we should allow and prefer `this ref`. ## Conclusion Fix it. Go out as a bug fix. # Reachability, definite assignment and local functions There's a bit of a mess around when captured variables in local functions are definitely assigned. This is different from lambdas, because local functions take all calls into account. The forthcoming ECMA spec changes the wording of these rules, so that they are not defined based on reachability. Proposal: - Make beginning of local functions always reachable. (And lambdas, which is implemented but not spec'ed). - A captured local in a local function is considered definitely assigned if it's definitely assigned before every call of it. - That last one can be vacuously true. ## Conclusion We like this. It could give errors in a few cases that don't today (including the bug report). Is this an acceptable breaking change? In order to get an error, you'd have to have: - an unassigned local variable - an uncalled local function that uses it Both are unlikely, undesirable and easily fixed. We'll check with compat council. # Equality operators on tuples Needs to be defined by the language, in order to deal with long tuples, and to recursively apply `==` rather than `Equals`. If somebody wrote their own `ValueTuple` *with* user defined equality, then this would break the use of that. That's not a supported scenario; you could get into the same kind of trouble with `Nullable`. For tuple literals, there is a tension between left-to-right evaluation and performance. It seems that `t1 == t2` should mean the same as `t1 == (t2.Item1, t2.Item2)`. The upshot of the feature really is to deconstruct tuples (if they aren't already, by being tuple literals), then do point-wise comparison. We will evaluate all operands left to right, recursively through tuple literals, and save to temps. We don't evaluate target-typed things, and we don't yet convert them. Then we do point-wise `==` (or `!=`, in element position order, separated by `&&` (Or for `!=`, `||`). This may involve conversions. We are ok with those happening "late", and conditionally (won't happen if previous comparison failed), because conversions aren't usually expected to have side effects. For dynamic we think we can allow it by just converting the result of, e.g. `d == e` (where `d` is dynamic) to `bool`. Conversion from tuples matter, but not to tuples. If I have a `Foo` and a tuple, and `Foo` converts to tuple, then that won't help you. ## Conclusion We think we have it, but will revisit during implementation. ================================================ FILE: meetings/2017/LDM-2017-12-06.md ================================================ # C# Language Design Notes for Dec 6, 2017 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda # Range Several arguments against a generic `Range`, such as conversions between them. Step() should maybe be step with range # Operator `..` `op_Range` in IL, marked as special method. Should it be target typed? Example: `FloatRange`. If we don't supply it, no-one ever can, unless we make it target typed. Conversion between ranges does not seem like a good idea: Even though it's technically possible, it might not make conceptual sense. Target typing seems better. There's an issue with compat and ambiguity, where operator declarations on the operands and the result may clash. The proposal is to let the target type win. But that may not be necessary. The operator declared in the operands corresponds to the "natural type", and if there's an exact match to the target type, it would win. For `IntRange` we get: ``` c# IntRange r = 1 .. 10; ``` Here `..` is defined on the operand types, and when that one is applied, the natural type is `IntRange`, and that wins in overload resolution. For `LongRange`: ``` c# LongRange lr = 1 .. 10; ``` That works just dandy with target typing, but if there's also a conversion from `IntRange` to `LongRange` then there seem to be two competing conversions. We probably don't want that, and need to think through if that's the case, and if so how to amend it. ## Alternative 1 Look for an instance method (which can be an extension method), and have declared conversions and no target typing. That would lead to N x N extension methods and N x N conversion methods having to be declared to get the same "convenience". But for the situation of having `x .. y` and *not* a target type, it would be simpler, in that it wouldn't need the `..` operator to be retrofitted to existing types. ## Alternative 2 Have a generic `Range`. A range expression produces a range of the best common type of the operands. It can be target typed to another `Range`, as long as there's a conversion from the end points to `S`. Comparison is done by expanding to use of `<=`, which better be defined. # Wrap around Let's make sure the library side doesn't allow foreach to throw on the boundary (`int.MaxValue`) or wrap around infinitely. # Inclusive/exclusive For floating point/non-enumerable, there's real expressiveness at stake. That's probably not the main scenario, though. Problem with exclusive: I create a range from Sunday to Saturday, I need a name for the thing outside the range. Also, what if I need `int.MaxValue` as the top element? Problem with inclusive: Going to array.Length. Slicing and windowing. Evidence in the Mono code base for instance, shows both prevalent, with a slight overweight of inclusive. # Range pattern ================================================ FILE: meetings/2017/README.md ================================================ # C# Language Design Notes for 2017 Overview of meetings and agendas for 2017 ## Jan 10, 2017 [C# Language Design Notes for Jan 10, 2017](LDM-2017-01-10.md) 1. Discriminated unions via "closed" types ## Jan 11, 2017 [C# Language Design Notes for Jan 11, 2017](LDM-2017-01-11.md) 1. Language aspects of [compiler intrinsics](https://github.com/dotnet/roslyn/issues/11475) ## Jan 17, 2017 [C# Language Design Notes for Jan 17, 2017](LDM-2017-01-17.md) 1. Constant pattern semantics: which equality exactly? 2. Extension methods on tuples: should tuple conversions apply? ## Jan 18, 2017 [C# Language Design Notes for Jan 18, 2017](LDM-2017-01-18.md) 1. Async streams (visit from Oren Novotny) ## Feb 21, 2017 [C# Language Design Notes for Feb 21, 2017](LDM-2017-02-21.md) We triaged some of the [championed features](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22), to give them a tentative milestone and ensure they had a champion. As part of this we revisited potential 7.1 features and pushed several out. 1. Implicit interface implementation in Visual Basic *(VB 16)* 2. Delegate and enum constraints *(C# X.X)* 3. Generic attributes *(C# X.0 if even practical)* 4. Replace/original *(C# X.0 if and when relevant)* 5. Bestest betterness *(C# 7.X)* 6. Null-coalescing assignments and awaits *(C# 7.X)* 7. Deconstruction in from and let clauses *(C# 7.X)* 8. Target-typed `new` expressions *(C# 7.X)* 9. Mixing fresh and existing variables in deconstruction *(C# 7.1)* 10. Implementing `==` and `!=` on tuple types *(C# 7.X)* 11. Declarations in embedded statements *(No)* 12. Field targeted attributes on auto-properties *(C# 7.1)* ## Feb 22, 2017 [C# Language Design Notes for Feb 22, 2017](LDM-2017-02-22.md) We went over the proposal for `ref readonly`: [Champion "Readonly ref"](https://github.com/dotnet/csharplang/issues/38). ## Feb 28, 2017 [C# Language Design Notes for Feb 28, 2017](LDM-2017-02-28.md) 1. Conditional operator over refs (*Yes, but no decision on syntax*) 2. Async Main (*Allow Task-returning Main methods*) ## Mar 1, 2017 [C# Language Design Notes for Mar 1, 2017](LDM-2017-03-01.md) 1. Shapes and extensions (*exploration*) 2. Conditional refs (*original design adopted*) ## Mar 7, 2017 [C# Language Design Notes for Mar 7, 2017](LDM-2017-03-07.md) We continued to flesh out the designs for features currently considered for C# 7.1. 1. Default expressions (*design adopted*) 2. Field target on auto-properties (*yes*) 3. private protected (*yes, if things work as expected*) ## Mar 8, 2017 [C# Language Design Notes for Mar 8, 2017](LDM-2017-03-08.md) We looked at default interface member implementations. 1. Xamarin interop scenario 2. Proposal 3. Inheritance from interface to class 4. Overriding and base calls 5. The diamond problem 6. Binary compatibility 7. Other semantic challenges ## Mar 15, 2017 [C# Language Design Notes for Mar 8, 2017](LDM-2017-03-15.md) Triage of championed features 1. JSON literals 2. Fixing of captured locals 3. Allow shadowing of parameters 4. Weak delegates 5. Protocols/duck typing/concepts/type classes 6. Zero and one element tuples 7. Deconstruction in lambda parameters 8. Private protected ## Mar 21, 2017 [C# Language Design Notes for Mar 21, 2017](LDM-2017-03-21.md) Discussion of default interface member implementations, based on [this guided tour](https://github.com/dotnet/csharplang/issues/288). 1. Concerns raised on GitHub and elsewhere 2. Inheritance? 3. Breaking name lookup on `this` 4. Events 5. Modifiers 6. Methods 7. Properties 8. Overrides 9. Reabstraction 10. Most specific override 11. Static non-virtual members 12. Accessibility levels 13. Existing programs ## Mar 28, 2017 [C# Language Design Notes for Mar 28, 2017](LDM-2017-03-28.md) Design some remaining 7.1 features 1. Fix pattern matching restriction with generics 2. Better best common type ## Mar 29, 2017 [C# Language Design Notes for Mar 29, 2017](LDM-2017-03-29.md) 1. Nullable scenarios 2. `Span` safety ## Apr 5, 2017 [C# Language Design Notes for Apr 5, 2017](LDM-2017-04-05.md) 1. Non-virtual members in interfaces 2. Inferred tuple element names 3. Tuple element names in generic constraints ## Apr 11, 2017 [C# Language Design Notes for Apr 11, 2017](LDM-2017-04-11.md) 1. Runtime behavior of ambiguous default implementation ## Apr 18, 2017 [C# Language Design Notes for Apr 18, 2017](LDM-2017-04-18.md) 1. Default implementations for event accessors in interfaces 2. Reabstraction in a class of default-implemented member 3. `sealed override` with default implementations 4. Use of `sealed` keyword for non-virtual interface members 5. Implementing inaccessible interface members 6. Implicitly implementing non-public interface members 7. Not quite implementing a member 8. asynchronous `Main` ## Apr 19, 2017 [C# Language Design Notes for Apr 19, 2017](LDM-2017-04-19.md) 1. Improved best common type 2. Diamonds with classes 3. Structs and default implementations 4. Base invocation ## May 16, 2017 [C# Language Design Notes for May 16, 2017](LDM-2017-05-16.md) 1. Triage C# 7.1 features that didn't make it 2. Look at C# 7.2 features 3. GitHub procedure around new design notes and proposals 4. Triage of championed features ## May 17, 2017 [C# Language Design Notes for May 17, 2017](LDM-2017-05-17.md) More questions about default interface member implementations 1. Conflicting override of default implementations 2. Can the Main entry point method be in an interface? 3. Static constructors in interfaces? 4. Virtual properties with private accessors 5. Does an override introduce a member? 6. Parameter names ## May 26, 2017 [C# Language Design Notes for May 26, 2017](LDM-2017-05-26.md) 1. Native ints ## May 31, 2017 [C# Language Design Notes for May 31, 2017](LDM-2017-05-31.md) 1. Default interface members: overriding or implementing? 2. Downlevel poisoning of ref readonly in signatures 3. Extension methods with ref this and generics 4. Default in operators ## Jun 13, 2017 [C# Language Design Notes for Jun 13, 2017](LDM-2017-06-13.md) 1. Native-size ints 2. Native-size floats ## Jun 14, 2017 [C# Language Design Notes for Jun 14, 2017](LDM-2017-06-14.md) Several issues related to default implementations of interface members 1. Virtual properties with private accessors 2. Requiring interfaces to have a most specific implementation of all members 3. Member declaration syntax revisited 4. Base calls ## Jun 27, 2017 [C# Language Design Notes for Jun 27, 2017](LDM-2017-06-27.md) 1. User-defined operators in interfaces 2. return/break/continue as expressions ## Jun 28, 2017 [C# Language Design Notes for Jun 28, 2017](LDM-2017-06-28.md) 1. Tuple name round-tripping between C# 6.0 and C# 7.0 2. Deconstruction without `ValueTuple` 3. Non-trailing named arguments ## Jul 5, 2017 [C# Language Design Notes for Jul 5, 2017](LDM-2017-07-05.md) Triage of features in the C# 7.2 milestone. They don't all fit: which should be dropped, which should be kept, and which should be pushed out? 1. Static delegates *(8.0)* 2. Native int and IntPtr operators *(7.X)* 3. Field target *(anytime)* 4. Utf8 strings *(8.0)* 5. Slicing *(7.X)* 6. Blittable *(7.2)* 7. Ref structs *(7.2)* 8. Ref readonly *(7.2)* 9. Conditional ref *(7.2)* 10. Ref extensions on structs *(7.2)* 11. Readonly locals and params *(X.X)* 12. ref structs in tuples *(don't)* 13. Overload resolution tie breakers with long tuples *(use underlying generics)* ## Jul 26, 2017 [C# Language Design Notes for Jul 24 and 26, 2017](LDM-2017-07-26.md) We started putting a series of stakes in the ground for nullable reference types, based on the evolving strawman proposal [here](https://github.com/dotnet/csharplang/issues/790). We're doing our first implementation of the feature based on this, and can then refine as we learn things from usage. 1. Goals 2. Nullable reference types 3. Rarely-null members ## Aug 7, 2017 [C# Language Design Notes for Aug 7, 2017](LDM-2017-08-07.md) We continued refining the nullable reference types feature set with the aim of producing a public prototype for experimentation and learning. 1. Warnings 2. Local variables revisited 3. Opt-in mechanisms ## Aug 9, 2017 [C# Language Design Notes for Aug 9, 2017](LDM-2017-08-09.md) We discussed how nullable reference types should work in a number of different situations. 1. Default expressions 2. Array creation 3. Struct fields 4. Unconstrained type parameters ## Aug 14, 2017 [C# Language Design Notes for Aug 14, 2017](LDM-2017-08-14.md) We looked at the interaction between generics and nullable reference types 1. Unconstrained type parameters 2. Nullable constraints 3. Conversions between constructed types ## Aug 16, 2017 [C# Language Design Notes for Aug 16, 2017](LDM-2017-08-16.md) 1. The null-forgiving operator ## Aug 23, 2017 [C# Language Design Notes for Aug 23, 2017](LDM-2017-08-23.md) We discussed various aspects of nullable reference types 1. How does flow analysis silence the warning 2. Problems with dotted names 3. Type inference 4. Structs with fields of non-nullable type ## Oct 4, 2017 [C# Language Design Review, Oct 4, 2017](LDM-2017-10-04.md) We looked at nullable reference types with the reviewers, Anders Hejlsberg and Kevin Pilch. 1. Overall philosophy 2. Switches 3. Libraries 4. Dotted names 5. Type narrowing 6. The dammit operator 7. Array covariance 8. Null warnings 9. Special methods 10. Conclusion ## Oct 11, 2017 [C# Language Design Notes for Oct 11, 2017](LDM-2017-10-11.md) We looked at the Oct 4 design review feedback for nullable reference types, and considered how to react to it. 1. Philosophy 2. Switches 3. Dotted names 4. Type narrowing 5. Dammit operator type narrowing 6. Dammit operator stickiness 7. Array covariance 8. Null warnings ## Nov 8, 2017 [C# Language Design Notes for Nov 8, 2017](LDM-2017-11-08.md) We went over the status of the prototype for nullable reference types, to address outstanding questions and make any last minute calls before release. 1. Constructors 2. Dotted names 3. Default expressions 4. Should we track null state for nonnullable ref types? 5. Inferred types for method type inference 6. Inferred nullability in hover tips 7. Smaller things not yet done 8. Unconstrained generics 9. Other issues ## Nov 27, 2017 [C# Language Design Notes for Nov 27, 2017](LDM-2017-11-27.md) We went over the feedback on the nullable reference types prototype, and discussed how to address the top issues that people had found using the feature on their own source code. 1. Interacting with existing, unannotated APIs 2. Accommodating alternative initialization patterns 3. Tracking nullable value types 4. Tracking dotted names 5. Special methods 6. Filtering out nulls ================================================ FILE: meetings/2018/LDM-2018-01-03.md ================================================ # C# Language Design Notes for Jan 3, 2018 Quote of the Day: "What about `CallerPoliticalAffiliationAttribute`?" ## Agenda 1. Scoping of expression variables in constructor initializer 2. Scoping of expression variables in field initializer 3. Scoping of expression variables in query clauses 4. Caller argument expression attribute 5. Other caller attributes 6. New constraints # Scoping of expression variables in constructor initializer Should an expression variable `x` introduced in a `this` or `base` initializer be available in the constructor body? ``` c# C(int i) : base(out var x) { ... x ... } ``` It feels natural and occasionally useful that `x` would be in scope in the body. Any reason not to allow it? Not at the language level. There's a design question as to how it's represented in `IOperation`, but that's not a language question. ## Conclusion Let `x` be in scope in the whole constructor, including the body. # Scoping of expression variables in field initializer Where should an expression variable `x` introduced in a field initializer be in scope? ``` c# class C { int i = M(out int x) .N(x), // here? y = M(x); // here? int j = x; // here? int X => x; // here? } ``` A number of options: 1. Only in the same initializer expression 2. In the whole field declaration, including initializers for subsequent fields 3. In all subsequent initializers in the class declaration 4. In the whole class body, including function members (so it would effectively become a private field if necessary) There's something tempting about 4, especially considering the relatively loose scope rules we chose for expression variables inside statement lists. ``` c# void M() { int i = N(out int x); int M() => x; // in scope here } class C { int i = N(out int x); int M() => x; // why not here? } ``` Also, if we ever do primary constructors, and want to allow statements interspersed with member declarations, we'll probably wish we did 4. However, there are also difficult questions with 4: can it be used before it is declared? Is it visible across partial classes? These are all questions that could be answered, but we are also a little uneasy making it so easy to "accidentally" declare an extra private field, in case some function members close over it. That latter concern could be alleviated by reducing to option 3, but now the scope is weird, and most of the usefulness is gone. May as well reduce to 2 or 1 then. ## Conclusion We'll go with option 1. If you want to use expression variables to carry information between initializers, you'll instead have to do your initialization in the constructor body. The script dialect will have to stay consistent, so declaration variables must be lifted to fields, and be visible in subsequent statements. # Scoping of expression variables in query clauses What should be the scope of an expression variable `x` that occurs in a query clause? It seems desirable at first that the variables would carry over from clause to clause: ``` c# from x in e where M(x, out var y) select y ``` However, to do this we would need to perform much more semantic analysis of the query than we do today, before we lower. It feels out of touch with the syntactic flavor of queries today. Instead, a more reasonable approach may be to make it easier to carry multiple range variables forward from a `let` (and maybe `from`) clause, using deconstruction syntax: ``` c# from x in e let (m, z) = (M(x, out var y), y) select z; ``` ## Conclusion We should allow expression variables in queries, but keep them scoped to the individual query clauses. In other words, they aren't range variables. They are normal variables scoped by the lambdas that we translate into. # Caller argument expression attribute [csharplang/issues/287](https://github.com/dotnet/csharplang/issues/287) 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: 1. Take a binary breaking change (that's probably what XUnit would do) 2. Use different API names (won't be picked up automatically by old code) 3. Change in ref assemblies only (might for `Debug.Assert`) In summary, folks have a decent slate of options. ## Conclusion Fine to accept this one. # Other caller attributes We are not comfortable with those just yet. We'd need to work on the details. For now we are suspicious of having a record of a lot of different information, and of the number of proposed ones in [csharplang/issues/87](https://github.com/dotnet/csharplang/issues/87). # Constraints [csharplang/issues/103](https://github.com/dotnet/csharplang/issues/103) and [csharplang/issues/104](https://github.com/dotnet/csharplang/issues/104) propose to allow the `Enum` and `Delegate` types as constraints. No special meaning, just stop disallowing them. That's a starting point. Adding keywords `enum` and `delegate` could be discussed later. [csharplang/issues/187](https://github.com/dotnet/csharplang/issues/187) proposes a contextual keyword `unmanaged`; we would represent it by putting a `ModReq` in the constraint of the parameter. It implies the `struct` constraint. Reference assemblies are a problem: some tools produce reference assemblies that remove private fields from structs, which already leads to semantic problems in the compiler (definite assignment, allowing pointers to them), and now would lead to more. That's fundamentally not a language problem though. ================================================ FILE: meetings/2018/LDM-2018-01-10.md ================================================ # C# Language Design Notes for Jan 10, 2018 ## Agenda 1. Ranges and endpoint types # Ranges and endpoint types Wouldn't it be nice to have a range type that work on any comparable? Possibly, but we're not eager to solve this right now. We need to believe that our future selves can extend the language with some form of target typing, for instance. There's a burden on them (us) to be able to do that without a compat break (silent semantic change), e.g.: There's a type in the future that has conversions so that it would work one way in 7.3 and a different way in the future. We probably want `..(int, int)` and `..(long, long)`. We want to treat those as usual operators, so they have to allow user defined conversions of the end points. We could consider blocking off normal conversions of the operands, but that's extremely distasteful. So first tentative decision: There's `Range` (for ints) and `LongRange`. The `..` operators are built-in. There's an implicit conversion one way, and an explicit the other, also both built-in. ================================================ FILE: meetings/2018/LDM-2018-01-18.md ================================================ # C# Language Design Notes for Jan 18, 2018 ## Agenda We discussed the range operator in C# and the underlying types for it. 1. Scope of the feature 2. Range types 3. Type name 4. Open-ended ranges 5. Empty ranges 6. Enumerability 7. Language questions # Scope of the feature The scenario we are eager to address right now relates to indexing and slicing, where the elements of the range are contiguous integral indices into some data structure. We want to design the language feature and API so that we address this scenario in the best possible way, without cutting off future support for range syntax in other scenarios, such as math, inclusion tests, etc., which require more generality. # Range types We've talked about having a couple of types, say `Range` and `LongRange` representing general-purpose ranges over ints and longs, respectively. However a `Range` type that ranges over all ints might have a length that is greater than what an `int` can contain! We could represent the `Length` property with a `uint`, but then it doesn't interoperate well with all the `int`-based types and logic in the language and framework. And when we think about the core scenario, this pain would all be for naught: for indexing purposes, indices are always non-negative `int`s, and ranges of them would therefore never be longer than `int.MaxValue`. It therefore seems that we should build a `Range` type specifically for indexing purposes, and have language support *only* for that, but in a way that we can generalize later. Specifically we can support conversion of `x..y` expressions only to `Range` for now, but open up for user-defined `..` operators later. ## Conclusion Design just an indexing-oriented `Range` type for now. # Type name Should the indexing-specific range type be called `IndexRange` or just `Range`? On the one hand it is a bit dangerous to use the good name for a special-purpose range type. On the other it is going to be *very* common, probably much more so than any other future range type. It is possible that we will someday have a `Range` type that represents a more abstract mathematical notion of ranges. It will then be a bit odd for `Range` and `Range` to be only loosely related to each other. ## Conclusion We can live with that risk. We think it is ok for this type to be called `Range`. # Open-ended ranges Should we allow open-ended range values, as represented by missing endpoints in the language syntax: `x..`, `..y` and `..`? It seems a significant loss of expressiveness if we don't. Scenarios for open-at-one-end are common, and scenarios for open-at-both-ends include multi-dimensional slicing, where we want a concise way to keep "all of" a given dimension: `tensor[.., ..1, ..]`. Open-ended ranges are semantically different from just having `0` and `int.MaxValue` at the ends, because indexing/slicing APIs will require range arguments to be "within range" of the target data structure. Where `m..int.MaxValue` would therefore often yield an exception, an open-ended `n..` would just mean "from `n` to the end, wherever that is", and be legal as long as `n` is within range. Also, we can't just represent open-ended ranges as closed ones with the target data structure's min and max index substituted in, because range values can exist independently of a given target data structure. Internally, the `Range` type can e.g. use negative ints to represent open ends, so as not to waste space. This representation would not be visible from the outside though. Can we use nullable int in the constructor, to represent endpoints that may not be there? Then `null` means open in that end. We could have an `(int, int)` constructor overload for efficiency. Proposal for this to be written offline. ## Conclusion Yes, support open-ended ranges. # Empty ranges Can the `Range` type represent empty ranges? In that case, does the start point matter or not, or are all empty ranges the same? They might behave differently when you slice, depending of whether the start point is in range in the target data structure. (If not, they will throw). Can you index an empty array with an empty range? Yes. As long as it's in range; otherwise it throws. This is consistent with slices, spans and strings. ## Conclusion It's important to support empty ranges. The start point should matter even for empty ranges, in that indexing/slicing would throw if the start point of an empty range wasn't in range of the target data structure. # Enumerability `Range` should implement `IEnumerable`, and needs to support the enumerable pattern of having a struct enumerator type. This is extra bulk in the type, but that's just how we do things. Enumeration on open ranges should probably throw. # Language questions Given this there are a couple of questions on the language support, which we gathered here, but did not yet answer. ## Should range expressions have `Range` as a natural type, or should they just convert to it? If they have `Range` as a natural type, then you can infer `Range` as the type of a range expression: `var r = 3..5;`. And support for `foreach` naturally falls out: `foreach (var x in 3..5) { ... }`. However, this locks in `Range` as the "primary" range type for the language forever. It would mean that applicable overloads taking the `Range` type would always win over other overloads taking range expressions (once those are allowed). Also, the `foreach` support based on this natural type wouldn't be very expressive: It wouldn't allow starting at negative numbers, or going backwards. ## Other notations We're fairly certain we want an inclusive `start..end` syntax. Presumably, open-ended ranges are expressed by leaving out one or the other endpoint. But should there be other notations? In particular, it seems that a syntax for giving an index and a *count* would handle a common scenario; possibly *more* common. All current APIs are like that, and many (most?) use cases would take a given number of elements at a time. It's not obvious what such a syntax should be, though. `start:count` and `start::count` both would have some ambiguity with existing syntax. Also, at some point we might want to consider ranges that are exclusive of their endpoints. That's not a big deal with `Range` as the only target range type, where exclusive is just a question of `+1` or `-1`. But if at some point we start considering ranges of floats etc., inclusion or exclusion would need first class representation in the range types, and in the language's notation. ================================================ FILE: meetings/2018/LDM-2018-01-22.md ================================================ # C# Language Design Notes for Jan 22, 2018 ## Agenda We discussed the range operator in C# and the underlying types for it. 1. Inclusive or exclusive? 2. Natural type of range expressions 3. Start/length notation # Inclusive or exclusive? ``` c# var numbers = ints[from..to]; ``` Should this mean inclusive or exclusive of the element at `to`? Not many languages have *only* inclusive ranges. Apart from F#, they tend to either have exclusive ranges or have notations for both (at the upper end; the lower is always inclusive). Python uses exclusive ranges. A lot of people seem to be confused about it; it doesn't immediately gel with their intuition. The obvious advantage of course is that the collection's `Length` is directly allowed at the end: ``` var s = a[0..a.Length]; ``` In foreach loops over ranges, if you use constant end points, again intuition seems to suggest inclusiveness: ``` foreach (var x in 1..100) { ... } foreach (var x in 0..100) { ... } ``` You don't often use constants in practice, though, except as a zero lower bound. In fact, in F# it is often a bit of a pain to write a `for` loop for array iteration, for instance, because you need to subtract one at the end to avoid overrunning. Let's look at these four scenarios: 1. Create a range, `incl`, that goes from `start` *through* `end` 2. Create a range, `excl`, that goes from `start` *up to* `end` (but not including) 3. Create a range, `rel`, that goes from `start` and has `length` elements 4. Create an empty range, `emp`, that starts at 0. This is what they will look like, given exclusive semantics, inclusive semantics, and a notation for `start:length` ranges: ``` c# // Exclusive x..y Range incl = start..end+1; Range excl = start..end; Range rel = start..start+length; Range emp = 0..0; // Inclusive x..y Range incl = start..end; Range excl = start..end-1; Range rel = start..start+length-1; Range emp = 0..-1; // Relative x:l Range incl = start:end-start+1; Range excl = start:end-start; Range rel = start:length; Range emp = 0:0; ``` The one that seems to have the least amount of computational gymnastics across the scenarios is the exclusive option. Also, the inclusive notation for an empty range is severely unappetizing! It is of course entirely possible that we have more than one syntax. One idea is that when we start talking about scenarios outside of indexing, we simply have a different syntax; maybe one that is built in to language constructs for containment and iteration: ``` c# bool b = x is in 3 to 5; foreach (var x in 0 to 100) { ... } ``` ## Conclusion Let us go with `..` means exclusive. Since we've chosen to focus on the indexing/slicing scenario, this seems the right thing to do: * It allows `a.Length` as an endpoint without adding/subtracting 1. * It lets the end of one range be the beginning of the next without overlap * It avoids ugly empty ranges of the form `x..x-1` # Natural type of range expressions If we allow `Range` to be the natural type of range expressions, the following will work: ``` c# var r = 4..6; // infer Range ``` On the other hand, if we do this, `Range` will be forever tied to range expressions as the preferential type. ## Conclusion We're good with that. # Start/length notation We may want an additional notation for specifying start and length. It can technically be added later, but if we want to do it we should try to do it now. ================================================ FILE: meetings/2018/LDM-2018-01-24.md ================================================ # C# Language Design Notes for Jan 24, 2018 ## Agenda 1. Ref reassignment 2. New constraints 3. Target typed stackalloc initializers 4. Deconstruct as ref extension method # Ref reassignment Proposal [here](https://github.com/agocke/roslyn/blob/4de95445af874269e74f9b022d83c89d85ec9669/docs/features/ref-reassignment.md). C# 7.0 added ref locals, but did not allow them to be reassigned with other refs. In C# 7.3 we are looking to allow that, and have a handle on the rules that need to be in place to make it safe. ## Ref assignment expressions The proposal adds a new ref-assignment expression of the form `r = ref v`. This makes `r` point to the storage location that `v` points to. The result of the expression is the variable designating that storage location. The variable can e.g. be evaluated or assigned to: ``` c# while ((l = ref l.Next) != null) ... // linked list walk ``` The linked list walk example shows why it is beneficial to have it be an expression form just like regular assignment, rather than just a statement form. Of course, just like with regular assignment, using it as an expression is easily overused; we think there is enough cultural awareness around this. Into the future we need to maintain a principle that expressions never start with `ref`. Therefore, `ref` in front of an expression is always part of an enclosing construct. This principle will help people (a little) when reasoning about these expressions. ## Lifetimes The compiler tracks lifetimes of variables, in order to make sure that a variable is not referenced by something that will outlast it. In C# 7.0 a ref local is simply created with the lifetime of the variable that it is initialized with. In a ref assignment expression, the compiler maintains lifetime safety by requiring that the lifetime of the right-hand side is at least as long as the lifetime of the left-hand side. ## Uninitialized ref locals With ref reassignment it now becomes meaningful to allow ref (and ref readonly) locals to be left uninitialized at declaration. In that case what should be their lifetime? We can discuss that later. ## Ref locals in looping constructs Iteration variables declared in `foreach` and `for` loops can now be `ref`. In the case of `for` loops this only makes sense because ref reassignment is allowed. Foreach iteration variables can never be reassigned in the body, and that is also the case for ref iteration variables: they cannot be ref reassigned. # New constraints Let's finalize the new forms of constraints. ``` c# void M() where T1: unmanaged where T2: Enum where T3: Delegate { } ``` ## Unmanaged It should be called `unmanaged`, not some other word like `blittable`. F#, the runtime and the C# spec all use the term "unmanaged" for types that you can take a pointer to. It can't be a keyword (that would be breaking) and not even a contextual keyword (in the sense that there's a syntactic context that determines it). Instead it needs to be semantically recognized, like `var`. Just like `var`, it can be escaped to make clear that you are using it as an identifier. If a type of that name is in scope, there's no way to get at the constraint meaning of it. So just like `var`, a devious person can declare an `unmanaged` type to prevent people from using the unmanaged constraint. ## Delegate It is useful to allow `System.Delegate` as a constraint, as it has several methods on it. It doesn't guarantee that the type argument would be a delegate type; it could be `Delegate` itself, or `MulticastDelegate`, etc. Let's just unblock `Delegate` and `MulticastDelegate` from being a constraint, and give it no special meaning. This is useful and *reduces* complexity in the language. ## Enum Same for enum. We could imagine a special `enum` constraint that means `Enum + struct`, but instead let's just stop disallowing `System.Enum`. People should be allowed to manually write ``` c# where T : struct, Enum ``` We a special rule to allow `struct` and `Enum` together, since otherwise only interfaces are allowed to combine with the `struct` constraint. ## Other non-sealed reference types? There are a few other non-sealed reference types that are prevented from being used as constraints, e.g. `Array` and `ValueType`. While it is tempting to unblock them all, now that we're at it, let's not rock that boat until we have useful scenarios for it. # Target typed stackalloc initializers ``` c# var x = new int[] { 1, 2, 3 }; // allowed today var z = stackalloc int[5]; // z is int* for back compat Span zs = stackalloc int[5]; // target typed var y = stackalloc int[] { 1, 2, 3 }; // should be allowed? y is int* Span ys = stackalloc int[] { 1, 2, 3 }; // should be allowed? y is Span ``` The `int*` is for back compat. In contexts other than this the natural type of `stackalloc` is `Span`. That's a bit inconsistent, and there are probably other ways to skin the cat, but they would probably add more syntax and not reduce the inconsistency - just push it around. So we're good with this. Should we allow the element type to be inferred from the initializer, just as we do with array initializers? Yes. # Deconstruct as ref extension method ``` c# public static void Deconstruct(this int i, out int x, out int y); // 1 public static void Deconstruct(this in int i, out int x, out int y); // 2 public static void Deconstruct(this ref int i, out int x, out int y); // 3 ``` Currently 1 and 2 are eligible as deconstructors, whereas 3 is not. you could argue that it is an arbitrary and strangely specific limitation. On the other hand, 3 is probably never desirable, as a deconstructor should not wish to mutate its receiver! Fixing this does not seem to add any value. Let's keep it the way it is. ================================================ FILE: meetings/2018/LDM-2018-01-31.md ================================================ # C# Language Design for Jan 31, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Pattern-based fixed If a type has a magic method of a certain name, which returns `ref` or `ref readonly`, you can `fixed` the type. If you try to pin null or an empty array, you get a null pointer. It never throws. For these new types, if it's a reference type we'll check first, and if it's null we'll return a null pointer. If the method wants to signal that there is nothing (e.g. an empty `ImmutableArray` struct), the proposal is to return a null ref. Those don't currently officially exist in the language, but you can manufacture one with unsafe code. ## Extension methods There are probably scenarios, such as adding the capability to an existing type based on an existing ref-returning member, or adding to certain constructions of a generic type. But even because of consistency with how we've been doing pattern-based things for ages, we should allow it. For string we want an instance method to win over the current way, but the current way to win over extension methods. # Ranges Terminology: say "bounded/unbounded" instead of "open/closed". "Inclusive/exclusive" for whether the endpoint is included. ## Negative indices Proposal to have negative indices: ```c# a[-5..-1] // Starts at length-5, excludes the last element ``` Proposal for start/count syntax: ``` c# a[5..+10] // Starts at 5, goes to 15 (exclusive) a[-10..+3] // Starts at length-10, goes to length-7 (exclusive) ``` Is it important to support collections with nonzero (positive) start index? Should the `Bind` method only take a length, not an index, for instance? Yes. There might be the odd collection out there that's indexed, and doesn't start with 0. That's fine, but the feature is not for that. Those types wouldn't have methods that take ranges. ## start/length notation `0..+-1` `-5..+3` // `-5..-2` `-5..+5` // `-5..` `-5..+7` // throw ## lexing/syntax We've been liking `:` for this, but it is highly ambiguous with many aspects of the language: ternary operator, named arguments, format specifiers in interpolated strings. ``` c# a[3..+7] a[3.+7] a[3.:7] // Not ambiguous object o = b ? 3..: 7.: 9 ; a[3..:7] // object o = b ? 3..: 7..: 9 ; find the range! a[3:.7] // NO a[3::7] // NO? a[3:7] // NO a[x:y] // NO ``` You don't need elision on either end for this. You only use it if you want to give a length. And if you want to start from the beginning you may just use `..x`: length and end are the same. ``` c# 3..-x M(.:..) ``` Colons only: ambiguous, and looks symmetric. `::` not as bad, only ambiguous with extern alias; realistic to semantically disambiguate. Plus: Looks like you are adding something, but clashes with unary + `.:` is the plan of record. ================================================ FILE: meetings/2018/LDM-2018-02-05.md ================================================ # C# Language Design Notes for Feb 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** Index could be a type Then it's about syntax of how to express an Index that's either n from front or n from back. How important is "from end"? It's definitely convenient, but its expressiveness is probably not super important. Lots of problems with using "-", with overload resolution etc. Let's use "^" as a strawman. That takes away some of the ambiguity. There's still a question as to how an overloadable "^" would work in the future. It would be a unary operator that returns something different than it takes. How would it be found, if it lives on the result type? There's target typing there, but it may not have a target type, or that may in turn come from overloaded operators. It would have to be handled similarly to conversion in some ways. A crazy idea: Use the existing `~` operator! It creates negative numbers, yes, but don't think of them as such. Think of them as integers from end. `~0` is `-1`, `~1` is `-2` etc. This is intriguing, because we'd need *no* extra language support, and just have a convention. Quite weird though! ================================================ FILE: meetings/2018/LDM-2018-02-07.md ================================================ # C# Language Design Notes for Feb 7, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Tuple equality We want to make it so that the `==` operator is delegated to the elements, but the choice of `&` operator shouldn't be up to the elements. We can't think of this in terms of a user-defined `==` on the `ValueTuple<...>` overloads, since those don't have access to the specific `==` implementations of the type arguments. 1. Same as `(tempA == tempC) && (tempB == tempD)`, taking whatever `&` operator eventually gets used. 2. Same as `(bool)(tempA == tempC) && (bool)(tempB == tempD)` but only when there is an implicit conversion to `bool` 2a. Same as 2 or `!(tempA == tempC).false && !(tempB == tempD).false`, so there are two ways to make the individual comparisons `bool` We want to do 2a, so that we make every effort to turn the result of each comparison into bool. For `==` we would use the `false` operator, for `!=` we will use the true operator. But the `&` and `|` are applied to booleans. For dynamic, let's look at what `if` does and probably do the same. # The fixed statement We're adding support for a type to have a special method that returns a pinned ref. ## Copy? Should that special method be executed on a copy or on an original l-value? We don't see good reasons to. - The method might want to change the state for the benefit of a future `fixed` or otherwise - Wasteful to copy big struct ## Generics We need to know if it's a struct or a class, to decide whether to copy (for the null check) or not (to preserve mutations in a struct). We already solved this for `?.`. We can emit a check for whether the type is a reference type (check whether its default is null), and the JIT specializes. ## Nullable No lifting. You can do your own if you really want, but there is no way (for the compiler or user) to expose the value itself without copying. If you want to pin an specific nullable type you can, as long as an extension method is provided for it. ## Ref extension methods Allow? Yes, same as for ref extension methods in general: Has to be called on a mutable l-value. For `in`, anything is fine. ## Name of method `GetPinnableReference` is weird enough; we don't have to put "Dangerous" or something in the name. ================================================ FILE: meetings/2018/LDM-2018-02-14.md ================================================ # C# Language Design Notes for Feb 14, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Ranges 1. `Range`s are pairs of `Index`es, `Index`'es can be from beginning or end, `^` syntax or similar denotes from end 2. `Range`s are pairs of `int`s, there's a convention of using ~x, that is, one's complement, for "from end" 3. `Range`s are pairs of `Index`es, but the conversion from int uses the ~x convention rather than a new operator 4. `Range`s are pairs of `int`s, all from beginning, but with a special bit for "open at the end" 5. `Range`s are pairs of `int`s, all from beginning, with no notion of "open" 1-3 hope to generalize to indexing in general, not just ranges. ``` c# coll[^x]; // option 1, from end coll[~x]; // option 2-3, from end ``` For people with existing indexers who want to add this, there are problems: - if you replace an `int` indexer with an `Index`, then you have a binary compat problem - if you add an overload, then option 2 and 3 would never pick that overload For indexing, `^0` would mean "length minus zero", which would be out of range. So there's an irony where we do all this work to allow expressing 0 from end, which would not be a legal index. Arguably, then, all of this bending-over-backwards is a consequence of wanting to express "zero from end" as the upper bound of an exclusive range. Making it inclusive at the end makes `^0` or `~0` actually denote the last element. In which case, it's even more necessary to express "zero from end". `"xxx|yyy|zzz"`, you want to truncate based on the position of the pipe. You want to extract the `yyy`. ``` c# var startIndex = s.IndexOf("|")+1; var endIndex = s.IndexOf(startIndex, "|"); // -1 ? var result = s.Substring(startIndex, endIndex-startIndex); var result = s[startIndex..endIndex] ``` Why should getting the end be easier than getting the beginning? Both should have to add/subtract one to get rid of the `|`. Or at least, so goes the argument for inclusive. The empty range, on the other hand, is a very convincing argument for exclusive. `0..-1` is just really nasty. Speculating about floating point ranges, having them inclusive-start, exclusive-end would actually likely be the best solution. They are imprecise by nature, so the only realistic way to get good adjacent "buckets" (where every element would be either in one or the others), is to not have symmetry between the endpoints on this. ``` c# var slice = myTensor[x1..x2, y1.:l, ^z1.., ..]; ``` Once you get multidimensional, everything "from end" gets really annoying to do manually, because even *getting* the end for a given dimension is quite involved. There's a small design question around `^0..` - is it legal or not? - We're overwhelmingly slightly preferring exclusive. - We do think that the scenario of counting from end is important, at least for ranges - Unsure that its value extends enough to other indexing scenarios. Exploring the hat options: 1. Limit hat to range expressions 2. Bake in Index to the language and return it from hat 3. Conversion from expression to Index 4. Typeless expression which gets meaning from range 1 keeps our options open, but leads to a natural next request. 2 lets indices be computed independently. 3 leaves open betterness for the future, at the cost of not allowing `var i = ^x`. 4 is a semantic version of 1. Unclear if different things are allowed between the two. Adding the `Index` type and the `^` operator is heavy lifting for relatively limited gain. Would we add indexers to existing types that can index from-end? Maybe to some, probably not to arrays (for performance reasons). Probably not more or less depending on whether we use `~` or `^`. We're still on the balance. ================================================ FILE: meetings/2018/LDM-2018-02-21.md ================================================ # C# Language Design Notes for Feb 21, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda Various big and small issues around nullable reference types, preparing for upcoming prototypes # How are explicit casts interpreted? ``` c# (object)null // object?, with warning (object?)string.Empty // object (IEnumerable)new[]{string.Empty} // IEnumerable (IComparer)Comparer.Default // IComparer, with warning ``` For the top two, there are two approaches you can think of: - nullability should be inferred from the expression - the cast represents an intent and we should honor it It's to some degree a tension between existing code or the right design for new code. If we were to infer nullability (rather than take it from the type), would nullability problems always be caught later on? ``` c# object o = ...; var o = (object)...; class X { object[] o; } ``` If we make it more lax, then there's more of a disconnect between top-level nullability and nested nullability. If we think of `?` not so much as a type thing but an observation on what's there, it doesn't seem so onerous to have it tracked locally. Another approach: Have it be a different warning. Then you can switch it off separately, for legacy purposes. For the casts: ``` c# (string)null; // A M((string)null);// B M((string?)x); // C M((string?)F()) // D - F is unannotated, and I want to impose my understanding of what it is M((string)F()) // E - F is unannotated, and I want to impose my understanding of what it is ``` In C you want to treat the argument as may-be-null, regardless of what is in x. In B you could have a warning that is off in legacy code Assume M is generic, and we make type inference based on arguments. Then D and E both make sense - they influence the result type of M, maybe. We shouldn't wave these off, but they are in some sense separable. In the prototype we could give you the choice. We could have it on by default, and the warning could tell you how to turn it off. C is either useless, or it means "forget inferred nullability". And it won't occur in old code. Let's expand B: ``` c# M((string)y); // ``` Say this is legacy, and now M gets upgraded to take `string?`. And `y` is now possibly null. The warning on `(string)y` could be one of those that is turned off for legacy purposes. Between two options: A: locals are implicitly nullable B: locals need explicit annotations like everything else, there's a concession to legacy ================================================ FILE: meetings/2018/LDM-2018-02-26.md ================================================ # C# Language Design Notes for Feb 26 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** "We introduced off-by-two errors!" # Ranges Python has indexing from the end, as well as range endpoints from the end. The last element is -1. In ranges, the only way to express "to the end" is then to omit the end point (since they're also exclusive). There is no way to talk about the position at "length" in Python. We are concerned with that approach, because you can easily make an error where you compute "distance from end" into a variable as 0, then accidentally it means "from beginning" instead of "from end". The benefit of `^x` and `~x` is that they can fully express the "at index length" index as `~0` or `^0`. Pro `~`: * Just works * No new syntax or types Against: * No protection against bugs (accidentally using `-`) * Cannot add overloads that go from end * No documentation for whether the int indexer understands negative If we do this just for `Range` (including maybe `^` syntax), then it's hard to add `Index` later. Existing types with indexers (e.g. arrays and strings) would be lots of work to evolve to make use of `Index` (or `Range` for that matter). Could the `int` conversion to `Index` also take negative numbers, but with the Python meaning? Then it would essentially "subtract 1 from negative numbers". The problem with `~` is that it is off-by-one from Python. The `~` can only hide it so much, but if you look in the debugger, `~1` is `-2`! Current options: 1. Do like Python: just live with "0 from end" not being expressible as a number; it's a special case (5) 2. Use ~ (1) 3. Add `Index` and `^` (6) 4. Don't support "from end" (1) Could we disallow "mixed" ranges (one from beginning one from end)? That's not a reasonable restriction. `1..-1` strips off the double quotes, etc. We're down to two options, 1 and 3. Let's take these through VB design as well, to see if we get new insights. Most likely to care are app developers, like web developers. Library authors will just be trading in Ranges that are already created. # Bestest betterness 1. When gathering candidates, if one of them has inferred type arguments that do not satisfy constraints, we'll discard the candidate. This means you can overload on constraints! 2. If you're in a static context, instance members are discarded, and vice versa. Only simple names in instance members will include both, as well as Color/Color situations 3. Converting a method group to a delegate type, we consider a conversion where the return type is wrong, even though that conversion is an error. Methods with such parameters will also be weeded out. In the first round this led to worse error messages (because we no longer pointed to the "wrong" method), but this has been fixed. This has been done in a way to not affect type inference. # Fixed arrays ================================================ FILE: meetings/2018/LDM-2018-02-28.md ================================================ # C# Language Design Notes for Feb 28, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** Parameters: Value parameters can be treated like locals, but we may not want to. Ref and out parameters need to be guarded by non-W warnings. Is it a problem that this can lead to two related warnings? Maybe a little bit, but it's actually mostly good. You can address the declaration of s2 in three different ways: * Make ns non-null before assigning * Put a question mark on s2 * Turn of W warnings Would be weird if that last one introduced another warning! Type inference: We could have `var` always have `?`. ``` c# static T[] MakeStack(T element) { } static int GetLengthOfMiddleName(Person p) { string? middleName = p.MiddleName; //return middleName.Length; if (middleName is null) return 0; var stack = MakeStack(middleName); // infer string[] or string?[] } bool b = false; string s = null; // suppressible var s2 = //(b ? s : s); Choose(b, s, s); var l = s2.Length; // not suppressible ``` Should we have type inference depend on the declared or the flowed type state? We reiterated that discussion, but conclude (again) that it's the null state that counts. This maximally helps avoid unnecessary warnings on legacy code. ## Cast and non-null ``` c# var s1 = "Hello"; // But I want to assign null later var s2 = (string?)"Hello"; // Either disallowed, makes no difference or forgets null state var? s3 = "Hello"; // Nullable but keeps the null state ``` So `var?` would be more useful than casts for `var` scenarios (because you are declaring a variable whose null state matters later). For generic arguments it doesn't matter to keep the flow state, so `(string?)` cast works fine. It's a little weird that `s2` and `s3` don't work quite the same way. They do today. Still, as a plan of record let's do this. So we keep the casts, and tentatively keep `var?`. ================================================ FILE: meetings/2018/LDM-2018-03-14.md ================================================ # C# Language Design Notes for Mar 14, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Indexing without pinning Fixed size buffer is formally a pointer, but when we are indexing it we don't need to materialize it as a pointer. We could stop requiring that even when the container is movable, and let you index fixed buffers that are movable. ## Conclusion Don't require fixed statement for indexing. Still unsafe operation. This not only makes the code a bit simpler, but can avoid significant overhead for pinning. # MVP Summit feedback # Records People are interested in getting rid of the boilerplate. Some just worry about member-wise repetition, others also about value semantics. Combines well with some (almost any!) sort of discriminated union feature to provide the value of F# and other functional languages, that you can see your whole data model on half a page. ## Conclusion This is important. We need to understand existing scenarios, and avoid a big cliff if you want to go advanced. We need to be super pragmatic. We want both records and discriminated unions, and we succeed when both are independently useful but fit well together. This is *not* a scenario for source generators. They may be able to address it, but for generally useful (not domain-specific) syntax like this, it should be a proper language feature. # Anonymous implementations Lots of scenarios around adaptation and delegation. Implementing `IEquatable` and `IComparable`, or `IEqualityComparer` and `IComparer`. Has some interaction with records or primary constructors, in that the context for construction is ambient, and you might want e.g. constructor bodies directly in the body of the "class". ## Conclusion If we can make it nice enough we are not adverse to this, but it probably doesn't have the highest priority. # Async Disposal Yeah do it. Some discussion more generally about easier alternatives to using. The simplicity of C++ RAII is attractive, maybe there are ways we can simplify, e.g. like F#'s use on variable declarations. Also defer statements. # Source generators Lots of interest in that. This is not a language thing, it is more a Roslyn/tooling feature. Let's try to raise it with them. # Nullable ## On-the-side annotations Mostly useful during a transition period. May be a lot of work of only temporary importance. On the other hand, the lack of it may hinder adoption. We should keep an open mind as to what such a feature would look like. But all versions we can think of have very significant challenges. Note that a library vendor can annotate their public surface area without dealing with their own warnings. Three phases: 1. Annotate public surface area with intent 2. Turning on warnings internally 3. Annotating locals Level 1 is a bit dangerous: you may be wrong about your intent if you don't check against your own source code. But it may be the least bad way of moving forward for a given library. ## Adoption There are challenges to adopting on existing code base that aren't there for new code. # Nullable constructors Annoying when you initialize differently. Solution must be some combination of easier opt-out and more analysis. We need to think about this more. This is when you trust someone else to initialize it, and you want to force them to do it with a non-null value. Lazy isn't a problem, because the underlying field owns up to its nullability. # Warnings There are certainly places where the same "null" leads to multiple dereferencing warnings. We could complain about only the first one. There may be other places where there are cascaded warnings. (Also the W warnings). We can probably satisfy most of these. We have to keep an eye on common uses and make sure people have a decent experience. Coming up: More nullable (type parameters, special members/annotations) Triage Range ================================================ FILE: meetings/2018/LDM-2018-03-19.md ================================================ # C# Language Design Notes for Mar 19, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda Triage # 7.3 Moved undone 7.3 features to 8.0. # Allow default in deconstruction We allowed for `==`, so it seems we should also allow it for assignment and initialization. Should probably not require `ValueTuple`, but that's more of a compiler thing. ## Conclusion The feature is a good idea. Even though it's simple, we should not rush it into 7.3. 8.0. ``` c# case Customer { MiddleName: "" or null } ``` # and, or and not patterns 1350 `not` feels useful. `and` is hard to come up with scenarios for. `or` has more scenarios, but may be better served by a `in { 1, 2, 3 }` style pattern. ``` c# if (x is not string s) { ... } else { ... /* s */ } ``` `not` would cause definite assignment to "flip". Should `s` be disallowed in a `not` pattern. The `{}` pattern for not null is a bit cryptic. `not null` would certainly be more direct. ## Conclusion Punt to 8.x, when we know more of the rest of the pattern story. # partial type inference #1349 We've talked about this many times in the past, but couldn't settle on a syntax that was nice enough and worth its while. A 4th option: allow M\(...) to match an M with more type parameters. But that probably goes against intuition and would complicate overload resolution. For 2: Just commas is known from typeof, so that concept is already in the language, though you can't mix "there" and "not there" today. ## Conclusion 8.X is when we'll look at it again. # Permit `t is null` for unconstrained type parameter #1284 For null we special case type parameters in all other kinds of places, so this is the only place where you can't. Also, `t is 3`. We allow *type* patterns, but not constant patterns. ## Conclusion Absolutely should do this. Mark as 8.0. # Implicitly scoped using #114 #1174 We have sympathy for this syntactic sugar, and we'd be interested in allowing it. It seems related to similar features, such as "defer" statements (avoids nesting `try/finally`). Also fits nicely with the work we already did with expression variable scoping. Also namespace without curlies, where you just say `namespace X.Y;` and it's in force for the remainder of the file. ## Conclusion Discuss at 8.X. Could get pulled up to 8.0. Let's also put defer and namespace into 8.X for consideration then. # User-defined positional patterns A next step from recursive patterns. ## Conclusion Let's revisit in 8.X when recursive patterns have played out. ================================================ FILE: meetings/2018/LDM-2018-03-21.md ================================================ # C# Language Design Notes for Mar 21, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda We discussed various open questions around nullable reference types # The null-forgiving operator ## Name Null-acquiescing Null-forgiving Null-silencing Null-quieting Darnit ## What does it do? Changes top-level nullability to not-null, as well as silencing warnings even on nested nullability ``` c# void M(string? ns, List? ln) { string s = ns!; // silence and change nullability var s2 = ns!; // silence and produce string ln = null; List? l = ln!; // silence and change var l2 = ln!; // Idea 1 l! // change nullability at the top level l // idea: change nullability at the nested level // Idea 2 (List?!)ln; // I do a cast that should warn but ! silences it List? l3 = (!)ln; // Shorthand } ``` Should `!` only change top-level nullability, and not suppress nested diagnostics? Or the other way around? Is it a problem that it does both and "suppresses too much". Since we made affordances for legacy code, the need for passing `null!` for instance is less: unannotated APIs already suppress warnings. Scenarios for `!` now that legacy APIs aren't a scenario: 1. I'm in the middle of converting my own code; need them at the boundary 2. The compiler can't figure it out, but I'm right Great example of the latter: ``` c# List strings; var query = strings.Where(s => s != null).Select(s => s.Length); // What?? Why warning? ``` Here you can put the `!` in `s!.Length`, but what if you have ``` c# void M(IEnumerable source) { ... } List strings; M(strings.Where(s => s != null)); // What?? Why warning? ``` Here you need to suppress on the nested nullability, because you know better. Currently you can say `!` at the end of the argument to set it right. Should that be a different syntax? What's the problem with `!` doing both jobs? ``` c# void M(string? ns, List? ln) { // I happen to know that if ln is not null, it doesn't contain nulls List? l = ln!; _ = l.Count; // warning? } ``` Now `l.Length` is not warned on because `!` did "too much". We wanted it to silence warnings, but it also changed the null state. The simplest fix with current semantics would be to cast to a nullable type: ``` c# List? l = (List?)ln!; // or var l = (List?)ln!; ``` Proposals: 1. Change null state and suppress 4 2. Two syntaxes 5 3. Only suppress warnings 1 4. Only change null-state 0 Approachable and easy to use. They are all about telling the compiler to shut up when you know what you're doing. For 3, the things we would make people do to change the null state would also be a hurdle. If we separate it, one may be a temporary feature that will go away over time, once people have transitioned. We're not going to settle this today. The prototype does "1+" which is even more lenient than 1. # Special methods # Hidden diagnostic ================================================ FILE: meetings/2018/LDM-2018-03-28.md ================================================ # C# Language Design Notes for Mar 28, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** Ranges # Shipping a prototype We would like to ship a prototype of ranges, in order to get feedback and settle the remaining decisions. We would need to offer the Range type, maybe as a NuGet. But it would also be nice to offer range-enhanced other types, like `Span` and maybe arrays. Maybe the compiler can fake out special indexers in the prototype. Or we also add extension indexers. Tying it to revs of .NET Core previews may be too sluggish, and fraught with dependencies. Or we create a wrapper type for Spans with the extra behavior, and smooth over the edges as best we can (implicit conversions etc) ## Conclusion Let's do: 1. A temporary "language feature" that's extension indexers based on method names 2. Range support in the language 3. A preview NuGet package with `Range`, associated types and "extension indexers" on known types # Which feature? We have two options: 1. Do like Python: just live with "0 from end" not being expressible as a number; it's a special case 2. Add Index and ^ The first one is the more restrictive, and also the cheaper one (from both work and number of abstractions). This will help us understand whether people need the extra. But not supporting "from end" is too restrictive; we have very good reason to believe people need that. # Nested stackalloc Issue #1412, trivial to implement in 8.0, where the stack-spilling machinery is there anyway for pattern matching. # Exhaustiveness in switch expressions Should non-exhaustiveness be an error or a warning? If warning, what should happen at runtime? For now, it's a warning, and if you get there we throw a new exception type for this. ================================================ FILE: meetings/2018/LDM-2018-04-02.md ================================================ # C# Language Design Review Apr 2, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # C# 8.0 Tag with needs runtime support or ecosystem support Process: make it more clear where we are. Help people understand when beating on a feature would be wasting their time. # Nullable Make sure we work backwards to understand how long it takes to build the whole experience. ## Dotted names We probably have a good level of invalidation. ## Type strengthening Based on null state and `!`. Should definitely keep that. ## ! Because `!` only applies "right here", it is ok to also silence warnings recursively. But we should not consider automatically flowing `!` on the given execution path then, because you may not always want to silence all warnings on the variable subsequently. ## Unannotated assemblies Maybe there should be a warning that you are referencing unannotated assemblies. ## Tracking non-null variables and "W" warnings Understand the motivation. This is ok. ## Type parameters Unconstrained may be either nullable or nonnullable, so we have to be defensive. That's quite restrictive, but probably right. ## Structural relationships In TypeScript there are more type relationships because of structural types. We don't even get to first base here. # Ranges ## Open Sure about syntax? Should there be `*` instead? ## From end Indexing from end is probably more common in Python than any ranges at all! Cutting that off with `-x` syntax is a shame. If we weren't doing `^`, just do ranges with positive numbers. Solve the "from end" problem in general or not at all. ## Conclusion If indexing and multiple dimensions are in the core syntax, might as well do the whole enchilada. Optimize in compiler when using `^` on arrays and strings. ================================================ FILE: meetings/2018/LDM-2018-04-04.md ================================================ # C# Language Design Notes for Apr 4, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda # Design review follow-up ## Ranges Let's flip what we prototype to implement the `Index` and `^` option. ``` c# (Index) ^ i ``` ## Nullable Let's start having the discussion on external annotation soon, bringing in the right people from BCL's etc. One proposal for external annotations: ``` c# [Nullable(typeof(MyClass))] class C { static object f1; static object? f2; } ``` Nice because it's source based. May also use or mirror JetBrains' annotations. https://github.com/JetBrains/ExternalAnnotations Offer the capability just for BCL vs for general libraries? # Ordering of ref and partial keywords There's a strict ordering around `ref` and `partial` preceding `struct`. We need to relax a bit, or a lot: 1. `ref partial` and `partial ref` are both allowed, but need to be right before `struct` 2. allow `ref` and `partial` anywhere in the modifier list Let's do 2, since that leads to a more consistent language. No reason to have special limitation on some modifiers, even though there might be guidance (and code style enforcement in the IDE) to put things in a certain order. # Patterns In Roslyn, we would probably put deconstructors that only return the "important" data, whereas non-optional tokens would be left out. (We're essentially reinventing abstract syntax tree.) If we evolve the node, we can add another overload of the deconstructor with more out parameters. The code gen is now roughly as efficient as what you would have written, though it doesn't know about testing `Kind` fields, of course (which actually isn't always more efficient). ## Order of evaluation in pattern-matching Giving the compiler flexibility in reordering the operations executed during pattern-matching can permit flexibility that can be used to improve the efficiency of pattern-matching. The (unenforced) requirement would be that properties accessed in a pattern, and the Deconstruct methods, are required to be "pure" (side-effect free, idempotent, etc). That doesn't mean that we would add purity as a language concept, only that we would allow the compiler flexibility in reordering operations. It may be a little disconcerting not to specify order of evaluation, but if patterns don't do what user code will do, they will be slower and will not be generally adopted/useful. People generally don't care what the spec says - they care if something changes release over release. So it's more important that the compiler doesn't change what it generates from version to version. ### Conclusion We're good with this. We don't want to slow down pattern matching just to guarantee order of evaluation when people do something side effecting where they shouldn't. We'll make an effort to discover if changes to the compiler change the output of existing code. Then we'll have a discussion about whether that is warranted. ## Range Pattern If we have a range operator `1..10`, would we similarly have a range pattern? How would it work? ``` c# if (ch is in 'a' to 'z') switch (ch) { case in 'a' to 'z': ``` ### Conclusion We like the idea, but are unsure about value yet, and probably want to pursue it in connection with `foreach` and `from` uses. ## `var` deconstruct pattern It would be nice if there were a way to pattern-match a tuple (or Deconstructible) into a set of variables declared only by their designator, e.g. the last line in this match expression ``` c# var newState = (GetState(), action, hasKey) switch { (DoorState.Closed, Action.Open, _) => DoorState.Opened, (DoorState.Opened, Action.Close, _) => DoorState.Closed, (DoorState.Closed, Action.Lock, true) => DoorState.Locked, (DoorState.Locked, Action.Unlock, true) => DoorState.Closed, var (state, _, _) => state }; ``` (Perhaps not the best example since it only declares one thing on the last line) This would be based on some grammar like this ``` antlr var_pattern : 'var' variable_designation ; ``` where the _variable_designation_ could be a _parenthesized_variable_designation_, i.e. generalizing the current construct. To make this syntactically unambiguous, we would no longer allow `var` to bind to a user-declared type in a pattern. Forbidding it from binding to a constant would also simplify things, but probably isn't strictly necessary. At a recent LDM it was suggested that `var` could perhaps be used as a placeholder for an unknown type taken from context. But that would conflict with this usage because this usage changes the syntax of what is permitted between the parens (designators vs patterns). This is implemented in the current prototype, in which `var` is now a contextual keyword. ### Conclusion This is a useful shorthand, and we already love it in C# 7.0 deconstructing declarations. We should have it here too, for symmetry and brevity. ## ref/lvalue-producing pattern switch expression As currently designed, the “switch expression” yields an rvalue. ``` c# e switch { p1 when c1 => v1, p2 when c2 => v2 } ``` @agocke pointed out that it might be valuable for there to be a variant that produces a ref or an lvalue. 1. Should we pursue this? 2. What would the syntax be? `e switch { p1 when c1 => ref v1, p2 when c2 => ref v2 }` You could argue that while the conditional (ternary) operator is often used in perf-sensitive code, and combining with `ref` was important there, it's less likely to see a combination of patterns and high-performance code. ### Conclusion This feels right, but is not all that important. Let's keep it on the back burner. ## switching on a tuple literal In order to switch on a tuple literal, you have to write what appear to be redundant parens ``` c# switch ((a, b)) { ``` It has been proposed that we permit ``` c# switch (a, b) { ``` There are a couple of ways of doing it: 1. Make the outer parens optional when there is a tuple literal 2. Make the inner parens optional when there is a tuple literal 3. It's a list that's part of the switch statement (and expression, when we get there) ## Conclusion Let's do 1. This means that in certain cases switch statements will no longer have parentheses, so analyzers that expect that will have to adjust. ================================================ FILE: meetings/2018/LDM-2018-04-25.md ================================================ # C# Language Design Notes for Apr 25, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda # Warn on nullable ref type argument when T has `class` constraint ``` c# static void F(T t) where T : class { t.ToString(); /* no warning */ } F(maybeNull); // warning: string? does not satisfy 'class' constraint F(string.Empty); // warning: string? does not satisfy 'class' constraint ``` This is a real danger, and we should be warning here. Note that in both cases the warning is about constraint checking: even in the second line we infer `string?` and it warns on constraint check. We should probably have the error message say that this is because of nullability. # Warn on nullable reference type argument when T has non-nullable reference type or interface type constraint ``` c# static void F(T t) where T : IDisposable { } F(maybeNull); // warning: Stream? does not satisfy 'IDisposable' constraint ``` Yes, warn for same reasons. We also want to warn (or error) on inconsistent nullability in constraints. Specifically if one constraint is known to be non-nullable and another is known to be nullable. What about ``` c# class C where T: class? where U : T, IDisposable where V : T, IDisposable? { // Fine } class D where T: class where U : T, IDisposable where V : T, IDisposable? { // Warn on V } ``` ``` c# ... where T : Node? ... where T : Node // both currently allowed, but not where T : Node? ... where T : class, INode ... where T : class?, INode? ``` ``` c# static void F(T t) where T : IFoo { } IFoo foo; F(foo); // warning, unless IFoo is covariant ``` Another kind of clash: ``` c# interface IBar : IFoo {} static void F(T t) where T : IFoo, IBar { } ``` We want to warn on this: maybe find shared base interfaces between the two constraints, and check if they have consistent nullability. Other, slightly more complex example, same conclusion: ``` c# interface IBar : IFoo {} static void F(T t) where T : IFoo, IBar where U : class{ } ``` # Allow nullable reference types and interface types as constraints `where T : Person?, IDisposable?` Yes # Allow `class?` constraint Yes How do we express in metadata? Let's put top-level nullability as an attribute on the type parameter itself, rather than on the constraints. We can noodle more on this later. # Allow `object` constraint ``` c# static void F(T t) where T : object { } F(maybeNull); // warning: constraint violated ``` ## Need to talk about relationship to value types soon ``` c# int? i = null; object o = i; o.ToString(); ``` # Allow `object?` explicitly? It's the "most general" constraint that is implied by unconstrained generics. Today we disallow `object` because of that. We don't know that this is a terribly useful restriction today, but we'll keep doing it (now with `object?`) unless we get evidence to the contrary. # Warn on dereference of T when T is unconstrained? Yes, if a type parameter `T` can be instantiated with a nullable reference type, then we should track null state and warn on unguarded dereference. The warning when occurring on unconstrained generics might suggest using the `object` constraint. # Warn on assignment to `object` of unconstrained `T`? Yes. This can be a `W` (cosmetic) warning when the `object` variable is a local. # default(T) with unconstrained T ``` c# T M() { T t = default(T); // W warning return default(T); // safety warning } ``` This is something completely safe. I don't want it to warn!: ``` c# T M() { var t = default(T); if (something) t = somethingelse; if (t != null) WriteLine(t.ToString()); } ``` This could be an argument for allowing `T?` that is not about special methods. Or it is an argument against having the W warnings at all. Let's keep this example around and revisit. But for now, let's consider `default(T)` to be potentially null, and therefore warn on its unguarded use. # Annotations We would like to deal with methods with special null semantics, such as string.IsNullOrEmpty, TryGetValue, Assert.NotNull, Object.Equals, Object.ReferenceEquals. We'll come back to this later. ================================================ FILE: meetings/2018/LDM-2018-04-30.md ================================================ # C# Language Design Notes for Apr 30, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Switch expressions Current syntax: ``` c# e switch { 1 => "one", 2 => "two", var x when x > 2 => "too many", _ => "too few" } ``` No `default` - instead use `_`. If the compiler thinks there are cases you don't handle, it'll warn. If you actually don't handle a case we throw an exception (NRE for prototype, something else in the long run). Also discussed: - use `match` instead of `switch` - keyword first, followed by parens, like `switch` statement - or without parens - optional implicit default at the end? The current thing is nice for error recovery. The lack of statements inside may be a frustration, but that's orthogonal. Let's leave it for now. ## Exploration Is there a way to instead generalize the syntax from the conditional (ternary) operator? After all, semantically speaking this could be viewed as a general form: Conditional operators and switch expressions on bool are semantically equivalent. ``` c# e ? true => e1 : false => e2 ``` The fact that you know the number of colons today means you can have fewer parentheses than you would get away with here. ``` c# e ? true => e1 ? x : y : false => e2 ``` It's not in fact obvious whether there should be many `?`s and one `:`, or one `?` and many `:`s: ``` c# // Interpret ? as following the tested expression, and : as a separator of test/result pairs e ? 1 => "one" : 2 => "two" : var x when x > 2 => "too many" : _ => "too few" // Interpret ? as introducing test/result pairs, and : as introducing the fallback result e ? 1 => "one" ? 2 => "two" ? var x when x > 2 => "too many" : "too few" ``` The `=>` glyph is probably not right in a `?:` style syntax. It would have to be something else that more clearly signals pattern/result pairs. ``` c# e ? 1 -> "one" ? 2 -> "two" ? var x when x > 2 -> "too many" : "too few" ``` ## Considering our options ``` c# // Compromise - terser e ? { 1: "one", 2: "two", var x when x > 2: "two many", "too few" } // Formatted e ? { 1: "one", 2: "two", var x when x > 2: "two many", "too few" } e switch { 1: "one", 2: "two", var x when x > 2: "two many", "too few" } // Some of these in context of a var var x = e ? 1 -> "one" : 2 -> "two" : var x when x > 2 -> "too many" : _ -> "too few"; var x = e ? { 1: "one", 2: "two", var x when x > 2: "two many", "too few" }; var x = e switch { 1: "one", 2: "two", var x when x > 2: "two many", "too few" }; // Some of these as one-liners in a method call M(x switch { null => 0, _ => x.Length }); // 1 M(x switch { null: 0, x.Length }); // 2 M(x ? null -> 0 : _ -> x.Length); // 3 M(x ? { null: 0, x.Length }); // 4 M(x ? null -> 0 : x.Length); // 5 M(x ? { null -> 0, x.Length }); // 6 ``` Argument against 1: ``` c# strings.Select(x => x switch { null => 0, _ => x.Length }); // Lots of => with different meaning ``` Argument against `->`: Has meaning in unsafe code Argument against `:` as used in 4: Clashes with other uses of `:`. Where input is an expression rather than a variable: ``` c# M(e switch { null => 0, var x => x.Length }); // 1 - 0 M(e switch { null: 0, var x: x.Length }); // 2 - 13 - 6 M(e ? null -> 0 : var x -> x.Length); // 3 - 1 M(e ? { null: 0, var x: x.Length }); // 4 - 6 - 3 M(e ? { null -> 0, var x -> x.Length }); // 6 - 0 ``` We might want to allow the last thing to be a default value without pattern, but not in the prototype. ## Conclusion The prototype will have version 2. We're saving for later whether the last clause should be able to leave off a pattern. # Property pattern ``` c# if (e is { Name: "Mads", Employer: { ID: string id } }) { WriteLine(id); } // 1 - Current if (e is { Name = "Mads", Employer = { ID = string id } }) { WriteLine(id); } // 2 if (e is { Name == "Mads", Employer == { ID == string id } }) { WriteLine(id); } // 3 if (e is { Name is "Mads", Employer is { ID is string id } }) { WriteLine(id); } // 4 ``` 1 is what we have implemented, but it clashes a little with what we just decided for switch expressions. 2 mirrors object initializers the most 3 implies equality, but clashes in meaning with `==` elsewhere 4 emphasizes `is` as a means for applying patterns ``` c# var result = person switch { { Name: "Mads", Employer: { ID: string id } }: id, (_, id: var pid){ Name: "Matt" }: pid, _: null }; ``` There's a clash but maybe it doesn't feel too bad. The second pattern with three different meanings of `:` is certainly not common. ``` c# var result = person switch { { Name is "Mads", Employer is { ID is string id } }: id, (_, var pid){ Name is "Matt" }: pid, _: null }; ``` ## Conclusion Decision: stay with `:` for prototype, remains open question though! ================================================ FILE: meetings/2018/LDM-2018-05-02.md ================================================ # C# Language Design Notes for May 2, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Revisit syntax for switch expression We got a lot of feedback on the decision to use `:` between pattern and result in each case of a switch expression, and we want to revisit it one more time before releasing the prototype. ``` c# state = (state, action) switch { (DoorState.Closed, Action.Open) => DoorState.Opened, (DoorState.Opened, Action.Close) => DoorState.Closed, (DoorState.Closed, Action.Lock) => DoorState.Locked, (DoorState.Locked, Action.Unlock) => DoorState.Closed, _ => state }; state = (state, action) switch { (DoorState.Closed, Action.Open) -> DoorState.Opened, (DoorState.Opened, Action.Close) -> DoorState.Closed, (DoorState.Closed, Action.Lock) -> DoorState.Locked, (DoorState.Locked, Action.Unlock) -> DoorState.Closed, _ -> state }; state = (state, action) switch { (DoorState.Closed, Action.Open) ~> DoorState.Opened, (DoorState.Opened, Action.Close) ~> DoorState.Closed, (DoorState.Closed, Action.Lock) ~> DoorState.Locked, (DoorState.Locked, Action.Unlock) ~> DoorState.Closed, _ ~> state }; state = (state, action) switch { (DoorState.Closed, Action.Open) : DoorState.Opened, (DoorState.Opened, Action.Close) : DoorState.Closed, (DoorState.Closed, Action.Lock) : DoorState.Locked, (DoorState.Locked, Action.Unlock) : DoorState.Closed, _ : state }; ``` There are pros and cons for all of these. In our previous decision we may have overemphasized one set of examples over another. `:`: Has an affinity to `switch` statements - it just removes the `case`. However, in `switch` statements, *statements* come after, whereas here, *expressions* do. `=>`: We have already put those elsewhere (expression bodied members) to say that you yield an expression as a result. However: 1. Pain to do parsing magic for the last one, maybe. (Not a strong argument) 2. precedence issues: if you had a `?:` in your pattern or `when` clause it would consume `=>` for a lambda 3. Looks like a lambda but isn't one 4. Will drive an expectation of a block body Conclusion: We're undecided, but we will switch back to `=>` for the prototype. # Nullable special members ``` c# static bool IsNullOrEmpty([NotNullWhenFalse] string? s) { } ``` Should use "When" in the name. ``` c# static void AssertNotNull([EnsuresNotNull] T? t) where T : class { } ``` Not necessarily for fatal methods. Could be a method that initializes a ref: ``` c# static void EnsureNotNull([EnsuresNotNull] ref string? s) { if (s is null) s = ""; } ``` Probably needs "Ensures" here to signal difference from restriction on incoming. Maybe "Ensures" on all of them? ``` c# class Object { [NullableEquals] public static bool ReferenceEquals(object? x, object? y) { } [NullableEquals] public static bool Equals(object? x, object? y) { } [NullableEquals] public virtual bool Equals(object? other) { } [NullableEquals] public static bool operator==(object? x, object? y) { } } ``` For operators and static binary methods on Object, the compiler can just know. For other user defined equalities we need an attribute. Also `IEqualityComparer` and `IStructuralEqualityComparer`, both generic and non. We probably still want to put the attribute on binary equality methods for clarity, but the behavior will be there regardless for the ones the compiler knows about. For unary instance method equalities, the attribute should just be `[NotNullWhenTrue]` (since the receiver is inherently not null!). Again, for the ones the compiler knows about (`Object.Equals` and `IEquatable.Equals`), we should have the behavior regardless of the attribute, but we should probably put the attribute in anyway. For *not* equal methods we may not need an attribute, since non-operator not-equalses are probably exceedingly rare. We're a little concerned about int-returning comparers, since chasing in a compiler analysis whether their result is 0 is quite subtle. We're open to discussing it again. As for `[NullableEquals]` on bool-returning binary equality comparisons, we should make it only work for exactly two parameters for now. If there are more parameters, it's not going to be clear that the first two are the ones being compared. `[NullEquality]` for now. We think we know what it does but we should drill in later to be sure. For the virtual method, first there is the concern about whether we can expect overrides to follow the contracts. Aside: when we see `o.x` with an instance member, then we should know that after, o was not null. In general, should we consider scoping reasoning around throws to the nearest catch clause? ================================================ FILE: meetings/2018/LDM-2018-05-14.md ================================================ # C# Language Design Notes for May 14, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda We discussed reactions and feedback from talk and booth at the BUILD conference. # Nullable ## Special methods We're on the right track ## External annotations We probably need them, we'll get back to it ## Incoming values to public API The feature doesn't help people remember to check non-null parameters for null. We could make it so that the null state of such parameters starts out as MaybeNull. This would definitely help people follow the current practice of aggressively protecting against unwanted null parameters. If in the future callers become more "reliable" on average, due to widespread adoption of the feature, then this may seem too harsh. Of course this wouldn't help when somebody passes you a `string[]` with nulls in it. And unless the parameter is actually dereferenced (or passed as non-nullable) inside of the method, you'd still get no warning. This speaks to maybe having a separate analyzer to help you remember to check, instead of building it into the nullable feature? "Public" could be `public`, `protected` and `protected internal`, or could be more dialable. Oftentimes people use public without intending it to be an API. They may be forced to by some other circumstance: interface implementation, or databinding etc. It's also a problem if multiple overloads leave the checking to just one of them, that they all delegate to. Now they will get warnings when they just pass on the parameter. There are lots of people who do not do argument checking, but instead leave the parameter to NRE on first occasion. This would work against that pattern. This rule feel somewhat more ad hoc than the other nullability rules, especially as it depends on accessibility. ### Conclusion Definitely worth discussing the scenario. There are many counterexamples to doing it like this, but it also seems reasonable to help people who want to do argument validation remember to do so. We're leaning to where this is an analyzer, not part of the feature itself. # Index and Range Generally the approach was approved of. It deals with some of the limitations in Python, for instance. The full generality of index expressions (working outside of indexing, having a natural type) wasn't always completely appreciated, but also wasn't considered harmful. The `^` glyph is a bit foreign at first, but nothing worse than that. ## Conclusion We're on the right track. # Default interface member implementations Some reaction against it, where it just seems really wrong to put code inside of an interface. Also, many people don't have the problem of evolving interfaces: if those interfaces are only implemented in their own code bases, they can just fix things up. So the motivation doesn't really apply to them. This is important if you are building public API, and also for consuming API from other languages, with Java and Swift interop in Xamarin being a prime example. ## Conclusion We still think this is an important feature. # Records and discriminated unions The main motivation for people who ask for these features seems to be simply conciseness. Exhaustiveness checking is interesting, but not nearly as important. Exhaustiveness is in fact not obvious: ``` c# sealed class Animal { sealed class Dog: Animal {} sealed class Cat: Animal {} } int M(Animal a) { return a switch { Cat c => 1, Dog d => 2, } } int M(Box b) { return b switch { Box(Cat c) => 1, Box(Dog d) => 2, } } ``` These two switches may look exhaustive, but are actually missing null cases both of the `Box` and the `Animal`. To be exhaustive they should really be: ``` c# int M(Animal a) { return a switch { Cat c => 1, Dog d => 2, null => 3 } } int M(Box b) { return b switch { Box(Cat c) => 1, Box(Dog d) => 2, Box(null) => 3, null => 3 } } ``` We probably want to have different exhaustiveness checking depending on whether `a` is `Animal` or `Animal?`. But that means that missing null cases can only yield warnings, just like nullability does, since we don't want nullability to have any semantic effects or yield errors. ## Struct unions We could work with the runtime to allow us to overlay different types inside of a struct. For unions where each variant is small, structs would probably be highly preferable from a performance perspective. ================================================ FILE: meetings/2018/LDM-2018-05-21.md ================================================ # C# Language Design Notes for May 21. 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Target typed new There's a PR for target typed `new` expressions ```c# M(new (1)); ``` We generally like it, and have been interested in a feature like this for a while, but we're a bit concerned about the ways in which use of it can be brittle. In the code above, adding another constructor to a type (which one of the overloads takes) can now cause an ambiguity, or cause a different overload to be picked. It's roughly as "bad" as adding another implicit conversion. We could consider restrictions. We could do it only in initializers, which is a common request. But that's still a harsh restriction. We would probably soon be back to considering the general version of the feature. We could warn (or have an analyzer warn) if there is more than one possible target parameter type among overloads corresponding to a target-typed `new` argument. We could also take a similar approach as we do to out vars. There we don't even take the parameter type into account for betterness purposes. We're ambiguous if two overloads differ only on the type of an out parameter, regardless of whether one is "better" than the other. If we do this similarly, then folks wouldn't easily get silent changes when overloads are added: instead they'd get ambiguity errors, and would be forced to put in a type name, or otherwise disambiguate. ## Conclusion We would like to pursue this feature. Let's schedule a design meeting to make sure we have the design ironed out. We like the more restrictive approach to overload resolution, similar to out vars. # Return/break/continue expressions This is a convenience. However, it risks a syntactic conflict with other potential futures, especially "non-local returns" (allowing a lambda to return from its enclosing method) and "block expressions" (allowing statements inside expressions). While we can imagine syntaxes for those that do not conflict, we don't want to limit the design space for them at this point, at least not for a feature that is merely "nice to have". Also, while we've been talking about this in analogy with throw expressions, that isn't quite right. `throw` is a dynamic effect, whereas `return`, `break` and `continue` are statically bound control transfers with a specific target. ## Conclusion We're grateful for the pull request, but do not want to go forward with the feature at this point. # Async streams ## Async foreach task consumption Just like foreach it will look for an interface or a pattern. Should the pattern require `Task`, or be pattern-based all the way down? ### Conclusion Pattern all the way down. Same as with `await`. ## Async foreach extension methods `foreach` doesn't allow extension methods for `GetEnumerator` etc. We would like to change that, but there are obscure potential breaking changes there. Should `foreach await` allow extension methods? ### Conclusion Let's allow, and work to allow for synchronous `foreach` as well. ## Async iterator lambdas We don't do iterator lambdas today, and it's low priority to add it, though it's not really harmful. ### Conclusion We won't do it for async iterators either, until such time as we decide to do it for the synchronous ones. ## When is it an iterator? In synchronous iterators, the `yield` keyword is what makes it an iterator. The same for async iterators: the combo of the `async` modifier and the presence of the `yield` keyword makes it an async iterator. Whether you actually await is at most the subject of a warning, just as withe other async methods, and has no other bearing. ## Pattern-based return type of async iterators? Async methods no longer have to return `Task` and `Task`, but can follow a pattern. We don't currently do a similar thing for iterators, but we should think about it, also for async iterators. ## foreach await over dynamic Block it. For synchronous foreach we resort to the nongeneric `IEnumerable`, but there is no nongeneric `IAsyncEnumerable`, and there won't be. ================================================ FILE: meetings/2018/LDM-2018-05-23.md ================================================ # C# Language Design Notes for May 23, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Working with data Collections of data with heterogeneous types. It's *not* objects, but decoupled from operations. - tuples - anonymous types - classes All have a mutability issue, just different ways. Modeling enumerated types is complex and doesn't provide exhaustiveness. Also, you cannot efficiently switch over different types. You do tricks like visitors, abstract kind properties, etc. Performance, correctness and succinctness are all in conflict. Type patterns help, because they look good, but they are still not as efficient or as safe as they could be. Technically speaking, mutability and value equality are a dangerous combo on classes. If the object mutates, its equality and hashcode change, meaning you could lose track of them in dictionaries, etc. On the other hand, C# is mutable by default: should we bend over backwards to not support this combo? Object initializers aren't strictly necessary, but they jive well with `with` expressions, give a less positional view. Object initializers are really popular today, in that they are used a lot. But it's unclear whether they are because the declaration site doesn't provide constructors, and instead do the "easy" thing of just offering auto-properties. Withers: we keep talking about them for readonly data, but they might very well be useful on mutable data as well. The "data classes" proposal puts weight on *not* being positional. It could even reorder members alphabetically in generated positional constructs such as constructors. Separately, there is an idea of "named tuples". These are what have previously been proposed as "records". These are an evolution story for tuples. This may be an overload of concepts. It may be that we can view them as aspects of the same feature; one may be an evolution of the other. Discriminated syntax: Enum classes. Starts very simple, probably has ways of letting you grow up. That gets messy though, with the nesting, but we could use partial to separate things. Kind fields must necessarily be unspeakable, and discoverable only by the compiler. Otherwise they can't be efficient. Records and data classes are extensions of existing constructs. Enum classes are more of a new concept. We kind of agree on the simple cases. It's how they grow up and fit into the existing world that's fraught with questions. Interestingly, you sometimes want your "enums" *not* to be exhaustive. This is the case when you expect it to evolve with more cases. ================================================ FILE: meetings/2018/LDM-2018-05-30.md ================================================ # C# Language Design Notes for May 30, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Annotating parts of a project Roslyn starts out with about 2000 warnings. We may want to support nullable annotations for only a part of a project, so that you can gradually ease into them. This touches on a core question: Are there separate opt-ins for *consuming* nullability (give me warnings please) and *producing* nullability (consider unannotated reference types to be non-nullable; "URTANN"). If we have a scoped "URTANN", then turning on warnings for the whole file would still have limited impact, until annotations start becoming abundant. But we may also want to consider warnings to be turned on in a scoped way, at a certain granularity. Source files? Fine grained program elements? It might be better not having this, though, as it comes with the risk of introducing more problems (through annotation), without discovering it (because the consumption has warnings off). ``` c# T M(string s) => M2(s); // No warning because s is oblivious [URTANN] T M2(string s) => ... ``` The opt-in to warnings could be a "fake" error code that represents a category. We may not need a brand new dedicated command line option, but we're willing to go there. 1. We agree that we are comfortable (for now at least) in opting in to the warnings at the whole project level only 2. We agree that there should be a separate mechanism for opting in/out of non-null annotations (URTANN) 3. We agree that URTANN should be attribute-based, and able to be applied (or unapplied, with a `false` argument) at multiple levels of program elements Decision: Let's have `URTANN(true)` implicit by default, unless you have another module-level URTANN. We can maybe auto-generate it if you don't have it already. It should be called `NonNullTypesAttribute(bool)`. ================================================ FILE: meetings/2018/LDM-2018-06-04.md ================================================ # C# Language Design Notes for Jun 4, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** # Nullable flow analysis ``` c# static void F(object? x) { object y = null; Action f = () => y = x; // warning? if (x == null) return; f(); y.ToString(); // warning? } ``` A proposal is to take the most nullable the variable can be and use that state in a lambda that captures it. (This is only relevant for top-level nullability, as nested nullability is not tracked through flow.) There's a slight concern that we could run into cycles, where "the most nullable state" depends on "the most nullable state". We think that it is not going to be an issue. ``` c# M(string? x, string? y) { Action f = () => y = x; x = null; //M1(f); x = ""; M2(f); y.Length; } ``` It seems a shame that optimizing like this, by caching the delegate, could lead to more nullable warnings. We could imagine tracking delegates through locals and basing the analysis on where they are used (called or passed). Envisioned safe tightenings: * Only care about null assignments that happen after the lambda becomes "effective" * If a lambda goes into a local variable, then it is only "effective" when that local variable is used * Such a local, when invoked directly, does not depend on future null states Another option is to just be more loose about the whole thing, and allow there to be holes. Specifically we could assume that the lambda is executed either when it appears or not at all. The hole is that this does not accurately account for deferred execution. Thinking about the effect of the lambda above on `y`, it can happen at any time after the lambda. So at any point after that, the null state of y would be a "superposition" of null states. So if a lambda makes a variable `MayBeNull`, it would be irreversibly `MayBeNull` for the remainder of the method. *Even right after a null check!* It's on perpetual lockdown! This seems draconian. It feels stronger than our position on dotted names, which lets us assume that the null state remains stable between observing and using. That suggests we should be at least somewhat loose. We could maybe just assume that the lambda is conditionally executed at the point of capture, and we assume that mutations don't happen later. Aside: lambdas that assign captured variables aren't as esoteric as they may seem. For instance, our whole ecosystem around analyzers relies on a recommended pattern that does that. ## Conclusion We analyze a lambda as if it is conditionally executed only at the point of capture into a delegate. It does rely on order of execution to an uncomfortable degree. Some of the refinements we've considered could be introduced later. For instance, these two examples would behave differently if `y` comes in non-null and `x` comes in maybe-null: ``` c# void M1(Action, ref string s); M1(() => y = x, ref y); void M2(ref string s, Action); M2(ref y, () => y = x); ``` Side note: There's a similar granularity issue with the nullability attributes, whether they should apply to parameters as soon as that parameter is encountered, or only after the whole argument list. # Local functions When a local function is captured into a delegate, we just do the same as for lambdas: assume a conditional execution at the point of capture. When local functions are called, abstractly speaking we should do the same as for definite assignment, where the *requirements* and the *effect* are inferred for each local function, and then applied in place where the function is called. The difficulty of course comes in when functions are recursive. For definite assignment, we can prove that a recursive analysis terminates, because of the monotony of definite assignment (you never become *un*assigned). Can we make a similar argument/proof for nullability? # Conditional attributes and special annotations Let's change `EnsuresTrueAttribute` to `AssertsTrueAttribute`, and always take them into account, even when the condition is false. ================================================ FILE: meetings/2018/LDM-2018-06-06.md ================================================ # C# Language Design Meeting ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda Expression trees # Scenario ## Big data Bing scenario, sending code from ad hoc queries would be too heavyweight through traditional compiling and sending of assemblies. Also it's not just execution but intense analysis, which feeds into where the query ends up running etc. Introspection. Currently work around various limitations. Async calls may be part of the query, and there's a lot of code manipulation for that. ## Machine learning and GPU are other important scenarios: capturing code for auto-differentiation (ML) or for translation to GPU executable code. # Restriction as a feature Would allowing more nodes remove a useful restriction, that people depend on today? For instance, most new nodes we add may not be sensible in EF. We've always had this problem, and been able to write something that fails at runtime. Partial solutions: - provide alternative factories with a pluggable builder pattern, driven by the target type (just like we now do for task-like types) - offer analyzers that are domain specific. Provider model: Probably not that unrealistic to implement your own factory. Plugging it through the language probably requires some extra work, but it seems doable. This would allow static checking of shape, and would give analyzers something to latch off of for further restriction of nodes. The expression lambda rewriter today is nicely isolated in the Roslyn codebase and only ~1100 lines. There's a further step we could take, which is to freeze current expression trees in time. Then whoever wants to understand new language features needs to use a new expression type. # Reduction Expression trees have been significantly expanded since the language tied into them. There is an extension story, where a node can be reducible. It then offers to translate into other nodes. This helps older providers still work, as well as lets you not have to do the work to implement all the nodes, only the irreducible ones. We don't want to overdo reduction. We probably wouldn't reduce async lambdas to trees representing the state machine. That would overly commit us to some very specific implementation choices, and probably also leave performance opportunities on the table. # Philosophy It's a big question whether we want to commit to expression trees following the language in perpetuity. But we don't need to commit to that. As long as we feel an upgrade is useful. # Extending existing nodes This is not just about adding new nodes, but also expanding existing ones with new expressiveness. We'd have to be careful to keep generating old factory method calls for existing situations. # Bart's experiment github.com/bartdesmet/ExpressionFutures/tree Inherits and shadows `System.Linq.Expressions.Expression`. Supports dynamic, async. Needed some hacks because it is a separate library, if it goes into the BCL it would have access to the right things. Supports null-conditional, discard. Statements are generally supported up to C# 6.0. Since these are a shadow library over System.Linq.Expressions, there's an updated Roslyn compiler that also understands those. # Plan What we would like to do: * Use factory methods as the well-defined interface between compiler and API * Feel good about incrementally improving without having to do all of the language at once * Add provider model/builder pattern for other factories * Get to a prioritized list of specific language constructs to start supporting * Evolve compiler and API together for specific constructs * Checkin with existing providers such as EF to ensure compat story is good, and their scenarios are taken care of For now, let's mull it over for a couple of months, pursue information, crisp up the plan. Only after a while will we be able to free up compiler resources. ================================================ FILE: meetings/2018/LDM-2018-06-25.md ================================================ # C# Language Design Notes for Jun 25, 2018 ***Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!*** ## Agenda 1. Target-typed new-expressions # Target-typed new-expressions ## Syntax ``` c# C c = new (...){ ... }; ``` You can leave off either the constructor parameters `(...)` or initializer `{ ... }` but not both, just as when the type is in. ## Conversion This will only work if a) we can determine a unique constructor for `C` through overload resolution, and b) the object/collection initializer binds appropriately. But are these errors part of *conversion* or part of the expression itself? It doesn't matter in a simple example like this, but it matters in overload resolution. ## Overload resolution There are two philosophies we can take on what happens when a target-typed new-expression is passed to an overloaded method. ### "late filter" approach Don't try to weed out overload candidates that won't work with the new-expression, thus possibly causing an ambiguity down the line, or selecting a candidate that won't work. If we make it through, we will do a final check to bind the constructor and object initializer, and if we can't, we'll issue an error. This reintroduces the notion of "conversion exists with errors" which we just removed in C# 7.3. ### "early filter" approach Consider arguments to constructor, as well as member names in object initializer, as part of applicability of a given overload. Could even consider conversions to members in object initializer. The question is how far to go. ### Trade-off The "early filter" approach is more likely to ultimately succeed - it weeds out things that will fail later before they get picked. It does mean that it relies more on the specifics of the chosen target type for overload resolution, so it is more vulnerable to changes to those specifics. ``` c# struct S1 { public int x; } struct S2 {} M(S1 s1); M(S2 s2); M(new () { x = 43 }); // ambiguous with late filter, resolved with early. What does the IDE show? ``` Adding constructors to the candidate types can break both models. Adding fields, properties, members called `Add`, implementing `IEnumerable` can all potentially break in the early filter model. ``` c# M2(Func f); M2(Func f); M2(() => new () { x = 43 }); S1 Foo() => new () { x = 43 }; ``` Even if we did late filtering, this would probably work (i.e. the `S2` overload would fail), because "conversion with error" would give an error in the lambda, which in itself rules out the overload. We're having a hard time thinking of practical scenarios where the difference really matters. Only if we go to the "extremely early" position where the expression could contribute even to type inference. We've previously considered: ``` c# M(C c); M(new C (...) { ... }); ``` Where the type arguments to `C` could be left off and inferred from the `new` expression. This would take it a bit further and allow ``` c# M (new (...) {...}); ``` In that same setup, contributing to type inference from the innards of an implicit `new` expression. ## Conclusion We are good with late checking for now. This does mean that we reintroduce the notion of conversion with errors. ## Breaking change As mentioned this introduces a new kind of breaking change in source code, where adding a constructor can influence overload resolution where a target-typed new expression is used in the call. ## Unconstructible types That said, we could define a set of types which can never be target types for `new` expressions. That is not subject to the same worries as the discussion above, where the innards of the `new` expression could potentially affect overload resolution. These are overloads where no implicit `new` expression could ever work. Candidates for unconstructible types: * Pointer types * array types * abstract classes * interfaces * enums Tuples *are* constructible. You can use `ValueTuple` overloads. Delegates are constructible. ## Nullable value types Without special treatment, they would only allow the constructors of nullable itself. Not very useful. Should they instead drive constructors of the underlying type? ``` c# S? s = new (){} ``` ### Conclusion Yes ## Natural type Target-typed new doesn't have a natural type. In the IDE experience we will drive completion and errors from the target type, offering constructor overloads and members (for object initializers) based on that. ## Newde Should we allow stand-alone `new` without any type, constructor arguments or initializers? No. We don't allow `new C` either. ## Dynamic We don't allow `new dynamic()`, so we shouldn't allow `new()` with `dynamic` as a target type. For constructor parameters that are `dynamic` there is no new/special problem. ================================================ FILE: meetings/2018/LDM-2018-07-09.md ================================================  LDM July 9th, 2018 ------------------- _QOTD: "Yeah, it's easy if you do it in a shi**y way"_ ## Agenda 1. `using var` feature 1. Overview 2. Tuple deconstruction grammar form 3. `using expr;` grammar form 4. Flow control safety 2. Pattern-based Dispose in the `using` statement 3. Relax Multiline interpolated string syntax (`$@`) # `using var` Feature **Motivation** Proposal: https://github.com/dotnet/csharplang/pull/1703 It's a common problem that multiple `using` statements can require successive nesting, causing what is mostly linear code to have the "down and to the right" problem, where increasing indentation makes the code less readable, not more. One way people try to solve this is using the ```C# { using (expr1) using (expr2) using (expr3) { ... } } ``` syntax, but that has two problems. First, many style guidelines prohibit "braceless" usings, but make an exception for this specific case. Second, if there is any intervening code required between the `using` expressions, this syntax form is not allowed. **Objections** Objections to this feature fall mainly in two categories. Either there is worry about determinism and ordering, or that this feature isn't sufficiently general to encompass the scenarios we would consider making the feature "worth it." The determinism concern is that refactoring from the `using (...) {...}` form could unintentionally lengthen the liveness scope to the entire method, instead of just to the closing brace of the using. The ordering concern is that nesting provides very clear ordering semantics, and the "stacked using" form also has a clear ordering, since there cannot be any code in between each `using`. This isn't necessarily true for using-variables. It's possible that both of these concerns could be mitigated by better refactoring and analysis tools. The generality concern is mainly around the `using (expr) { ... }` statement form, which doesn't have an equivalent using-variable form in the current design. **Conclusion** It's worth it. The concerns are valid, but don't seem bad enough to block the feature. ## Tuple deconstruction grammar form The first question was about the proposed grammar. The current design is a new type of statement (`local-using-declaration`). There are two potential holes in the grammar: no space for tuple deconstructions and no `using expr;` form. For deconstruction, we came up with a number of potential forms: ```C# (using var x, using var y) = M(); // Form 0 using (x, y) = M(); // Form 1 using (var x, var y) = M(); // Form 2 using var (x, y) = M(); // Form 3 using var t = M(); // Form 4 ``` Of these, only (4) would be legal in the current proposal. Of the remaining forms, form (0) seemed the clearest. There was consensus that this implied the declaration of two new variables, each of which was independently disposed, in the style of ```C# using var x = M1(), y = M2(); ``` It was not immediately clear whether the tuple itself was disposed in form (0). This was a common complaint with the rest of the forms as well: it is unclear what the semantics of each statement is. Is the tuple itself being disposed? Is disposal distributed over the elements? Both? Some tuple deconstructions also happen in "reverse" order of the tuple elements' lexical ordering. If dispose is distributed, what order are the elements disposed in? This also raised the question of nested declarations in the initializer, e.g. ```C# using var x = M1(out var y) ``` Is `x` the only `using` variable? Or is `y` one as well? **Conclusion** Let's continue with the proposal as-is. Form (4) works and should work. There may be compelling scenarios to open up the syntax to tuple deconstructions, but we don't have a convincing argument yet. We also don't have a clear rule for prohibition. For nested declarations, they are not declared as `using` variables. ## `using expr` grammar form These concerns dovetailed into discussion of the `using expr;` form, because some of these grammar forms may compose. For example, since `var (x, y)` is an expression in C#, the following could be a potentially legal statement with no modification: ```C# using var (x, y) = M(); ``` In this case `var (x, y) = M()` would be the `expr` in `using expr;`. This form seems desirable to round out the feature, but it isn't clear how it fits into the language. The previous decision seems to imply we don't want `using` deconstructions, but it isn't clear what rule we would use to prohibit them, in a principled sense. The feature also has some integration concerns. `using (expr);` is already a legal construct in the C# language with different semantics, although the compiler gives a warning about it today. There is some concern that `using expr;` and `using (expr);` are too close grammatically and that the syntax effectively rules out parenthesized expressions. Finally, there were questions about grammatical ambiguity with possible future language features. If C# were to allow statements on the top level, a `using System;` line could either be a using-directive if `System` is a namespace, or a using-statement if `System` is a type. The same problem could occur if we were to allow using-directives at the statement level. This doesn't seem very bad since we already have similar ambiguities with `Color Color` rules and resolve them properly during semantic analysis. These ambiguities are also probably present for using-directive aliases. There were a couple proposals to try to deal with some of these problems: 1. Any expression that declares variables is disallowed as a `using expr;` 2. Hold off on `using expr;` for now. 3. Allow `_` as a discard for `using var _ = expr;` - Or `using _ = expr;` **Conclusion** This is a blocking issue that we must decide on for C# 8.0. Either we should disallow this form entirely or find some principle to use to reject the constructions we find confusing. However, we think this problem is solvable and shouldn't block continued work on the feature. ## Flow control safety The last design issue was safety in the presence of `goto` and similar flow control features (e.g., local functions). The existing spec notes that backward flow control is not a problem, but what about forward flow control? For example, ```C# { goto target; using var x = new FileStream(...); target: var y = x; return; } ``` In the previous example, this is an error, because `x` is not definitely assigned. In fact, all uses in this category, where flow is manipulated to skip over the variable definition before a read, are safe because the variable will not be definitely assigned. In addition, because `using` variables are read-only, it also cannot be assigned later. One case which the spec does not currently handle is ```C# { goto target; using var x = new FileStream(...); target: return; } ``` Here `x` is never read, so there would be no definite assignment errors. However, there is an implicit read of the variable at the end of the variable lifetime, which could be a read of an unassigned variable. **Conclusion** A new line to the spec should be added saying that, if the end of a `using` variable's lifetime is reachable, that variable must be definitely assigned at that point. *Open question* * [ ] This needs more precise language. The spec does not have reachability at "points". What do we mean when we say the end of a block is "reachable"? # Pattern-based `using` statement We like the feature. Main question: what type of pattern do we look for? As a general guideline, we don't want to have another special case pattern. However, it seems like we have multiple styles already. - `GetAwaiter` doesn't allow `params` or optional parameters - LINQ does Do we want `using` to be like `await` or like LINQ? Also, do we require `void` return type? Most of the patterns today have strict requirements on return type, but they also usually consume the return type. `using` does not. **Conclusion** Keep the spec as is: `Dispose` must be parameter-less in instance-form, `void`-returning, and accessible. This allows for extension methods, but not optional parameters or `params`. # Multiline interpolated string syntax It's hard to remember which is the correct syntax: `$@""` or `@$""`. The proposal is to allow either. **Conclusion** No objections. ================================================ FILE: meetings/2018/LDM-2018-07-11.md ================================================ # C# Language Design Notes for Jul 11, 2018 ## Agenda 1. Controlling nullable reference types with feature flags 1. Interaction with NonNullTypesAttribute 1. Feature flag and 'warning waves' 1. How 'oblivious' null types interact with generics 1. Nullable and interface generic constraints # Nullable feature flag A nullable feature flag has been proposed that turns on the new warnings for the nullable reference type feature. A feature flag is proposed because new warnings could be generated for existing code, so the developer must opt-in to the new warnings. The question is: what does the feature flag do, exactly? Binding and emit are unaffected by feature flag, so code will not behave any differently with it enabled. If the feature is on you get nullability warnings. If the feature is *off* and you annotate a reference type with '?', a warning is produced that the feature is disabled, and the IDE should offer to turn the feature on. Without a warning, a developer could think they are using the feature because they've annotated their references, but in fact no warnings are being provided. **Question**: What if a developer wants to annotate their public API, but they don't want to consume the feature in their own code? **Answer** There are two options: - Turn the feature on and disable all the nullable warnings - OR turn feature off and suppress the warning about the feature being off ## Interaction with NonNullTypesAttribute Related to the feature flag, a NonNullTypes attribute is proposed that decides how unannotated types are interpreted by the compiler. If `NonNullTypes(true)` is present, unannotated types are assumed to be non-null. If it is absent or `NonNullTypes(false)` is present, then unannotated types are "oblivious" and don't produce warnings. How do the attribute and the feature flag interoperate? Two independently varying variables: - Assembly is annotated - Warnings are produced Here are the possibilities: | | Warning | No warning | |-------------|------------------|------------------------------| | Annotated | C# 8 feature on | C# 8 feature off w/ suppress | | Unannotated | N/A | C# 7 | Each of these seem reasonable. Q: What about external annotations? A: Don't know enough about their design yet to decide **Conclusion** Proposal accepted. For the `NonNullTypes` attribute, do we block it for older compilers? - We usually don't poison new feature attributes for older compilers (e.g., `dynamic`, `params`). We just provide errors in new compilers. - This has not been a problem in the past -- # Nullable references and warning waves Q: Do we want to include the nullable references in Warning waves? Would "v8" include all nullable warnings? - If the feature flag affects the `NonNullTypes` context, it doesn't just impact warnings - The benefit of warning waves is that tooling support wouldn't need to be customized for this feature Proposal: If you use the feature, it updates the warning wave and sets the `NonNullTypes` setting. Concern: If so, when you get the new warning wave, now you get nullable *and* all the other warnings. If you only want one set but not the other, there's no way to split them out. **Conclusion** Don't connect nullable warnings to warning waves # 'Oblivious' types and generic parameters Q: Does `M` require `T` to be a non-nullable reference or value type? A: Yes. If there's a mix of generic types where `T` is oblivious or non-oblivious, say ## Generic substitution The question is what to do about the following cases: | | List | List | List | |-------------|----------|------------------------------------------------------|---------------| | T = string! | | C#7: `List F(T t);`
C#8: `var s = F(“”);` | List | | T = string~ | C#8: `List F(T t);`
C#8: `var s = F(obliviousString);` | | C#8: `List F(T? t);`
C#8: `var s = F(obliviousString);` | | | T = string? | `List` | C#7: `List F(T t);`
C#8: `var s = F(nullString);` Note that the previous table uses a shorthand that is not meant to be actual C# syntax. `T!` means non-null reference type, `T~` means oblivious type, and `T?` means nullable type. `T?` is the only syntax which is actually expected to appear in valid C# programs. For ```C# List F(T? t); var s = F(obliviousString); ``` Decision: Type parameter wins. `s` is a nullable string. For ```C# List F(T t) var s = F(obliviousString) ``` Is `s` oblivious? Does this "flow" oblivious all over? **Proposal**: "Collapse" oblivious types as soon as possible So the previous type substitution, is `List`. Even outside of type substitution, oblivious types won't be inferred, so in `var s = obliviousString;`, `s` would be inferred as `string!`, not an oblivious type. **Conclusion** Proposal accepted. For ```C# List F(T t) var s = F(nullString); ``` The substitution is the argument type. `s` is a nullable string. # Nullable and generic constraints First, it is decided that `T?` requires `T` be known as a reference type. Notably, an interface constraint alone is not sufficient. What does the following do? ```C# void M1(T t) where T : I, T : class? ``` This is a warning or error, because `T : I` implicitly means that `I` is a non-nullable type and `class?` is a nullable reference type. The constraints are contradictory. There are two fixes. Either ```C# void M1(T t) where T : I?, T : class? ``` to make `T` a nullable reference type. Or ```C# void M1(T t) where T : I, T: class ``` and now `T` is a non-nullable reference type. ================================================ FILE: meetings/2018/LDM-2018-07-16.md ================================================ # C# Language Design Notes for Jul 16, 2018 ## Agenda 1. Null-coalescing assignment 1. User-defined operators 1. Unconstrained type parameters 1. Throw expression the right-hand side 1. Nullable await 1. Nullable pointer access 1. Non-nullable reference types feature flag follow-up # Null-coalescing assignment This feature has the syntax `??=` and only assigns the left-hand side of the assignment if the left-hand side evaluates to null. ## User-defined operators The proposal reads, `if (x == null) x = y;` **Conclusion** This should instead read `if (x is null) x = y;`. The difference is in the operators: null-coalescing assignment, like the null-coalescing operator, does not consider user-defined operators like `==`. The spec also states that the semantics are equivalent to `x = x ?? y`, but that should instead be `(x ?? (x = y))`. If `x` is not null, the assignment is never performed. ## Result type of the expression Other assignments have the type of the left-hand side e.g., `(x = y)` has the type of `x`, not `y`. This has the convenient property that any assignment expression can be replaced with the variable being assigned. However, there may be some value in "transforming" the type to non-nullable when possible. For instance, `(x ??= y)` could evaluate to a non-null type if `y` is non-null, since we know that the null-coalescing assignment cannot produce null. This is how the existing null-coalescing operator works. The null-coalescing operator also specifies that, in `x ?? y`, the conversion to a non-nullable version of `x` is preferred if it is available. **Conclusion** Let's favor the semantics of assignment. The return type should be the type of `x`. ## Null-coalescing assignment on unconstrained type parameters Null coalescing currently isn't allowed on type parameters, so the proposal currently doesn't allow it either. **Conclusion** This is a hole in null-coalescing as well. Both features should be allowed on unconstrained type parameters. ## Throw expression on the right-hand side Should the following be allowed: `a ??= throw new Exception`? `throw` expressions are only allowed in certain places right now, so we would have to explicitly add support. It makes more sense if the feature is equivalent to `a = a ?? b` where `b` is a `throw`, than `a ?? (a = b)`. **Conclusion** Not supported # Logical boolean compound assignment Also proposed are the features `&&=` and `||=`. Should these features live in the same proposal or be split out on their own? **Conclusion** Split them out. These features are potentially much more complicated because user-defined conversions for `true`, `false`, `&`, and `|` could come into play. # Null-conditional await, foreach `await?` is proposed, which would only await the task-like target if it is non-null and evaluate to null otherwise. A new syntax would be needed here because the result produces a different type. `await` will currently always have the "unwrapped" type from the task-like target, whereas `await?` would need to make that type nullable in the case that it is not already. An analogous feature is proposed for `foreach?`. In favor of the feature, it may be more useful with nullable reference types and more nullable Task-like things. On the other hand, it could lead to more use of nulls in places that some of the design team considers code smells, like widespread use of null Tasks or IEnumerable. **Conclusion** Probably not in C# 8, but maybe later. It may still have value, but probably not as much as other features. # Nullable pointer access expressions The proposal includes `?->` and `?[]`. It purposefully leaves out `*?` as being confusing as a prefix operator. **Conclusion** We don't love it. Not planned for any release. We may consider an external contribution. # Non-nullable reference type feature flag follow-up We followed-up on the feature flag design from [last Wednesday's LDM](LDM-2018-07-11.md). The main concern was making the language semantics affected by a non-language option, namely a compiler flag. It was proposed that the NonNullTypes attribute could effectively cover the same information as the feature flag and the attribute together. Proposal: * `[NonNullTypes(true)]`: Warnings are on and unannotated types are non-nullable * `[NonNullTypes(false)]`: Warnings are on but unannotated types are oblivious * ``: Warnings are off and unannotated types are oblivious The biggest benefit: if the warning opt-in is in source code, the nullable warnings are not a breaking change. It's also noted that this is similar to how CLS compliance already works with the CLSCompliant attribute. One downside is that there is no way to turn the warnings off once there is a `NonNullTypes` attribute anywhere in the code. A solution is to provide a `Warnings` property on the type which lets the user configure warnings. This property would be true by default, but the user could override it e.g., `[NonNullTypes(true, Warnings = false)]`. Also, it's unclear how this would work in environments like scripting. In normal compilations it's easy to find somewhere to put an attribute, but that's not necessarily the case in scripts. **Conclusion** Accepted with some notes: * `[NonNullTypes]` means `[NonNullTypes(true)]` * To prevent upgrading from C# 7 to C# 8 being a breaking change, we should poison the `[NonNullTypes]` attribute using `ObsoleteAttribute` (like `ref` structs), but provide a better message * `?` in a context where there is no attribute or [NonNullTypes(false)] should be a warning since the user is probably misunderstanding how to use the feature. `!` is still useful even if unannotated types are oblivious, so it is only a warning if there is no `NonNullType` present at all ================================================ FILE: meetings/2018/LDM-2018-08-20.md ================================================ # C# Language Design Notes for August 20, 2018 ## Agenda Nullable open issues: 1. Remaining questions on [suppression operator](https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fdotnet%2Froslyn%2Fissues%2F28271&data=02%7C01%7C%7C6defe1e21ab54cce8d0008d606be5d23%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636703812006445395&sdata=DAdh5dev1mnr%2F5zxtvuJVcHP%2Bzewrzz4z9iuGkl%2BUHg%3D&reserved=0) (and possibly cast) 2. Does a dereference update the null-state? 3. Null contract attributes 4. Expanding the feature 5. Is T? where T : class? allowed or meaningful? 6. Typing judgments containing oblivious types 7. Unconstrained T in List\ then `FirstOrDefault()`. What attribute to annotate `FirstOrDefault`? # Discussion # Remaining questions on suppression operator ## 1.1 Suppression of nested nullability *Q: Should `!` suppress warnings for nested nullability?* There's a question here about the interplay of casting and the `!` operator, since they do somewhat similar things. The problem with the current design is that neither casting nor `!` give any warnings/errors at either compile time or runtime for nested nullability. We think it would be useful to have at least one mechanism that provides more safety around nullable. **Conclusion** The user should get a warning if you cast away nested nullability. No warning if you use '!'. This provides a safety mechanism for casts and still allows for an "I Know Better" command, which was the primary motivation for `!`. ## 1.2 Meaningless `!` operators *Q: Should `nonNull!` result in a warning for unnecessary `!`?* **Conclusion** These constructs are meaningless, but unlikely to do something the user didn't want. No compiler warnings for `!!...` or `nonNull!`. Maybe an IDE feature. ## Result of `obliviousValue!` **Conclusion** Top-level non-null, suppressed warnings for nested. # Dereference of nullable types Consider the following example: ```C# string? x = y; var z = x.Substring(1); ... ``` **Conclusion** There's clearly a problem with this example that will generate a warning: `x` is a nullable reference type, but it's being dereferenced without a null check. The first question is: what is the type of `x` after the call? The answer is non-nullable `string`, but the more detailed answer is this results in a split state. The specification recognizes that dereferencing a null value results in a NullReferenceException, so state should be split into exceptional and non-exceptional flow. To spell it out in a more detailed example: ```C# try { string? x = y; var z = x.Substring(1); ... } catch { ... } ``` After the `Substring` call, `x` is non-nullable, but in the catch block, `x` is nullable, as there was a potential NullReferenceException if `x` was null. Additionally, like other flow control warnings, we will only provide a warning about dereference of nullable type once. So if `x` is dereferenced again in the catch block, another warning will not be produced. ## Null contract attributes We've started building a list of attributes that are useful for annotating existing code to note when null or non-null types are produced. For instance, `string.IsNullOrEmpty()` will always be true if the receiver was null. It would be useful to mark this method with an attribute which can indicate this to the compiler, so ```C# if (!x.IsNullOrEmpty()) { ... } ``` should let the compiler know that `x` is not null within the `if` block. *Q: Do we want to do a larger review of all these attributes?* **Conclusion** Let's take all the current attributes and start prototyping. We'll adapt as we move forward. ## Expanding the feature We have two potential extensions here. The first is possibly allowing a `!` annotation on a parameter *name*, which indicates that the compiler should produce a dynamic null check on entry to the method. For instance, a method `void M(string s!) { }` would not only indicate that `s` is meant to be a non-nullable reference type if the nullable reference type feature is fully enabled, it would also insert code at the beginning of the method to throw an `ArgumentNullException` if `null` is passed anyway. The second is how to treat nullable value types. There are two extensions we are considering for nullable value types. The first is about extending the analysis. This is as simple as extending flow analysis to update null state of nullable value types based on information of null checks. For instance, accessing `.Value` on a `Nullable` could produce a warning if the value was accessed without checking for null first. An even more advanced extension would allow `Nullable` values to be accessed without going through `.Value` if the variable is proved to not be null. **Conclusion** Jared's going to write up a proposal on the compiler-inserted dynamic checks. For `Nullable`, we agree with extending the analysis. We're not sure about the silent call of `.Value` or automatic conversion. ## `class?` constraint *Q: Is `void M(T? t) where T : class?` allowed?* **Conclusion** Rule: you can only use `?` on types you know to be non-nullable. `T : class?` is possibly nullable, so you can't use `T?`. ## Typing judgments containing oblivious types Mainly comes down to the type of `x` in `var x = oblivious;`. We need more time for this. Return later. ## Annotating `List.FirstOrDefault()` *Q: Unconstrained T in `List` then `FirstOrDefault()`. What attribute is *used to annotate `FirstOrDefault`?* **Conclusion** [MaybeNull], since `T` is unconstrained and could either be a nullable or non-nullable type. More information may be available after type substitution. ================================================ FILE: meetings/2018/LDM-2018-08-22.md ================================================ # C# Language Design Notes for August 22, 2018 # Agenda 1. Target-typed new 1. Clarification on constraints with nullable reference types enabled # Discussion ## Target-typed new Proposal: https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-new.md *Question: Should target-typed `new` be allowed for tuple types?* We currently don't allow the constructor syntax `new (int, int)(0, 0)`. Should we allow `(int, int) t = new(0, 0)`? Would this mean the same thing as a tuple literal, or a call to a constructor on System.ValueTuple? This would also expose some of the differences between ValueTuple and tuple types, in that there is no constructor for a tuple type with greater than 7 elements. Decision: Let's allow it, as long as that doesn't require a lot of extra work. The meaning would be to call the underlying System.ValueTuple constructors. This would expose differences in tuples with a lot of elements, but this seems like a very rare and unimportant case. *Question: Allow `throw new()`? It would convert to bare `Exception` by the spec.* Decision: Disallow. Fundamentally, we don't like this stylistically. *Question: Allow `new()` with user-defined comparison and arithmetic operators?* Decision: Allow. ## Generic constraints with nullable reference types *Question: In the following snippet, is `U` a non-nullable reference type?* ```C# void M() where T : class, U : T` {} ``` Answer: Yes *Question: In the following snippet, is `I` a non-nullable reference type?* ```C# interface I {} void M() where T : I {} ``` Answer: Yes *Q: Do we want to warn for redundant constraints?* A: We don't currently. Let's stay with that decision for now. ================================================ FILE: meetings/2018/LDM-2018-09-05.md ================================================ # C# Language Design Notes for September 5, 2018 ## Agenda 1. Index operator: is it a unary operator? 1. Compiler intrinsics # Discussion ## Index operator There are multiple questions here: 1. Is the operator syntactically a unary operator? 1. Is it a user-definable operator? 1. Does it have the same precedence as other unary operators? 1. Do members which do not exist implicitly exist anyway as an intrinsic? 1. Does it have overloads? Is `^^1` allowed? That would imply that there's an overload which takes an index. Follow-up: is `..` a binary operator? **Conclusion** Agreed that it's syntactically a unary operator. Also agreed that it is a semantically treated as a unary operator that it is not user-definable. Right now, we don't see a great need to add a second overload. There is a single overload for `int`. We're not strictly defining `..` as a binary operator right now. It has its own syntactic form. Also, we're renaming '^' to the "hat" operator. ## Compiler intrinsics Proposal: https://github.com/dotnet/csharplang/blob/master/proposals/intrinsics.md There is contention between using `void*` and some stronger typing, either a delegate or some kind of function pointer syntax. The benefit of using a pointer type is that it is always unsafe, which this feature requires if there are no allocations (because an appdomain could unload and cause the invocation to point to arbitrary memory). For `calli`, there's a worry about moving the typing from the point of retrieving a function pointer to the declaration of the target and calling convention. The `extern` declaration, specifically, is disliked. Does this only work for managed code or also for native code? Do we have to care about the calling convention? **Conclusion** We'd probably be willing to accept this in some form. We think the current proposal needs some work. Some thoughts/suggestions: 1. Don't allow instance `&receiver.M` form -- only types for instance methods e.g., `&TypeName.M`. 1. Drop the separate declaration for the `calli` invocation. A special calling form like `_calli_stdcall(args, f)` is suggested. We don't like signature being declared somewhere other than the method definition or at the call site. 1. Would like the calling convention, if used, present at the call site. ================================================ FILE: meetings/2018/LDM-2018-09-10.md ================================================ # C# Language Design Notes for September 10, 2018 ## Agenda 1. Nullability of constraints in inheritance and interface implementation 2. Static local functions # Discussion QOTD: *Don't open your mind so much the language falls out* ## Nullability of constraints in overriding In C# we don't allow override methods to re-specify generic constraints. The question then is how to treat constraints that are inherited from a base method with a different `NonNullTypes` attribute. For example: ```C# // Assembly 1 [NonNullTypes(true)] class Base { virtual void M() where T : C? { } } --- // Assembly 2 [NonNullTypes(false)] class Derived : Base { override void M() { } } ``` Does using `T` as a non-null type produce a warning, even though we've said `[NonNullTypes(false)]` in the derived class? **Conclusion** When the base context is `true` and the derived is `false`, suppress related warnings. When the base is false and the derived is true, treat unannotated constraints as oblivious. ### Inheritance and explicit interface implementation Similar to the above case, there's a question about how "sticky" nullability is through interface implementation and inheritance. For instance, the following class uses a constructed inherited member to explicitly implement an interface. Does nullability on the type parameters have to match? Here is the correct implementation: ```C# interface I1 { void M() where U : T } class C : I1 { void I1.M() {} } ``` And what if you remove the question mark in the explicit implementation? ```C# class C : I1 { void I1.M() {} } ``` **Conclusion** If the member and the implementing type share the same `NonNullTypes` context, the annotations must match, so the example directly above that mismatches `C?` with `C` would be an error stating that the class `C` does not implement the interface `I1`. If the `NonNullTypes` context differs and the context on the outside is `false` and the inside is `true`, there is no error or warning about the mismatch, but a warning that `?` is disallowed in `NonNullTypes(false)` context. If the context on the inside is `false` and the `outside` is true, there is also no error or warning, this time because the interior context causes `C` to be treated as oblivious, which can match with `C?`. ## Static local functions Proposal: https://github.com/dotnet/csharplang/issues/1565 We like this proposal. One potentially confusing part is that static local functions cannot capture local variables, even in containing static methods. This is appropriate for the design, and we intend to keep it, but acknowledge that it may be confusing at first. **Q & A** *Q:* If the proposal is accepted we would allow two states to be enforced: all capturing is allowed and no capturing is allowed. Do we want to allow intermediate states with something like capture lists? **A:** No, this is as far as we go. *Q:* Should we allow attributes too? That could be useful for related scenarios, like P/Invoke. **A:** Yes, we liked the idea originally, but it didn't make C# 7.0. We'd like to finalize support for this. *Q:* Should we relax shadowing rules? This isn't strictly related to the proposal, but it seems like the restriction is more draconian with static local functions because you cannot capture variables and instead have to come up with new names if they are being passed as arguments. **A:** We dislike differing shadowing behavior between static and non-static local functions. We're warm to the idea of allowing all local function parameters shadow locals in parent scopes. We're also interested in allowing shadowing in lambdas. We would like to see a separate proposal on this to document the details. *Q:* Can static local functions capture type parameters? **A:** Yes. *Q:* Do we want to allow "static lambdas"? **A:** The value seems much lower since lambdas are usually much shorter. It's also a more intrusive syntax inline to the call. Rejected. ================================================ FILE: meetings/2018/LDM-2018-09-19.md ================================================ # C# Language Design Notes for September 19, 2018 ## Agenda Triage: 1. XML doc comment features 2. New `foreach` pattern using `Length` and indexer 3. Object initializers for `readonly` members 4. `readonly` struct methods 5. `params Span` 6. Nullable reference type features on `Nullable` ## Discussion ### XML Doc and related features Proposals: - https://github.com/dotnet/csharplang/issues/313 - https://github.com/dotnet/csharplang/issues/1766 - https://github.com/dotnet/csharplang/issues/1767 - https://github.com/dotnet/csharplang/issues/1768 - https://github.com/dotnet/csharplang/issues/315 - https://github.com/dotnet/csharplang/issues/320 - https://github.com/dotnet/csharplang/issues/401 - https://github.com/dotnet/csharplang/issues/1764 - https://github.com/dotnet/csharplang/issues/1765 We're not very excited about designing features in doc comments, primarily because a lot of design work depends more on tooling ecosystem than language design. It seems like there's a lot of coordination that should happen outside LDM before we look at it inside LDM. Most of the features aren't useful without a near-term guarantee of tooling support. **Conclusion** We need the tooling and ecosystem designers and owners to weigh in before we're ready to move forward. Specifically, we need a fully fleshed out, detailed proposal for exactly what the total work is and a timeline for completion. Once that's in place, we should consider all the XML doc comment changes at once. We're moving this into an `X.0` release, since we think the tooling changes merit a major release and we are blocked on external work. ### New `foreach` pattern using `Length` and indexer Proposal: https://github.com/dotnet/csharplang/issues/1424 There are a bunch of options around signaling support for this feature without incurring a breaking change, including using an attribute, using a new interface, considering a new pattern (including the ref/ref-readonly variants). `foreach` already has a lot of different variants, so any change has to fully account for all the possibilities. It's also notable that we've seen LINQ performance as a concern for quite a while and we haven't ruled out doing something for that. If we add a new interface for LINQ we might want to use the same interface as a trigger for new optimizations here. We almost certainly wouldn't want to create a duplicate interface. **Conclusion** We don't like this specific proposal, but will continue to look at features in this area. ### Readonly object initializers Proposal: https://github.com/dotnet/csharplang/issues/1684 **Conclusion** A lot of crossover with records proposals here. This seems like more of a special case -- let's see if we can make a generalization work before we go this route. ### `readonly` functions on structs Proposal: https://github.com/dotnet/csharplang/issues/1710 First concern is ordering restrictions of the "readonly" modifier. It can also indicate a "readonly ref" return type, so we need to make sure it's not ambiguous. There's also another option: do we allow explicit receivers so the user could specify `void M(in this S s)` and explicitly declare the variables which are readonly? **Conclusion** We're interested. Let's keep the design as-is for now. ### `params Span` Proposal: https://github.com/dotnet/csharplang/issues/1757 This would be especially interesting if the CLR implements a new feature for stack allocating an array of reference types. **Conclusion** Let's keep it in C# 8. A lot of stuff to work out here. ### Add nullable reference type features to nullable value types Proposal: https://github.com/dotnet/csharplang/issues/1865 Let's look at this for 8.0 if only to make sure doing it later is not a breaking change. ================================================ FILE: meetings/2018/LDM-2018-09-24.md ================================================ # C# Language Design Notes for September 24, 2018 ## Agenda F#/C# combined LDM Two primary goals: 1. What new C# features are going to require work to get F# support? 2. How can the design of C# features play well with the broader .NET environment? ## Discussion First, which proposed C# 8.0 features may need interop work in F#? - [**Nullable reference types**](https://github.com/dotnet/csharplang/issues/36) - This is by far the top priority - F# will need to recognize C# nullability attributes and possibly emit them itself - [`params Span`](https://github.com/dotnet/csharplang/issues/1757) - Consumption is probably more important than production - Need to make sure F# compiler doesn't choke on a ParamArrayAttribute on a type other than array - [Slicing/Range](https://github.com/dotnet/csharplang/issues/185) - There should probably be a way to convert from F# to C# range/index - [Records](https://github.com/dotnet/csharplang/issues/39) - Significant work if this actually makes it into C# 8.0 - [UTF8 string literals](https://github.com/dotnet/csharplang/issues/184) - Probably more work in language parity, specifically on F# usage of the UTF8 string type itself - [Default interface methods](https://github.com/dotnet/csharplang/issues/52) - As currently spec'd, F# should at least be able to implement interfaces with default implementations - Open question as to how much consumption or production will be supported in F# if C# goes ahead with the current design - [Pattern-based using](https://github.com/dotnet/csharplang/issues/1174) - We think F# already supports this. No work to do here - [Async streams (IAsyncEnumerable)](https://github.com/dotnet/csharplang/issues/43) - Probably need a helper method to convert from `IAsyncEnumerable` to F# AsyncSeq, but since `Task` doesn't yet have first-class support, this is not the highest priority - [Native-sized integers](https://github.com/dotnet/csharplang/issues/435) - Possibly some codegen support for whatever we do here - [Readonly members on structs](https://github.com/dotnet/csharplang/issues/1710) - Probably no work on the consumption side, but work would be required if F# wanted to allow declaring them Second, how should interop with other .NET languages impact C# language design? ### Nullable reference types This is both the biggest item and also the most likely to ship in C# 8.0. Right now, there's no mechanism for other types to be treated with the same nullability rules provided via the C# nullable reference type feature. Specifically, there's the question of how F#'s `Option` type will inter-operate with C# nullable reference types. At the moment we don't have any design to let arbitrary types to be treated as "nullable" wrappers, but we do have a proposal to allow the `Nullable` type to opt-in to nullable reference type behaviors (https://github.com/dotnet/csharplang/issues/1865). We're considering broadening this to other nullable wrapper types. This may also affect our future discriminated union design. The other question is if we want to explore a different way of representing nullable reference types in the CLR. Right now the proposal is to do this entirely using attributes and not treat these as different types in C#. If we did want to treat them as different types, CLR work should be considered. ### Default implementations in interfaces We went very far down one specific design strategy (traits). Let's come up for air and see if we want to explore any other points in the design space. We should also take into account our proposals around type classes (https://github.com/dotnet/csharplang/issues/110). ### Discriminated unions It's not on the list for C# 8.0, but when we get around to it we should explore code generation strategies with the CLR to see how we could get the most efficient type switching implementation. F# also notes that they don't encourage using discriminated unions in public APIs due to the risk of breaking changes from exhaustiveness checking. We should consider whether we want the same restriction or provide some support for versioned discriminated unions. ================================================ FILE: meetings/2018/LDM-2018-09-26.md ================================================ # C# Language Design Notes for September 26, 2018 ## Agenda Warning waves ## Discussion Motivation: Right now we consider adding a new warning to existing code to be a breaking change. We want a way to add diagnostics without it being a breaking change. Solution: Some mechanism to opt-in to breaks ("warning waves"). For warning project levels, we consider "latest" to be the most common setting aside from "none". Most people who want all possible warnings will target "latest", and will only choose an earlier warning level number when the get a new warning that they can't fix at the moment for some reason. Negatives: Seemingly unrelated warnings are grouped into a bundle that may be confusing. However, enabling individual warnings seems both a pain for users to do every update, and very difficult for discovery (where do users find the list of new warnings?) When you haven't enabled the warning wave, we still want the diagnostics to appear, but we want them to be suppressed such that the IDE and tooling can see them, but the user does not (normally). Exactly what type of suppression we use is to be solved. ### Q & A One problem we'd like to address with warning waves is definite assignment of structs with private members where, due to various compiler bugs, the struct appears to be definitely assigned when it is not. *Q: Definite assignment diagnostics are always errors right now -- should we allow "error waves" as well?* **A:** No, for now. Let's see if we can provide warnings for some of these issues without producing errors. *Q: Should warning waves be per-SyntaxTree or per-Compilation?* **A:** Per-Compilation for now. The SyntaxTree level may be an interesting feature but there are no scenarios compelling enough right now that merit the additional complexity. *Q: Is the command line option case sensitive or case insensitive?* **A:** Case-insensitive. *Q: How does the IDE get access to info on the warning waves?* **A:** The compiler should provide an API to get a list of all warning IDs in a particular wave. The IDE should also pull from the compiler resources to find the description for each particular warning. The compiler also needs to know which warning versions are valid. We also need an API for getting which warning wave a particular warning is part of, if any. Warnings in new language features are never part of a warning wave. *Q: What's the bar for adding a warning to a warning wave? Is it the same as whether or not we would have created a diagnostic if we were doing the feature for the first time now?* **Conclusion** No. This will still cause users to do extra work, so we need to make sure that each warning we're adding is worth the change. We should also consider how much work will go into a particular warning wave and avoid overwhelming the user. Based on feedback we can dial the severity back and forth. However, we should introduce high-impact changes as soon as possible to make it clear what kind of breaks users are signing up for. *Q: Do we want a "major" flag for upgrading language version only with major version?* **Conclusion** No. *Q: Which warnings do we put in?* **Conclusion** Let's do prioritization later, but don't spend too much time deciding exactly which one. In general, the system for adding warnings should go through LDM. *Q: What are the version numbers?* **Conclusion** We aren't sure yet. "7.3.1"? "8.0.0"? Jared proposes Git SHA. **Notes:** 1. `csc /warnversion:` with no warnversion could list all the possible warning versions with all of the diagnostic ids in each warnversion and the title of that warning. 2. No warning waves is `/warnversion:none`. All warnings is `/warnversion:all`. ================================================ FILE: meetings/2018/LDM-2018-10-01.md ================================================ # C# Language Design Notes for Oct 1, 2018 ## Agenda 1. Nullable type inference 2. Type parameters and nullability context # Nullable type inference Generic type inference is also used to determine "best common type", e.g. in anonymously typed arrays, finding the inferred return type of lambdas with multiple returns, etc. Type inference roughly proceeds as follows: 1. Gather up all the types of the constituent expressions (some, e.g. `null`, may not have a type to contribute) 2. Among those, gather the ones to which all the constituent expressions have an appropriate implicit conversion 3. If these are all identity convertible to each other, construct the result from them This third step is a little cryptic. Most of the time, when types are identity convertible to each other, they are the *same* type. But there are two exceptions in the language today: - `object` and `dynamic` are identity convertible to each other - Tuple types with pairwise identity convertible element types are identity convertible, regardless of element names In step 3 above, in places where the identity convertible types differ by `object` vs `dynamic`, choose dynamic. Where they differ by tuple element names, have the tuple element be unnamed. This is all relevant to nullable reference types, because we are about to introduce a third way in which non-identical types can be identity convertible to each other: - Identity conversion between reference types is determined without regard to their nullability (though if the conversion is performed, it may lead to a warning) This means we need to say how to construct the result of type inference with regard to nullability of reference types. Where the identity convertible types differ by nullability, we'll determine the nullability based on the variance of the type's position: - **Covariant**: A type is in a covariant position if it is - a top level type, - a type argument to a covariant type parameter of a generic type in a covariant position - a type argument to a contravariant type parameter if a generic type in a contravariant position - **Contravariant**: A type is in a contravariant position if it is: - a type argument to a contravariant type parameter of a generic type in a covariant position - a type argument to a covariant type parameter if a generic type in a contravariant position - **Invariant**: A type is in an invariant position if it is: - a type argument to an invariant type parameter - a type argument to a type parameter of a generic type in an invariant position For a given type position in the result type, we'll always pick among the nullabilities present in that position, with one exception. - In a covariant position pick nullable if present, otherwise oblivious if present, otherwise nonnullable - In a contravariant position pick nonnullable if present, otherwise oblivious if present, otherwise nullable - In an invariant position: - if both nullable and nonnullable are present, then yield a warning and pick oblivious (this is the one exception) - otherwise if either nullable or nonnullable is present, pick that one - otherwise pick oblivious This leads to nice and symmetric rules, where nullable and nonnullable are treated equally, and oblivious isn't too infectious. As far as we can tell, the rules are associative and can be expressed in a pairwise manner, without causing order dependence. If oblivious had dominated nullable and nonnullable in the invariant case, that would have thwarted associativity. The one thing that's a little odd is where nullable and nonnullable clash in an invariant position. Ideally this would lead to an error, but we only want to allow nullability to lead to warnings, not errors, so we need to have some answer for what comes out. Oblivious seems the right choice, in that we've already warned that something is wrong, and it will lead to suppression of further warnings caused by that. What's odd about it is that oblivious normally comes from legacy code that's explicitly opted out of the nullability feature. This is the only place where it can occur in new code that is all "opted in". # Type parameters declared and used in different nullability contexts Allowing a more granular in-file choice between nullability contexts (whether nullable annotations are on or off) leads to new kinds of situations. For instance, a type parameter can be declared in an "off" context (oblivious to nullability) but used in an "on" context. This is the topic of [Roslyn issue 30214](https://github.com/dotnet/roslyn/issues/30214). The context where the type parameter is declared determines whether it should be sensitive to the nullability implications of its constraints. In the example in the issue, the type parameters are oblivious, and should not lead to nullability diagnostics, because they are declared in a "legacy" context. ================================================ FILE: meetings/2018/LDM-2018-10-03.md ================================================ # C# Language Design Notes for Oct 3, 2018 ## Agenda 1. How is the nullable context expressed? 2. Async streams - which interface shape? # Nullability context In order to accommodate "null-oblivious" legacy source code while it is under transition, we want to allow regional changes to the context for how nullability is handled. There are actually two interesting "nullability contexts": 1. Annotation context: should an unannotated type reference in the context be considered nonnullable or oblivious? 2. Warning context: If null annotations are violated within the context, should a warning be given? We have learned the hard way that we cannot use regular attributes due to circularities in binding. Essentially, modifying semantics with attributes is a bad idea not just from a "moral" perspective, but, as it turns out, a technical one: you need to do binding to understand the attributes. If the attributes themselves affect binding (as these would), well hmmm. This is causing us to rethink the context switching experience. We have three general candidate ideas for describing regional changes to the nullability context: 1. A new modifier 2. A "fake" or pseudo-attribute 3. Compiler directives We don't have even a strawman-level idea for good modifier keywords, so we are going to drop that one from the discussion. For the two others, there are strawman proposals below for the purposes of discussion. ## Pseudo-attributes The idea is to keep the currently implemented attribute-based user experience, but discover the attributes specially, in an earlier phase of the compiler, rather than through normal binding (where it is too late). Nullable annotations and warnings are controlled by the same attribute, `[NonNullTypes]`. It has a positional boolean parameter that controls annotations, and defaults to true. It has an additional named boolean parameter that controls warnings and defaults to true. The attributes override each other hierarchically, and can be applied at the module, type and member levels at least. Here is an example where nullable annotations are turned off for some members, and warnings are turned off for another member: ``` c# [module:NonNullTypes] public class Dictionary : ICollection>, IEnumerable>, IEnumerable, IDictionary, IReadOnlyCollection>, IReadOnlyDictionary, ICollection, IDictionary, IDeserializationCallback, ISerializable { public Dictionary() { } public Dictionary(IDictionary dictionary) { } public Dictionary(IEnumerable> collection) { } public Dictionary(IEqualityComparer? comparer) { } [NonNullTypes(false)] public Dictionary(int capacity) { } [NonNullTypes(false)] public Dictionary(IDictionary dictionary, IEqualityComparer comparer) { } [NonNullTypes(false)] public Dictionary(IEnumerable> collection, IEqualityComparer comparer) { } public Dictionary(int capacity, IEqualityComparer? comparer) { } [NonNullTypes(warn = false)] protected Dictionary(SerializationInfo info, StreamingContext context) { } } ``` Some consequences of the attribute being "fake": - argument expressions would have to be literals - there's generally a trade off between expressiveness and effort - maybe we wouldn't even be able to allow the named parameter - What are implications to the semantic model in the Roslyn API? Should you be able to tell the difference? ## Directives The idea is to control nullable annotations and warnings as separate `#`-prefixed compiler directives that apply lexically to all source code until undone by another directive: - `#pragma warning disable null` and `#pragma warning restore null` for turning warnings off and on (simply using the `null` keyword as a special diagnostic name for the existing feature) - `#nonnull disable` and `#nonnull restore` for turning nullable annotations off and on (reusing the `disable` and `restore` keywords from pragma warnings) Here is the same example as above, using the directives approach: ``` c# public class Dictionary : ICollection>, IEnumerable>, IEnumerable, IDictionary, IReadOnlyCollection>, IReadOnlyDictionary, ICollection, IDictionary, IDeserializationCallback, ISerializable { public Dictionary() { } public Dictionary(IDictionary dictionary) { } public Dictionary(IEnumerable> collection) { } public Dictionary(IEqualityComparer? comparer) { } #nonnull disable public Dictionary(int capacity) { } public Dictionary(IDictionary dictionary, IEqualityComparer comparer) { } public Dictionary(IEnumerable> collection, IEqualityComparer comparer) { } #nonnull restore public Dictionary(int capacity, IEqualityComparer? comparer) { } #pragma warning disable null protected Dictionary(SerializationInfo info, StreamingContext context) { } #pragma warning restore null } ``` A consequence: - Might require a compiler switch for the global opt-in - which we were happy to get rid of when we first adopted the attribute approach. ## Discussion The purpose of the feature is to toggle *contextual information* for a region of code: a) whether unannotated type references in the region should be interpreted as nonnullable, and b) whether warnings should be yielded for violations of nullable intent within the region. It is uncommon for attributes to affect context. Modifiers sometimes do (unsafe, checked/unchecked), and directives sometimes do (#pragma warning). Attributes usually directly affect the entity to which they are attached, rather than elements (declarations, expressions, etc.) within it. It is also uncommon - and usually considered undesirable - for attributes to affect semantics. In fact, it is affecting semantics that is causing the problem in the first place, because attributes also *depend* on semantics. Syntactically, directives stand out from the syntax, and generally indent to the left margin. They are *about* the source code, not part of it. Attributes are enmeshed with the code itself, and stand out less. Which signal do we want to send? "The rules of the game have changed in this region of source code" vs "This class or member is special with regards to nullability"? Attributes are syntactically limited in their *granularity* – they can only apply to certain nodes in the syntax tree. Directives are free to appear between any two tokens (as long as there are line breaks between them), including at both a smaller scale (in between statements and expressions) and a larger scale (around multiple types or even namespaces) than attributes. They can also modify top-level non-metadata constructs such as using aliases. Wherever we *infer* obliviousness for a specific declaration, directives would let you make that same declaration explicitly (albeit awkwardly), whereas attributes would generally be unable to. On the other hand, in practice your desired granularity would often be at the member or type level, where attributes would do just fine. The ability of directives to turn off and then on *at different syntactic nesting levels* is just weird, and hardly useful. Maybe the natural prevention attributes provide against such anomalies is a good thing. Then again, other directives already have this ability, and that doesn’t seem to cause trouble in practice. Inside of method bodies, change of the warning context seems more likely than the annotation context. Attributes are the means by which we would have to encode the context in metadata regardless. So using attributes in syntax would be more direct than having to come up with a scheme for generating attributes from weirdly placed directives. While changing context around a using alias or in the middle of a member body wouldn’t need to have direct metadata effects, the same is not the case for a context change inside, say, a constraints clause. We would need to either invent an encoding scheme, or error on such places. Directives would more likely allow editor-config integration. Attributes would maybe complicate the semantic model. Finally, "pseudo-attributes", recognized specially by the compiler, are a new concept to the language. Is it worth it? In practice, though, the seams are mostly not going to show: a user won’t generally need to worry that it’s not a regular attribute. Conversely, `#nonnull` would be a new directive: Is it worth it? ## Conclusion Given all this, we are in favor of the directive approach. We will start from the strawman and refine over the coming weeks. # Async streams The idea behind async streams is to make them analogous to synchronous `IEnumerable`s, allowing them to be consumed by `foreach` in asynchronous methods, and produced by `yield return`ing asynchronous iterator methods. The natural shape of the `IAsyncEnumerable` interface is therefore simply a straightforwardly "async'ified" version of `IEnumerable`, like this: ``` c# public interface IAsyncEnumerable { IAsyncEnumerator GetAsyncEnumerator(); } public interface IAsyncEnumerator : IAsyncDisposable { ValueTask MoveNextAsync(); T Current { get; } } public interface IAsyncDisposable { ValueTask DisposeAsync(); } ``` This works well. The main difference (other than `Async` occurring in names) is that `MoveNextAsync` is asynchronous, so that you need to await it to learn if there is a next value, and before `Current` can be assumed to contain that value. Just as the semantics of `foreach` can be described (and *is* described in the language spec) as an expansion to a while loop, `foreach await` is an almost identical expansion, except with some `await`ing going on. It is also generally quite efficient. The use of `ValueTask` instead of `Task` allows implementers (including the ones produced from iterators by the compiler) to avoid any allocations during iteration time, storing any state needed to track the asynchrony alongside the iteration state in the enumerator object that implements `IAsyncEnumerator` (which can often in turn be shared with the implementation of `IAsyncEnumerable` itself). Thus, the number of allocations needed to iterate an `IAsyncEnumerable` will typically be zero, and occasionally (in the case of concurrent iterations) one (for a second enumerator that can't be shared with the enumerable). One problem remains, performance-wise, and it is shared with the original synchronous `IEnumerable`: It requires *two* interface calls per loop iteration, whereas with clever tricks that could be brought down to "usually one" in cases where the next value is most often available synchronously. The best `IAsyncEnumerator` design we can come up with that satisfies those properties is the following: ``` c# public interface IAsyncEnumerator : IAsyncDisposable { ValueTask WaitForNextAsync(); T TryGetNext(out bool success); } ``` Here the `TryGetNext` method can be used to loop synchronously through all the readily available values. Only when it yields `false` do we need to fall back to an outer loop that awaits calls to `WaitForNextAsync()` until more data is available. In the degenerate case where `TryGetNext` always yields `false`, this is not faster: you fall back to the outer loop and always have two interface calls. However, the more often `TryGetNext` yields true, the more often we can stay in the inner loop and skip an interface call. The interface has several drawbacks, though: - It is meaningfully different from the synchronous `IAsyncEnumerator` - `TryGetNext` is unidiomatic, in that it switches its success result and its value result, in order to allow the type parameter to be covariant, making it annoying to manually consume - The meaning of the two methods is less intuitively clear - The double loop consumption is more complicated It's a dilemma between simple and fast. The performance benefit of the fast version can be up to 2x, when most elements are available synchronously, and the work in the loop is small enough to be dominated by the interface calls. But it really is a lot harder to use manually. While that doesn't matter when you use `foreach` and iterators, there are still enough residual scenarios where you really need to produce or consume the interface directly. An example is the implementation of `Zip`. There are ways to have the two approaches coexist. If we support both of them statically, we'd end up exploding e.g. async LINQ with vast amounts of surface area. That'd be terrible and confusing. But there's also a dynamic approach. Already today, many LINQ query operators are implemented to do type checks on their inputs to see if they support a faster implementation. For instance, `Count` on an array can just access the `Length` instead of iterating to count the elements. Similarly we could have a fast async enumerator interface that generally lives under the hood. But implementations of e.g. LINQ methods can type check for the fast interface and use it if applicable. The code generated for foreach could even do that too, though that probably leads to complicated code. We can also generate iterators that implement both interfaces. ## Conclusion We want to stick with the simple `IEnumerator`-like interface in general. We'll keep the "fast under the hood" option in our back pocket and decide after the first preview whether to apply that, but the surface area that people see and trade in should be the simple, straightforward "port" of `IEnumerator` to "async space". ================================================ FILE: meetings/2018/LDM-2018-10-10.md ================================================ # C# Language Design Notes for Oct 10, 2018 _QOTD: C# has a long and proud tradition of being oblivious_ ## Agenda 1. Pattern matching open questions ## Discussion All questions are in [this issue](https://github.com/dotnet/csharplang/issues/1054) ### Short discard diagnostics **Conclusion** We've taken breaking changes like this before, but there's more risk to this one because this code is legal all the way back to C# 1.0. This will not be an error -- an underscore in a case block with a constant `_` in scope will match the constant. A warning wave warning should be added to make matching a constant named `_` a warning. ### Nullable reference types vs switch analysis In general, the nullable analysis computes very similar information to switch exhaustiveness. It would be strange if the switch produced information contrary to the nullable analysis. There are some fundamental language semantic problems with making switch (and pattern) exhaustiveness and nullable analysis depend on each other. One possibility may be to perform the analysis for both of these situations simultaneously. We don't think that the exhaustiveness analysis doesn't affect the nullable analysis directly. Alternatively, we can phrase exhaustiveness as exclusive of nullable reference types and let the null analysis handle switch exhaustiveness for null specifically. Nullable value types would be handled as part of traditional exhaustiveness. **Conclusion** Let's explore the separation of exhaustiveness between null and non-null, st. all exhaustiveness warnings do not consider null, and warnings related to null are delayed until nullable analysis. Also, regardless of whether or not we generate a warning for the following case, `i` is not definitely assigned at the end: ```C# int i; switch (s) { case string t: i = 0; break; } Console.WriteLine(i); // is i definitely assigned? ``` ### Permit optionally omitting the pattern on the last branch of switch expr **Conclusion** Rejected. ### Should exhaustiveness affect definite assignment? **Conclusion** Confirmed the current behavior. ### Switch expression as statement expression **Conclusion** We like it. Not sure it will make it for C# 8.0. ### Single-element positional deconstruction **Conclusion** We need to think about 1-tuples again. ================================================ FILE: meetings/2018/LDM-2018-10-15.md ================================================ # C# Language Design Notes for Oct 15, 2018 ## Agenda 1. [Function pointers](https://github.com/dotnet/csharplang/blob/master/proposals/function-pointers.md) 2. [Readonly struct members](https://github.com/dotnet/csharplang/issues/1710) 3. Syntax for async streams ## Discussion ### Function pointers We're leaning towards no names. The encoding and scoping of the naming seems like it could be a problem. Not having names is not a new problem and it does not seem worse than far more common things like tuples. We many consider a richer augmentation of aliasing in general, which would help not only function pointers, but all the places where you may want to alias an existing type. Syntax: we played around with multiple funcptr type syntaxes. We haven't settled on a syntax yet, but we have been convinced by Jared that the `Func*` syntax is unworkable. If we later introduce a new structural function pointer type and syntax, we could evolve the current syntax to support the new syntax. *Q: Do we want the '&' when converting a method group to a `funcptr`?* One argument is that it's extra syntax at the place where the feature is least unsafe (at the declaration), instead of the place where's it's most unsafe (at the invocation). However, it is similar to syntax for other pointer types in the language and "feels right" when you see it. Let's keep it. **Conclusion** We like this approach a lot more than the previous attempt. The main item left is to make sure the syntax is unambiguous. ### Readonly struct members We discussed this feature back when we added "readonly" for struct definitions and decided we might consider it later. It is now later. Looking back on the language, we feel the silent copying of structs to avoid mutation is a bit of a wart. One thing stopping us from providing a warning is the lack of this feature, since sometimes you cannot mark a struct readonly just to avoid the copy. This feature would fill out the space enough that we could consider a warning wave or an analyzer to warn about silent struct copies. Examined a few syntaxes for this: - An attribute ([ReadonlyMethod]?) - `readonly` at the end - `readonly` at the beginning - Allow the explicit `this` parameter and let it be passed by `in` **Conclusion** We like the feature and think the syntax should be a `readonly` modifier at the beginning of the method, along with the other modifiers. ### Syntax of `foreach await`/`using await` `foreach await (...)` or `foreach async (...)` or `async foreach (...)`? In the past we've followed the pattern that there's always an explicit `await` in the code if we're awaiting. There's some debate on what sounds best. **Conclusion** `await foreach (...)` and `await using (...)`. ================================================ FILE: meetings/2018/LDM-2018-10-17.md ================================================ # C# Language Design Notes for Oct 17, 2018 ## Agenda 1. Open issues with default interface methods 2. Target typed new with default constructors on structs ## Discussion ### Issues in default interface methods Link: https://github.com/dotnet/csharplang/issues/406 #### Diamond inheritance We've come down to two options: pick an implementation somewhat arbitrarily, or produce a runtime exception. The positive about picking one is that we more strongly provide the guarantee that adding a default method implementation will not "break" the application. The minus is that there's no guarantee that the method we actually picked is the correct one. It's also noted that adding a virtual method to a base class can implement an interface in the derived class, but you can get the same result just through current language behavior: ```C# interface IFoo { void M() { impl; } } class Derived : Base, IFoo {} class Base : Base0 { public virtual void M() {} // added later } class Base0 : IFoo { void IFoo.M() { } } ``` **Conclusion** We're going to throw a runtime exception (we want a new exception type) when there is a runtime diamond and there is no unique most derived implementation. We've decided that the exception will be thrown at method resolution time, so the exception would be thrown when taking a delegate to the target method, rather than when the delegate is invoked. We should note in documentation when this kind of change could be a source or binary breaking change. We'd like to have some method to provide static verification, as long as we can provide a tool that has access to the runtime environment (like the IL linker). #### Permit partial in interface? Partial methods, in addition to the partial type? **Conclusion** Yes, with the same restrictions as in classes. #### `Main` in an interface? No reason why not. #### Confirm that we support public non-virtual methods Yes, we'll support it. #### Does an `override` in an interface introduce a new member? No override keyword in interfaces. This should resolve all listed questions. #### Properties with a private accessor We say that private members are not virtual, and the combination of virtual and private is disallowed. But what about a property with a private accessor? ``` c# interface IA { public virtual int P { get => 3; private set => { } } } ``` Is this allowed? Is the `set` accessor here `virtual` or not? Can it be overridden where it is accessible? Does the following implicitly implement only the `get` accessor? ``` c# class C : IA { public int P { get => 4; set { } } } ``` Is the following presumably an error because IA.P.set isn't virtual and also because it isn't accessible? ``` c# class C : IA { int IA.P { get => 4; set { } } } ``` The first example looks valid, while the last does not. This is resolved analogously to how it already works in C#. #### Warning for struct not implementing default method? This seems like something more suited for an analyzer. It also seems like this warning could be noisy, since it would fire even if the default interface method is never called and no boxing will ever occur. #### When are interface static constructors run? On the desktop CLR, static methods on interfaces currently run the static constructor on entry if it has not already been run. It's proposed that we adopt the same behavior for instance methods. **Conclusion** Static constructors are also run on entry to instance methods, if the static constructor was not `beforefieldinit`, in which case static constructors are run before access to the first static field. ### Target-typed new() Can you use new() on anything that has a valid constructor? Like `int`? Or a tuple? **Conclusion** Yes. ================================================ FILE: meetings/2018/LDM-2018-10-22.md ================================================ # C# Language Design Notes for Oct 22, 2018 ## Agenda Discuss two records proposals: 1. [The existing old one](https://github.com/dotnet/csharplang/blob/master/proposals/records.md) 2. [The data classes proposal](https://github.com/dotnet/csharplang/pull/1667) ## Discussion ### "Nominal" records. Nominal is more resilient to reordering or adding members. Part of the motivation here is the experience from CompilationOptions in Roslyn, which we accidentally broke a couple of times. The classes that nominal records would replace are often mutable, just so that object initializers can work. Nominal are proposed to facilitate object initializers even for immutable ones. The trick for allowing that is a pattern that uses an underlying builder struct. The proposal would emit errors or warnings on object initializers that neglect to initialize properties which aren't declared with an initializer. We think that object initializers are not just the result of "lazy" API design, but an initialization pattern that folks actually prefer. With many data elements it certainly has a better tooling experience. The presence of an accessible constructor with a (well recognized) builder at the end would be what allows an object initializer to work. It's a bit similar to params. For inheritance, you would make a builder per layer of inheritance, which gets a little ugly. It might be an argument for making them unspeakable. The proposal for builders being "striped" also builds the structure of the inheritance hierarchy into the public surface area, so that changing the hierarchy would be breaking under the hood. All alternatives that we can think of also have pretty horrendous downsides. #### Fields The proposal translates them into ref-returning getter-only properties, and sticks them in the builder. This would break reflection compared to today. VB doesn't have ref returns, so it's interesting how it deals with them when it encounters them. We need to make sure that this wouldn't degrade the VB experience. - [ ] Open Issue: does it break anything else? #### Equality Mutable data members in classes should generally not participate in equality and hash code by default. Otherwise, there's the risk that the item gets added to a hash table, then its hash code value changes when one of the members gets mutated. *Q: does this feature buy us enough to be worth the cost?* Maybe not just for compat, because most people won't rewrite old code the new way. It's the value to new code that's most important. That said, this is an important opportunity for cleanup that would help people not accidentally break themselves or their customers when they evolve. ### Positional records Should we reserve the `class C(X x, Y y, Z z)` syntax for primary constructors? First idea is that `data` as a modifier is necessary to make it a positional record. But then adding data changes the meaning of the parameter list quite radically. ### "Positional" vs. "Nominal" The positional proposal is syntactic sugar for something that people to a larger degree do today. The nominal is maybe more something that they wish they could do, but don't have the expressiveness to do yet. The positional proposal produces With methods that are so nice it might not even be worth it to put a with expression syntax on top. For the nominal proposal, because of the builders, you really want a syntax on top, or it gets unsightly. Summary of benefits of nominal over positional records: - Object initializer syntax (could possibly be added to positional) - Binary compat from version to version when you move to records (positional can't) - Source compat **Conclusion** There are many components of the proposals that don't quite yet feel like they click together, but we should keep working on that. ### Discriminated unions: The record syntax will probably affect discriminated union design. That doesn't mean we shouldn't keep exploring records. Discriminated unions will also probably face the public/private API dichotomy being explored in records: if you ever want to add more cases, you break every consumer's exhaustiveness check. ================================================ FILE: meetings/2018/LDM-2018-10-24.md ================================================ # C# Language Design Notes for Oct 24, 2018 ## Agenda 1. [Adding Nullable Reference Type features to Nullable Value Types](https://github.com/dotnet/csharplang/issues/1865) 2. Open issues with pattern matching ## Discussion ### Tracking null state for `Nullable` Note that the proposed null tracking would not warn on `GetValueOrDefault`, which is legal on null values. *Q: Do we want to prohibit `!` on assigning to a `Nullable`?* `Nullable` is fully type-safe and the analysis is precise. The only cases we can't prove are complicated uses/checks that the compiler can't prove. If we force the user to rewrite their code such that the compiler can prove safety, we may not need `!`. **Conclusion** Let's extend the tracking for nullable reference types to `Nullable`. For banning, `!`, we're worried this is too restrictive and there still may be places where you want the "easy out" of `!`. Note: For the flow analysis, we will consider a value type that is accessed as though it were non-null to be non-null thereafter. There is no situation for the reverse for value types. We will never treat a non-Nullable value type as a Nullable value type, regardless of how you treat it. ### Using nullable values as non-nullable values *Q: Would we regard the new uses of the underlying members as implicitly turning on nullable warnings?* **A:** Yes, probably. Pros: - It's more convenient - It's safe because you'll get a warning Cons: - The checking is not precise, because we allow `!` - Type analysis is less precise, but easier to understand. We adopted the flow analysis mainly because we had back compat concerns with existing null checking code for reference types. Here we don't have to deal with backwards compatibility. - This would also contravene the information provide the declaration site. This figures into seeing the annotation at the declaration vs the use site **Conclusion:** We're not going to do this. ### Pattern matching open issues #### Is a pattern permitted to match a pointer type? You can't explicitly match a pointer because you can't write a pointer type as a pattern (`*` is used for multiplication in this context). However, it would be weird to make an exception for discard and `var`, so it will be allowed for those use cases. *Q: What about `ptr is null`? Or `ptr is {}`?* Allow `ptr is null`. No `ptr is {}` or anything else. #### ITuple vs unconstrained type parameter Let's keep it an error for now. We may relax the restriction later. ### Matching ITuple in the presence of extension Deconstruct This has some similarities in dynamic/static lookup conflicts in GetEnumerator/IEnumerable. Currently, if we see a struct GetEnumerator that doesn't match the pattern we provide an error, even if there is a valid IEnumerable interface underneath. Here the Deconstruct is an extension method, so the analogy is not perfect. Using the Deconstruct method seems more consistent with what we do for instance methods, although this would be different based on whether or not the extension method is in scope. However, it seems difficult to actually implement the check for extension Deconstruct, because it's not clear whether none of the extension Deconstruct methods match because they are not meant for the given receiver, or if they were simply incorrectly written. If we disregard Deconstruct, this would create a difference between the behavior for instance Deconstruct methods and extension Deconstruct methods. **Conclusion** None. Let's look at the implementation in more detail and come back with a proposal. ================================================ FILE: meetings/2018/LDM-2018-10-29.md ================================================ # C# Language Design Notes for Oct 29, 2018 ## Agenda [Source-level opt-in to nullable reference types](https://github.com/dotnet/csharplang/issues/1939) ## Discussion ### Philosophy of specification To start off we discussed the proposal in the broader context of specifying nullability as a feature in the language specification. The proposal talks about "nullable contexts" but we also need to decide what a "context" implies. Two basic approaches: 1. Specify all the details in the language spec, including annotations. 2. Specify very loosely: "'?' means the programmer expects the value may be null. The absence means the programmer does not expect the value to be null." Where we land on this spectrum decides how prescriptive we need to be about the warnings produced and exactly what conditions produce them. First question: how much does the spec act as the intermediary of conforming compiler implementations? One argument is that warnings are not decisions about legal programs, so the requirements are softer than other areas of the spec. On the other hand, warnings often make a big difference when trying to port from one compiler to another, regardless of how much they matter to the spec. In the past, presence of warnings has been a major factor in porting from `msc` to `csc` and vice versa. However, the Roslyn analyzer public API could be seen as part of the warning surface which is entirely at the compiler level, and creates far more burden on other C# compiler implementations than any spec addition. An elaboration of (2) is that we could think about the entire feature, aside from the new syntax for `?` and `!` as a "built-in analyzer" for the compiler that isn't strictly specified by the language, but also therefore cannot affect the semantics of the language. **Conclusion** We're actually going to end up somewhere in a spectrum between (1) and (2), but we're interested in leaning more towards (2). ### Annotation vs Warning context *Q: Do we want to provide warnings about `?` when the annotation context is *disabled?* Similarly, do we want to emit metadata that treats these as nullable types to users of the library? If we don't warn, this would allow you to easily add `?` for use in other areas of the code where the context is enabled, letting you easily annotate your code over time. On the other hand, there's no way to go the other direction, indicating that parameters are non-nullable and that nullable types should not be passed. The original motivation was about a new user using the feature and you annotate one parameter of your method: ```C# void M(string arg1, string? arg2) ``` Without a pragma setting the non-null context, `arg1` is oblivious, but `arg2` is nullable. The user may not realize that `arg1` is not non-nullable because they don't have a pragma. The warning is a feedback system to let a user know that they only part of the nullable feature enabled. **Conclusion** Warn about `?` when annotation context is off. Regardless of the annotation context, the type is considered nullable and will generate warnings if used by a consumer in a warning-enabled context. Similarly, metadata will persist the nullable type and the consumer will consider the type nullable. *Q: Do we warn on `!` when the warning context is off?* First item -- even with the proposal, it's not clear what it means for a warning context to be on or off. Does disabling the warning mean that the warning context is off? **Conclusion** Don't warn about `!`, regardless of context. ### Scenarios There's some argument that (5) and (6) may be more important/common than (3) and (4), but that doesn't mean the conclusions change. **Conclusions** We like the `#nullable ...` directive, but we're not sure about the `#pragma warning nullable ...`. Let's keep it for now, but we're not settled on a specific definition. Note: the proposal has the current syntax wrong. The proposal lists the syntax for configuring a diagnostic as ```C# #pragma warning CS4321 restore ``` but it is actually ```C# #pragma warning restore CS4321 ``` This was not intentional and the proposal should not be read as flipping the ID order. In the proposal, `nullable` is meant to stand in for the diagnostic identifier, effectively acting as a reserved diagnostic ID. ================================================ FILE: meetings/2018/LDM-2018-10-31.md ================================================ # C# Design Review Notes for Oct 31, 2018 This was a review with the full design team (including Anders) to see how the whole release is shaping out. ## Discussion ### Nullable #### Flow analysis to turn a non-nullable type to nullable The question is whether flow analysis can cause types to become nullable if a value of non-nullable type is compared to null. ```C# void M(string x) { if (x == null) { // is x now treated as ‘string?’ here? } } ``` This is an issue that TypeScript has dealt with. There's some worry that most of the warnings will be produced not at the place with the problem. We should be careful that we're not going to annoy the user. #### Flow analysis and refactoring Flow analysis constrains refactoring because something may be tested null by flow analysis, but if you pass to a new method, the flow analysis is lost. For example: ```C# class C { string? Prop1 { get; } string? Prop2 { get; } } class C2 { void M1(C c) { if (c.Prop1 != null && c.Prop2 != null) { M2(c); } } void M2(C c) { // The null checking from M1 is lost here and M2 has to // check again for null to avoid a warning c.Prop1.Equals(...) } } ``` #### `!` on parameters Very different behavior depending on where `!` appears -- maybe too many meanings. #### Treatment of lambdas For ```C# void M() { int? x = null; Action a = () => x = 0; a(); // What's the null state of `x` here? } ``` Treating the delegate conversion as executing the method is unsafe, but not doing so is conservative and will warn on valid checking. TypeScript has also hit this and there's no easy answer. ================================================ FILE: meetings/2018/LDM-2018-11-05.md ================================================ # C# Language Design Notes for Nov 5, 2018 ## Agenda 1. Where can #nullable go? 2. Open issues with pattern matching ## Discussion ### Where can you put `#nullable`? *Q: Does `#nullable` appear in metadata in any way?* It may be useful to minimize the amount of extra metadata by putting it on containers (like types) and letting the context flow down. However, this becomes complex when considering things like `partial`. In general, we don't want to let the implementation decide the language support and we would prefer to limit things only if it improves the language experience, not makes implementation easier. Instead, we'll start by where it makes sense to allow `#nullable` for the language. *Q: If we were to allow it anywhere, where does it span?* One proposal is the last token in the type syntax. So ```C# Dictionary #nullable enable ``` specifies the nullable context for the second `string` and the context for `Dictionary`. *Q: If we believe that the least restrictive version is a reasonable design, what makes sense from an implementation perspective?* Basing the context on the end type token is a reasonable design for implementation. If we decide that this causes significant extra metadata to be generated, we can provide optimizations at emit time without changing the semantics of the language. **Conclusion** Basing the nullable context on the last token in the pragma region actually makes a lot of sense. Let's go ahead with that as the current plan. For warnings, we would base it entirely on diagnostic locations, which is what we do for pragma suppression right now. ### Misc. Nullable *Proposal: Do not allow nullable on the right of a using alias* Accepted. *Q: What about `typeof` and `nameof`?* Same behavior as nullable value types (allowed in `typeof`, not in `nameof`). ### Pattern matching #### Deconstruction and ITuple *Proposal:* 1. If the type is a tuple type (any arity >1; see below) then use tuple semantics 2. If the type has no accessible instance Deconstruct and satisfies the `ITuple` deconstruct constraints, use `ITuple` semantics 3. Otherwise attempt `Deconstruct` semantics (instance or extension) Arguments against: - Exhaustiveness gets better with an extension Deconstruct - Different behavior between pattern matching and deconstruction - But the extension and ITuple implementation should be semantically equivalent - Boxes in the case of no instance Deconstruct and an explicit ITuple implementation - But pattern matching boxes anyway for everything except a concrete value type Arguments for: - We don't have to add complex rules about which extensions are applicable *Alternative Proposal:* 1. If the type is a tuple type (any arity >1; see below) then use tuple semantics 2. If "binding" a Deconstruct invocation finds one or more applicable methods, use Deconstruct. 3. If the type satisfies the `ITuple` deconstruct constraints, use `ITuple` **Conclusion** We like the alternative more, but very slightly. #### Pattern matching with 0 and 1 elements *Proposal:* Permit pattern-matching tuple patterns with 0 and 1 elements (appropriately disambiguated as previously decided) ```C# if (e is ()) ... if (e is (1) _) ... if (e is (x: 1)) ... if (e is (1, 2)) ... ``` The primary concern here is that these disambiguations are impossible in deconstructing declarations and deconstructing assignments. However, we think that's a fine limitation, since there are simple workarounds in all of those contexts. Pattern matching, however, has no such workaround. **Conclusion** Accepted. *Proposal:* Consider `System.ValueTuple` and `System.ValueTuple` to be tuple types. No syntax changes. **Conclusion** Accepted. ================================================ FILE: meetings/2018/LDM-2018-11-14.md ================================================ # C# Language Design Notes for Nov 14, 2018 ## Agenda 1. Base call syntax for default interface implementations 2. Switch exhaustiveness and null ## Discussion ### Default interface implementations Example of base calls: ```C# interface I1 { void M(); } interface I2 { void M(); } interface I3 : I1, I2 { void I1.M() { } void I2.M() { } } interface I4 : I1, I2 { void I1.M() { } void I2.M() { } } interface I5 : I3, I4 { void I1.M() { base(I1).M(); base(I1).M(); } void I2.M() { base(I2).M(); base(I2).M(); } } ``` Q: Should we even allow the programmer to call explicit implementations? In existing C# code this is not allowed since all explicit implementations are private. You can always call by casting to the interface if the interface is not re-implemented, but there's no way to do this via a base call. However, there's no way to use overriding default interface implementation in C# 8.0 *except* for explicit implementation, so not allowing it would basically not allow the scenario. Java does allow this scenario, so full compat would require us to add the feature. Q: What type of dispatch would we use in the runtime? One way is to constrain to the interface, then do a virtual dispatch for a particular method. This probably would require additional (fairly expensive) runtime work. Q: What does the syntax need to express? One problem is for `I3`, where by signature `M` is ambiguous because it's not clear whether you mean `I1.M` and `I2.M`. The syntax would need to specify both that the `I3` implementation is requested *and* whether or not to choose `I1.M` or `I2.M`. One other option is to just not provide a way of disambiguating `M`. If there's an ambiguity you can't call any particular `M`. **Conclusion** Let's do the simplified form. If the `M` being called is ambiguous, it's illegal to call such a method. The only configuration we're considering is which base to look for the method. #### Syntax ```C# namespace N { interface I1 { int M(string s) => s.Length; } } class C : I1 { int I1.M(string s) { // Options: base>.M(s); base(N.I1).M(s); N.I1.base.M(2); base{N.I1} base:N.I1.M(s); base!N.I1.M(s); base@N.I1.M(s); base::global::N.I1.M(s); base.(N.I1).M(s); (base as N.I1).M(s); // base isn't an expression, so this isn't legal today ((N.I1)base).M(s); // same here base.N.I1.M(s); // clash with a member on base, doesn't allow for qualified name N.I1.M(s); // this is currently a static call on I1, can't work } } ``` We think the forms that take advantage of `base` not being an expression are too clever and would just be confusing. `N.I1.base.M(2)` is possibly tricky for human readability since `base` is in the middle and is harder to notice without coloration. One problem with `base(N.I1).M(s)` is if we want to add "invocation" operators to the language, that would disallow invoking your base. `base::N.I1.M(s)` has some history in the language. **Conclusion** Decided on `base(N.I1).M(s)`, conceding that if we have an invocation binding there may be problem here later on. ### Switch exhaustiveness and null Current design: warning about not handling all inputs *except* null. Q: What happens when the switch actually doesn't match what was given at compile time? **Proposal**: A new `MatchFailureException` that extends `ArgumentException`. This allows the compiler to use an existing exception for down-level support. There's no data, just a message. **Conclusion** The base should be `InvalidOperationException`, not `ArgumentException`. If the argument being switched on can be trivially boxed into object, we'll add it as an argument to the `MatchFailureException`. Otherwise, just fill in `null`. ================================================ FILE: meetings/2018/LDM-2018-11-28.md ================================================ # C# LDM notes for Nov 28, 2018 1. Are nullable annotations part of array specifiers? 2. Cancellation of async-streams # Discussion ## Nullable array specifiers Due to history, the syntax for array specifiers is actually "outside-in", i.e. the first set of `[]` actually refers to the outermost array. For the elements themselves, you always specify the `?` directly after the element type name, so `string?[][]` works regardless of interpretation. For specifying the nullability of the "innermost" vs "outermost" arrays, it seems like we have two possibilities for describing nullability here: 1. `string[]?[] a` 2. `string[][]? a` Either of these could specify that the *innermost* array is null. Here are full examples of the meaning of approaches (1) and (2) with a type declaration: ``` string?[][] a; new string?[3][] // 1. Array nullable string, 2. likewise string[]?[] a; // 1. Nullable array of array of string, 2. Array of nullable array of string string[][]? a; // 1. Array of nullable array of string, 2. Nullable array of array of string ``` If we look at instantiation, rather than type declaration, the two approaches look like: 1. ``` new string?[3][] new string[3]?[] // invalid new string[3][]? ``` 2. ``` new string?[3][] new string[3][]? // invalid new string[]?[3] ``` There's also one more option that's a hybrid. Because of array covariance and nullable covariance, assigning an array of non-nullable arrays to an array of nullable arrays does not generate warnings. Declarations would like (2), but instantiation would actually disallow `?` completely: 3. ``` string?[][] a = new string?[3][]; // string[]?[] a = new string[]?[3]; // disallow ? in new string[]?[] a = new string[3][]; string[][]? a = new string[3][]; ``` There are essentially two mental models of multi-dimensional arrays that have served people writing multi-dimensional array code: the way the code actually works, and a "type nesting" model that is mostly non-observable in the current language. When choosing between options 1 and 2/3, we have a decision to either preserve how multi-dimensional arrays actually "work", regardless of whether or not it's confusing, or try to accommodate how we might prefer they work and how people may actually think they work. Option (1) is attractive not only because it's how the feature currently works, but also because Option 2/3 would involve changing the order of the rank specifier specifically for nullable multi-dimensional arrays, so code that previously used `new string[3][]` would now be written `new string[]?[3]`, changing not just the location of the `?`, but also the location of `3`. One reason to go with accommodation is to make nullability specifically more similar to how it works in other places in the language. Generally, to make a type nullable, you add a `?` to the end. This would not be the case with the existing multi-dimensional array model. If most people are adopting existing code with jagged arrays, Option 2/3 may make it easier to adopt the rules common in other areas of the language to make the warnings go away. Since we don't provide nullability warnings for everything but assigning null as the innermost array, the nullability of the innermost array matters a little less. **Decision** Let's stick with the current implementation (1). People may have incorrect mental models which have served them sufficiently until now, but we don't yet see sufficient motivation for complicating the language. ## Cancellation of async streams ### CancellationToken in the IAsyncEnum* interfaces The first question is what kind of support for cancellation we want to put into the interface itself. The primary proposal here is changing the signature of `IAsyncEnumerable` to take an optional CancellationToken: ```C# IAsyncEnumerable { IAsyncEnumerator GetAsyncEnumerator(CancellationToken token = default); } ``` The user's implementation of the interface could then provide a mechanism to pass a CancellationToken during construction, and we could provide an extension method `IAsyncEnumerable WithCancellationToken(this IAsyncEnumerable e, CancellationToken token)` that could wrap an existing `IAsyncEnumerable` with a cancellable one. ### Async iterators and `await foreach` For the new language features there are two potential scenarios to support. The first is allowing the user to pass in their own token for consumption i.e., cancellation during an `await foreach`. The second scenario is allowing the user to consume a CancellationToken during production i.e., when writing an async iterator. #### Consumption Most proposals center around some extra syntax for `await foreach` that allows a CancellationToken to be passed e.g., ```C# await foreach (var x in e, cancellationToken) { ... } ``` If we allow specifying the cancellation token during enumeration, passing the cancellation token when calling the generated iterator is probably the wrong place to pass it, as you should put it in through enumeration. #### Production A user scenario is the user is writing an iterator and wants to react to a cancellation token that can in as a parameter. For example, ```C# async IAsyncEnumerable M(..., CancellationToken token = default) { ... } ``` If we're writing an iterator, the main feature we would need to support is putting the CancellationToken into the generated iterator and specifically connect it to a CancellationToken provided to the method. One way of supporting this would have a special "value"-like keyword that refers to an implicit cancellation token that's used in the generated enumerable. An even more maximalist proposal would allow implicit CancellationTokens that can be added to the end of *any* method, and if a CancellationToken is passed anywhere then it can be implicitly threaded through. This is a much larger feature. Another way of providing a CancellationToken could be specifying that a particular parameter that is the special CancellationToken, e.g. ```C# IAsyncEnumerable M(..., [Cancellation]CancellationToken token = default)` ``` Whichever way the custom token is specified, the token would be stored in the `IAsyncEnumerable` state machine, and if another token is passed to `GetEnumeratorAsync` then we would also store that and decide if we create a combined token for both, or override the original token provided. **Decision** 1. Change `IAsyncEnumerable.GetAsyncEnumerator()` to `IAsyncEnumerable.GetAsyncEnumerator(CancellationToken token = default)` and provide a `WithCancellation` extension method. 2. In the constructed iterator state machine, `GetEnumerator` should throw if the given CancellationToken is cancelled. It's not decided exactly where or how often we will check for cancellation. 3. Do nothing more, right now. Revisit the production and consumption sides of this before shipping. ================================================ FILE: meetings/2018/LDM-2018-12-03.md ================================================ # C# LDM notes for Dec 3, 2018 ## Agenda 1. `using` declaration open issues 2. Return type of `Range` indexer on array and FX types ## Discussion ### `using` declaration open issues *Q: Do we want to reserve '_' to mean discard in a `using` e.g., `using var _ *= ...;`? This would let the user set the scope for an expression without creating an identifier.* **Conclusion** Yes, this seems useful. #### Deconstruction *Q: Do we want to allow a deconstruction in the declaration e.g., `using var (x, y) = GetResources()`?* We noted that this could mean two different things: the outer tuple is disposed, then deconstructed, or the elements are deconstructed, then each is disposed. **Conclusion** This seems too complicated for the marginal benefit. Rejected. #### `goto` and using declarations Example: ```C# goto label; using var x = ...; label: ... ``` Can you `goto` past a `using` declaration? Can you `goto` before a `using` declaration? Can you `goto` in the same block, but not past the `using`? Can you even have labels and `using var` in the same block? **Conclusion** You definitely can't `goto` "past" a using var. That would be similar to jumping "into" a try-finally. We could allow jumping "out" of a `using var`, but we're concerned it may be confusing, since there may not be a syntactical indication that the disposal is executed. When `goto`ing a label in the same statement list as a `using var`, the `goto` must not have a `using var` syntactically between the `goto` and the label. #### Extension `Dispose()` and `Nullable` *Q: Should an extension `Dispose()` method be called if the receiver is null?* No, the behavior is defined as `item?.Dispose()`, and so `Dispose()` will not be called for `null` values. *Q: For a value of type `S?` and extension methods `Dispose(this S? s)` and `Dispose(this S s)`, which is called?* Same resolution as above: the behavior is defined as `value?.Dispose()`, so we do the same thing as `item?.Dispose()` (which picks `Dispose(this S s)`). ### Return values of Range indexers Proposals: 1. Add indexers that return the same types (string returns a string, array returns an array, Span returns a Span, etc.) 2. Don't add indexers that allocate/copy, add overloads to do slicing with ranges 3. Add indexers that return Span\ 4. Add indexers that return Memory\ 5. Do nothing (3) and (2) are initially attractive because they automatically produce the fastest possible behavior with no allocation. Essentially, users are in a pit of success from a performance perspective. There are two main problems with this: 1. Span is not considered "general purpose" and can be quite difficult to use. Not being able to store the value in a field or convert it to an interface is difficult. 2. Span and Memory are very new types and most code doesn't use them. Users would often be forced to coerce into another type anyway. This is particularly bad for strings, where almost all operations are only available on the `string` type. The opposing proposal is (1). The theory is that users are more likely to be able use that they pass in. For many users, the additional allocation and copying will not be prohibitive. For performance sensitive users, instead of passing arrays around directly, they can proactively convert to Span or Memory and all subsequent indexing operations will be 0-allocation. The main problem with (1) is performance-sensitive users are essentially in a pit of failure in this proposal. The short syntax makes the indexers attractive, but likely to harm performance. It was also noted that indexers are generally not supposed to hide expensive or complicated operations, according to .NET coding guidelines. However, Range indexers are fundamentally new operations (they even return a different type -- a collection instead of an element) and it's not clear we should naively apply the same standards for existing indexers to Range indexers. **Conclusion** Settled on (1), with overloads for `AsSpan(Range r)` that return a `Span`. This should reduce the work necessary to go directly from a Span-compatible types to Span slices. CoreFX Proposal: https://github.com/dotnet/designs/pull/48 *Q: Do we want to allow Range/Index indexers on multidimensional arrays? They are currently prohibited.* **Conclusion** Undecided. We need to discuss this in another LDM. ================================================ FILE: meetings/2018/LDM-2018-12-05.md ================================================ # C# LDM notes for Dec 5, 2018 ## Agenda Tracked nullable states, their correspondence to source and the rules they follow in flow analysis ## Proposal Since we are treating nullable analysis as a conventional lattice data flow analysis, we'd like to define the lattice. Here are the states based on current implementation: * Unknown * NotAnnotated * Annotated * NotNullable * Nullable The goal is to get to a concrete specification that defines how the flow state affects the language and how flow states interact. The first task is to decide how the annotations and the nullable context interact to define the flow state. At the moment, the context of where the variable is *defined* decides what the state of the variable is in, when it is used in a method. However, there is a proposal to use the context *of the method* to define the state the variable is in. There are now new proposed states, which are now split between states used in flow analysis, and states used as semantic annotation. Semantic states: * Nullable * Oblivious * NotAnnotated Flow states: * "Can be null" (true or false) Consequences: When reading a field we compute the flow state as follows: * `Nullable` -> `true` * `Oblivious` -> `false` * `NotAnnotated` -> `true` only if a type parameter that could be nullable, else `false` When writing a field we warn as follows: * `NotAnnotated`, `true` -> warn if the RHS can be null when the variable (or type) can't (i.e., take into account both being related type parameters) ## Discussion *Q: What warnings do we produce for the following?* ```C# void M(T p1) where T : class? { p1 = p1; // p1 may be null, but we don't produce a warning because // the T could be nullable } ``` The consequence is: ```C# void M(T p) where T : class? { p = null; // This DOES produce a warning, because there's a conversion // from `null` to T and we don't know that T is nullable } ``` Also: ```C# void M(T p) where T : class? { if (p == null) // <- this is fine { T x = p; // <- this also doesn't give a warning because // there is no conversion } } ``` And: ```C# void M(string p1) { if (p1 == null) { string x = p1; // W warning? -> Yes } } ``` Between the current state and the new proposal, what's the behavior of the following? ```C# #nullable disable var t = M(new object()); // What is T inferred as? ... #nullable enable t.Value = null; // warning? ... Box M(T t) => new Box(t); ``` Current: `Box` is inferred as `Box`, warning New proposal: `Box` is inferred as `Box`, so no warning ```C# void M(T x) { if (x == null) throw; var y = x; var z = M(y); // What is the inferred type? z.Value = null; // warning? } ... Box M(T t) => new Box(t); ``` Current implementation: `Box` is `Box`, warning New proposal: `Box` is inferred as `Box`, no warnings ================================================ FILE: meetings/2018/LDM-2018-12-12.md ================================================ # C# LDM notes for Dec 12, 2018 ## Agenda 1. Open issues with async streams 2. Pattern dispose ## Discussion ### Cancellation in GetAsyncEnumerator We previously decided that GetAsyncEnumerator takes a CancellationToken. When do we check if it's been cancelled? Our design for cancellation in the user code itself is to have the user write the core logic for their IAsyncEnumerable using an IAsyncEnumerator iterator method. The user can manually check for cancellation inside the iterator body. ```C# class C : IAsyncEnumerable { IAsyncEnumerator GetAsyncEnumerator(CancellationToken token) { ... token.ThrowIfCancelled(); ... } } ``` *Q: Do we want to generate any checks manually? One option is to check the cancellation token on every MoveNext call.* This comes down to: where is it most useful to request cancellation? The most important place to include the cancellation token is in the await calls themselves. This isn't compiler generated code at all -- if the expressions being awaited need a CancellationToken the user will need to pass it themselves. Other places, like every entry to MoveNextAsync, will be called regularly, but will only be able to cancel synchronous code, instead of the long-running async operation. **Conclusion** The compiler will not generate checks for cancellation for now. If we get user feedback that it's important, we will revisit this decision. ### Pattern binding Which pattern do we want the compiler to recognize for GetAsyncEnumerator? Binding options for some expression target of a `foreach`, `e`: 1. `e.GetAsyncEnumerable()` 2. `e.GetAsyncEnumerable(default(CancellationToken))` One of the primary drawbacks for (1) is that all users must make their CancellationToken parameters optional. This may be a problem for implementors who consider it very important to have a CancellationToken. **Conclusion** We will attempt to bind the call `e.GetAsyncEnumerable(default(CancellationToken))` and if it succeeds and has a conforming return type, the pattern binds successfully. ### Recognizing an iterator Consider the following: ```C# class Program { static async IAsyncEnumerable M() { throw new NotImplementedException(); } } ``` As currently designed, this is illegal. `async` methods must either return a Task-like type or return `IAsyncEnumerable/IAsyncEnumerator` and contain a yield statement. This is neither. The proposal is to produce an error. There are a number of fixes: 1. Add a `yield` in the body. 2. Remove the `async`. **Conclusion** Add an error, undecided on the error message. ### When we start recognizing pattern Dispose, do we call it in foreach? Certainly, it seems very important for the feature. Since a ref struct cannot implement interfaces, pattern Dispose is the only way to implement disposal. There are a number of possible breaking changes: 1. A pattern enumerable type did not implement IDisposable, but had a Dispose method. This method would now be call. 2. If there are two Dispose extension methods, that will now produce an ambiguity and thus a compilation error. **Conclusion** Let's narrow the pattern-based Dispose change down to ref structs only. This means every other type will be required to implement IDisposable. ================================================ FILE: meetings/2018/README.md ================================================ # C# Language Design Notes for 2018 Overview of meetings and agendas for 2018 ## Jan 3, 2018 [C# Language Design Notes for Jan 3, 2018](LDM-2018-01-03.md) 1. Scoping of expression variables in constructor initializer 2. Scoping of expression variables in field initializer 3. Scoping of expression variables in query clauses 4. Caller argument expression attribute 5. Other caller attributes 6. New constraints ## Jan 10, 2018 [C# Language Design Notes for Jan 10, 2018](LDM-2018-01-10.md) 1. Ranges and endpoint types ## Jan 18, 2018 [C# Language Design Notes for Jan 18, 2018](LDM-2018-01-18.md) We discussed the range operator in C# and the underlying types for it. 1. Scope of the feature 2. Range types 3. Type name 4. Open-ended ranges 5. Empty ranges 6. Enumerability 7. Language questions ## Jan 22, 2018 [C# Language Design Notes for Jan 22, 2018](LDM-2018-01-22.md) We continued to discuss the range operator in C# and the underlying types for it. 1. Inclusive or exclusive? 2. Natural type of range expressions 3. Start/length notation ## Jan 24, 2018 [C# Language Design Notes for Jan 24, 2018](LDM-2018-01-24.md) 1. Ref reassignment 2. New constraints 3. Target typed stackalloc initializers 4. Deconstruct as ref extension method ## July 9, 2018 [C# Language Design Notes for July 9, 2018](LDM-2018-07-09.md) 1. `using var` feature 1. Overview 2. Tuple deconstruction grammar form 3. `using expr;` grammar form 4. Flow control safety 2. Pattern-based Dispose in the `using` statement 3. Relax Multiline interpolated string syntax (`$@`) ## July 11, 2018 [C# Language Design Notes for July 11, 2018](LDM-2018-07-11.md) 1. Controlling nullable reference types with feature flags 1. Interaction with NonNullTypesAttribute 1. Feature flag and 'warning waves' 1. How 'oblivious' null types interact with generics 1. Nullable and interface generic constraints ## July 16, 2018 [C# Language Design Notes for July 16, 2018](LDM-2018-07-16.md) 1. Null-coalescing assignment 1. User-defined operators 1. Unconstrained type parameters 1. Throw expression the right-hand side 1. Nullable await 1. Nullable pointer access 1. Non-nullable reference types feature flag follow-up ## August 20, 2018 [C# Language Design Notes for August 20, 2018](LDM-2018-08-20.md) 1. Remaining questions on [suppression operator](https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fdotnet%2Froslyn%2Fissues%2F28271&data=02%7C01%7C%7C6defe1e21ab54cce8d0008d606be5d23%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C636703812006445395&sdata=DAdh5dev1mnr%2F5zxtvuJVcHP%2Bzewrzz4z9iuGkl%2BUHg%3D&reserved=0) (and possibly cast) 2. Does a dereference update the null-state? 3. Null contract attributes 4. Expanding the feature 5. Is T? where T : class? allowed or meaningful? 6. Typing judgments containing oblivious types 7. Unconstrained T in List\ then `FirstOrDefault()`. What attribute to annotate `FirstOrDefault`? ## August 22, 2018 [C# Language Design Notes for August 22, 2018](LDM-2018-08-22.md) 1. Target-typed new 1. Clarification on constraints with nullable reference types enabled ## September 5, 2018 [C# Language Design Notes for September 5, 2018](LDM-2018-09-05.md) 1. Index operator: is it a unary operator? 1. Compiler intrinsics ## September 10, 2018 [C# Language Design Notes for September 10, 2018](LDM-2018-09-10.md) 1. Nullability of constraints in inheritance and interface implementation 2. Static local functions ## Sep 19, 2018 [C# Language Design Notes for September 19, 2018](LDM-2018-09-19.md) Triage: 1. XML doc comment features 2. New `foreach` pattern using `Length` and indexer 3. Object initializers for `readonly` members 4. `readonly` struct methods 5. `params Span` 6. Nullable reference type features on `Nullable` ## Sep 24, 2018 [C# Language Design Notes for September 24, 2018](LDM-2018-09-24.md) Combined C#/F# LDM 1. What new C# features are going to require work to get F# support? 2. How can the design of C# features play well with the broader .NET environment? ## Sep 26, 2018 [C# Language Design Notes for September 26, 2018](LDM-2018-09-26.md) 1. Warning waves ## Oct 1, 2018 [C# Language Design Notes for Oct 1, 2018](LDM-2018-10-01.md) 1. Nullable type inference 2. Type parameters and nullability context ## Oct 3, 2018 [C# Language Design Notes for Oct 3, 2018](LDM-2018-10-03.md) 1. How is the nullable context expressed? 2. Async streams - which interface shape? ## Oct 10, 2018 [C# Language Design Notes for Oct 10, 2018](LDM-2018-10-10.md) 1. Pattern matching open questions ## Oct 15, 2018 [C# Language Design Notes for Oct 15, 2018](LDM-2018-10-15.md) 1. Readonly methods in structs 2. Intrinsics update with function pointers 3. Finalize syntax for async-streams. Per [last notes](https://github.com/dotnet/csharplang/blob/95c9267d0d54a9984086ca327b1f892790e6c2cf/meetings/2017/LDM-2017-08-30.md#syntax-options), the current tentative syntax is `foreach await (var x in ...) ...`. ## Oct 17, 2018 [C# Language Design Notes for Oct 17, 2018](LDM-2018-10-17.md) 1. [Open issues](https://github.com/dotnet/csharplang/issues/406) with default interface methods 1. target typed new with default constructors on structs ## Oct 22, 2018 [C# Language Design Notes for Oct 22, 2018](LDM-2018-10-22.md) Discussion of records proposals: 1. [The existing old one](https://github.com/dotnet/csharplang/blob/master/proposals/records.md) 2. [The data classes proposal](https://github.com/dotnet/csharplang/pull/1667) ## Oct 24, 2018 - [Adding Nullable Reference Type features to Nullable Value Types](https://github.com/dotnet/csharplang/issues/1865) - Open issues with pattern matching ## Oct 29, 2018 [C# Language Design Notes for Oct 29, 2018](LDM-2018-10-29.md) - [Source-level opt-in to nullable reference types](https://github.com/dotnet/csharplang/issues/1939) ## Oct 31, 2018 [C# Language Design Notes for Oct 31, 2018](LDM-2018-10-31.md) - *Design review* ## Nov 5, 2018 [C# Language Design Notes for Nov 5, 2018](LDM-2018-11-05.md) 1. Where can #nullable go? 2. Open issues with pattern matching ## Nov 14, 2018 [C# Language Design Notes for Nov 14, 2018](LDM-2018-11-14.md) 1. Base call syntax for default interface implementations 2. Switch exhaustiveness and null ## Nov 28, 2018 [C# Language Design Notes for Nov 28, 2018](LDM-2018-11-28.md) 1. Are nullable annotations part of array specifiers? 2. Cancellation of async-streams ## Dec 3, 2018 [C# Language Design Notes for Dec 3, 2018](LDM-2018-12-03.md) 1. `using` declaration open issues 2. Return type of `Range` indexer on array and FX types ## Dec 5, 2018 [C# Language Design Notes for Dec 5, 2018](LDM-2018-12-05.md) - Tracked nullable states, their correspondence to source and the rules they follow in flow analysis ## Dec 12, 2018 [C# Language Design Notes for Dec 12, 2018](LDM-2018-12-12.md) - Async-streams misc. questions - Compat issue with Dispose pattern in `foreach` # Upcoming meetings ## Dec 17, 2018 - Review the [Nullable Reference Types Specification](https://github.com/dotnet/csharplang/blob/master/proposals/nullable-reference-types-specification.md) ## Recurring topics - *Triage championed features* - *Triage milestones* - *Design review* ================================================ FILE: meetings/2019/LDM-2019-01-07.md ================================================ # C# Language Design Notes for January 7th, 2019 ## Agenda Nullable: 1. Variance in overriding/interface implementation 2. Breaking change in parsing array specifiers ## Discussion ### variance in overriding/interface implementation Issues: https://github.com/dotnet/roslyn/issues/23268, https://github.com/dotnet/roslyn/issues/30958 We currently don't support overriding or inheritance with co/contravariance in nullability. Overriding: we mostly take the signature from the overriding member, not the base. We could allow nullability variance and then use the most derived implementation. The problem is if we don't provide a warning on further derivation, another deriver can "re-loosen the contract." There doesn't seem to be any problem with an interface implementation, because the safety is provided in the actual code. Note: if you override an object-returning method with a dynamic-returning method, we currently use the dynamic return on callsites, which is a parallel to how nullable works. Options: 1. Keep stringent invariant rule 2. Allow safe variation on interface implicit implementation only 3. Allow on overrides also, check against base override **Conclusion** General agreement on (3). Possibly change tuple names to operate similarly (use the base's override as the authoritative signature). We will also use the same rules for explicit interface implementation. ### Breaking change in parsing array specifiers Issue: https://github.com/dotnet/roslyn/issues/32141 ```C# a ? x is A[][] ? b : c // ^ Is this a nullable annotation or part of a ?: ``` Options: 1. Keep decision find another solution for ambiguity a. always prefer ternary for back compat, use parens for new meaning in `is`/`as` 2. Change array decision a. `?` is a type constructor that's applicable to arrays b. Alternate syntax: `?` inside brackets: `a[?][,]` and `a[][,?]` One of the main problems with changing the array is: `new string[3][];` In this example, `3` refers to the outer array size, but if we make `string[][]?` mean "top-level nullable," there's either an inconsistency between the question mark location (`new string[3]?[]` means *inner* array is nullable) or we swap the location of the rank (`new string[]?[3]`). **Conclusion** Let's try to stick with our original design, and add tie breakers to parsing to prefer ternary in any ambiguity. Right now we know of problems with array types in `is` and `as`. There may be more expressions which can end with an array type, which we will address as we find them. *Update: This decision was revised in the Jan. 9th meeting.* ================================================ FILE: meetings/2019/LDM-2019-01-09.md ================================================ # C# Language Design Notes for Jan. 9th, 2019 ## Agenda 1. GetAsyncEnumerator signature 2. Ambiguities in nullable array type syntax 2. Recursive Patterns Open Language Issues https://github.com/dotnet/csharplang/issues/2095 ## Discussion ### Async streams GetAsyncEnumerator revisited Discussed over email as well. Proposal: bind `e.GetAsyncEnumerator()` and use the result, including methods with optional parameters or `params` and extension methods. We like the simple bind accepting optional parameters and `params`, but extension methods are a problem. We have a number of different features that have backwards compatibility constraints preventing them from preferring extension methods over interface implementation. In addition, the design of extension methods is always as a last resort and we prefer code directly from the type (including interface implementation). **Conclusion** Bind a lookup, but without extension methods. After looking for the interface, consider extension methods. Follow-up issue to deal with the difference with simple `foreach`, which always looks for a zero-parameter method and doesn't consider extension methods. *Q: What about [Caller...] attributes?* **A**: Let's do what we did for LINQ, which has to do roughly the same thing. ### Obsolete Do we respect the obsolete attribute in lookups in foreach, async foreach, and pattern dispose? **Conclusion** Yes. ### Ambiguities in nullable array types (again) Example: `if (o is string[][]?(var e0, var e1, var e2) s)` This is now ambiguous because the token after the `?` is not helpful in resolving the ambiguity. 1. Keep the order, but address using lookahead 2. ? is a type constructor (reverse the order of brackets and question marks) 3. reverse the order of ?'s only 4. Introduce Array\ ```C# Array new Array(3) new Array { new[] {1}, null, new [] {2, 3 }} ``` 5. Don't allow nullability in jagged arrays **Conclusion** Let's try (2) and see how it works. ### Should SwitchExpressionException capture an unreified tuple input? Yes, we think so. ### Rename deconstruct pattern to positional pattern? Yes. The extra developer work required is worth it. ### Permit trailing comma in a switch expression? ```C# e switch { 1 => true, 2 => false, } ``` Yes, permit optional commas anywhere there are curly braces, including switch expressions, property patterns, etc. ### Warnings for non-nullable fields that are not explicitly initialized Scenarios that result in unnecessary warnings: * Initialization helpers called from other constructors * Factory methods that call constructors * Object initializers * Chained constructors * Set-up/tear-down methods in test frameworks * Reflection Options: 0. Do nothing, keep the current behavior 1. Don't track initialization. 2. No warnings for private constructors 3. No warnings for constructors that call methods that might modify `this` 4. Track initialization debt with the class only, reporting warnings at public entry points. 5. Track initialization debt across classes (and assemblies), reporting warnings where the object is constructed **Conclusion** Let's keep option 0 for now. We're worried about silently hiding legitimate warnings or that options 4 and 5 are too complicated or expensive to implement. ================================================ FILE: meetings/2019/LDM-2019-01-14.md ================================================ # C# Language Design Notes for January 14th, 2019 ## Agenda 1. Generating null-check for `parameter!` ## Discussion ### Generating null-check for parameter! Proposal: https://github.com/dotnet/csharplang/pull/2144 *Q: Allow on unconstrained generic parameters?* **A**: Not a lot of reason to disallow it. `is` currently doesn't work on unconstrained type parameters, but we view that as a limitation we'd like to lift. *Q: Where is the null check for constructors?* Before or after the base/this calls? Syntactically, it's consistent to leave it in the constructor body, after the base/this calls. However, this could cause us to do more work before the exception is eventually thrown. **A**: In the case where the base/this call would throw anyway, there's not much difference. But in the case that the base/this are null-safe, putting the check in the body just delays the exception, which provides little value. The null check should be the first statement in the method. In general, the null check should be performed as soon as possible. *Q: Is there a warning for `void M(string? param!)`?* `string?` suggests that the method gracefully accepts null, but the feature immediately throws, which doesn't count. However, when overriding you may have a `string?` as your base, but your override only accepts non-nullable `string`. **A**: Yes. In the case where a user is warned about violating the OHI contract, the correct decision is to silence the OHI warning, keeping consistency between the implementation and signature of the method. *Q: Where is the null check for iterators?* **A**: Null check in the stub method, as soon as possible (before the yield). *Q: Can you put it on lambda parameter names?* **A**: Yes, as long as there are no problems in the syntax. If there are ambiguities with simple lambda parameter syntax, it can be allowed only in parenthesized form. #### Syntax There's a feature in TypeScript where you can put a `!` after a field name and that field will be exempt from checking that the field is assigned in the constructor. There's concern that `field!` syntax look a lot like `param!` but they would do almost the opposite thing: `field!` would ignore nulls, while `param!` would throw on null. An alternative would be `void M(string! param)` but it's very strange to put the `!` next to the type but not be part of the type and not be allowed almost anywhere else that a type is allowed. Options: 1. Solve both a. `M(string S!)`, `public string! s` b. `M(string! S)`, `public string s!` 1. Solve parameters a. `M(string s!)` b. `M(string! s)` 1. Solve initialization a. `public string s!` b. `public string! s` 1. Do nothing **Conclusion** Let's try to do (2a), only `void M(string param!)`, as described by Jared. ================================================ FILE: meetings/2019/LDM-2019-01-16.md ================================================ # C# Language Design Notes for Jan. 16th, 2019 ## Agenda 1. Shadowing in lambdas 2. pattern-based disposal in `await foreach` ## Discussion ### Shadowing in nested functions *Q: Allow shadowing for all lambdas as well?* **A**: Yes. *Q: Allow shadowing with range variables in LINQ queries?* ```C# // char c; var q = from c in s from c2 in s where c != ' ' select c; var q2 = s .SelectMany(c => s, (c, c2) => new { c, c2 }) .Where(_x => _x.c != ' ') .Select(_x => _x.c); ``` `c` and `c2` can't be named the same because they are equivalent to two parameters being named the same. However, should the new `c` be able to shadow the local `c`? **A**: Let's look at the implementation and decide based on the complexity. ### Pattern-based disposal in `await foreach` `WithCancellation` and `ConfigureAwait` both return a custom type that does not implement the interface, just the pattern. `await foreach` does not pick it up because it does not have pattern-based disposal and the type cannot implement the `IAsyncDisposable` type because it does not produce a `ValueTask` return for disposal. Proposals: 1. Just allow pattern-based disposal for `IAsyncDisposable`. Only allow pattern-based disposable for `IDisposable` in ref structs. a. For `await foreach`, use the pattern, then dynamically check for interface and use it if present. b. For `await foreach`, use the pattern, then statically check for the interface and use it if present. The dynamic check in (1a) is used because in C# 1.0 the non-generic `IEnumerator` did not implement IDisposable. The next question is whether to consider extension methods. The standard pattern we've previously settled on is: 1. Check for pattern 2. Check for interface 3. Check for extension methods Can we use this pattern for `IAsyncDisposable`? The primary question is whether to consider extension methods. **Conclusion** Let's do 1b. We do not have a non-generic `IAsyncEnumerator` that doesn't implement `IAsyncDisposable`, so the dynamic check is unnecessary. We will not consider extension methods for `IAsyncDisposable` or `IDisposable` (on ref structs). We will consider extension methods for `IAsyncEnumerable` and `IEnumerable`. ================================================ FILE: meetings/2019/LDM-2019-01-23.md ================================================ # C# Language Design Meeting for Jan 23, 2019 ## Agenda Function pointers ([Updated proposal](https://github.com/dotnet/csharplang/blob/master/proposals/function-pointers.md)) ## Discussion ### Creation of a function pointer to a managed method The proposal is `&Class.Method` to produce a function pointer. The question is whether `&Class.Method` is target-typed, whether it has a natural type when there's only one member in the method group, or both. Target-typing is useful because, like with delegates, it allows you to select a unique method out of a method group with multiple incompatible overloads. Natural type is useful because it allows things like `var` and `void*`. **Conclusion** Let's start by only doing target-typing. Also, the section "better function member" is not necessary without the natural typing. ### DllImport CallingConvention? There is actually a stub that the compiler calls for P/Invoke with DllImport that is always done using the managed calling convention, so there's no reason for function pointers to use the DllImportAttribute. NativeCallback is intended for the scenario where you want to avoid the stub overhead. ### NativeCallableAttribute Let's look at this in more detail. ### Syntax ```C# 1. func* managed int(string) 2. func*(string)->int 3. func* managed (string)->int 4. func* managed (string)=>int 5. managed int(string)* 5a. int(string)* 6. managed int(string) 7. managed (string)->int 8. delegate* int(string) 9. func int(string)* 10. delegate int(string)* ``` **Conclusion** We're not sure about all the potential ambiguities here. Let's look at (5a), possibly disambiguating with the calling convention. ### Things to clarify in spec * What does the CLR do if you try to call a method that has a modreq/modopt in the signature, but the `calli` has the signature without the modreq/modopt? ================================================ FILE: meetings/2019/LDM-2019-02-13.md ================================================ # C# Language Design for February 13th, 2019 ## Agenda Nullable Reference Types: Open LDM Issues https://github.com/dotnet/csharplang/issues/2201 ## Discussion ### Track assignments through `ref` with conditional expressions What is the nullability of `ref` variables when assigned through conditional expressions? ```cs string? x = ""; string? y = ""; (b ? ref x : ref y) = null; x.ToString(); // warning? y.ToString(); // warning? ``` ```cs string? x = null; string? y = null; (b ? ref x : ref y) = ""; ``` One option is to assume nullability after a `ref` has been taken to a variable. However, that would mean that a `ref` variable declared non-nullable could become nullable, which seems too heavy-handed. Similarly, disabling flow analysis for variables which are taken as the target of a `ref` feels like violating our model, which is largely based on flow analysis. Alias analysis, on the other hand, seems to complicated and any reliable implementation would be too difficult for users to understand. **Conclusion** Let's reach a middle ground. Assignment between any two identifiers copies the state and is then tracked separately. This is also true for `ref` locals. So, ```C# string? x = ""; string? y = ""; (b ? ref x : ref y) = null; x.ToString(); // warning y.ToString(); // warning ``` But the equivalent using `ref` locals does not. ```C# string? x = ""; string? y = ""; if (b) { ref string? rx = ref x; rx = null; } else { ref string? ry = ref y; ry = null; } x.ToString(); // no warning y.ToString(); // no warning ``` ### Nullability of conditional access with unconstrained type parameters What is the nullability of `x?.F()`? ```cs class C where T : U where U : C? { static void M(U x) { U y = x?.F(); T z = x?.F(); } T F() => throw null; } ``` This question seems interesting even without the type parameters and also contains a nested question about reachability. Is the `null` case of `?.` reachable even if the expression is non-nullable? And if it is, what is the null state of the LHS? ```C# string x = ""; x?.ToString(); // warning? ``` **Conclusion** The `null` case of `?.` is always reachable, meaning the result is always maybe null e.g., ```C# var y = x?.M(); // y is maybe null here, if possible ``` Moreover, the LHS is considered maybe null in the null branch, so by normal flow analysis, after the expression is evaluated a variable on the LHS will be considered maybe null. ```C# string x = ""; x?.ToString(); // warning that x is maybe null ``` ### `!` operator on L-values Where is `!` allowed? * `M(out x!);` (note this also definitely assigns to `x` through the `!`) * `M(out (x!));` * `M(out (RefReturning()!));` * `x! = y;` * `M(out string x!);` Current implementation is to allow in `out` scenarios, but disallow in assignment scenarios. We dislike allowing it in regular assignment. We like allowing it in simple `out` parameters. We're ambivalent on `M(out string x!)`, but it's not easily representable in the syntax model and is very similar to the related feature `parameter!`, with the opposite meaning. **Conclusion** Only allow `!` in simple `out` parameters with no declaration. ### `is` nullability in `false` case See [dotnet/roslyn#30297](https://github.com/dotnet/roslyn/issues/30297) ```cs string s = string.Empty; if (!(s is object)) { s.ToString(); /* could warn? */ } if (!(s is string)) { s.ToString(); /* could warn? */ } ``` There are variants of this scenario with `string!` and `string~`. Should `is` update the nullability in both branches or should the one branch be treated as unreachable? The problematic code is probably more like: ```C# void M(string s) { if (s is IComparable t) { } s.ToString(); // warning } ``` Here the user may not have meant to do a null check, but gets the side-effects of doing so anyway. **Conclusion** It seems important to respect a deliberate null check from the user, even if the input variable is non-nullable. As a next step we need to define exactly which tests we think are "deliberate" null checks. For instance, `x is null` is certainly a null check, but pattern matching may or may not be a *deliberate* null check, even if the code contains a null check. ================================================ FILE: meetings/2019/LDM-2019-02-20.md ================================================ # C# Language Design Notes for Feb 20th, 2019 ## Agenda - Nullable Reference Types: Open LDM Issues https://github.com/dotnet/csharplang/issues/2201 ## Discussion There are variants of this scenario with `string!` and `string~`. The question is whether we should learn in both branches, or whether we should treat one branch as unreachable. ### 'Deliberate' Tests for Nullability This is a continuation of the discussion from the [last meeting](LDM-2019-02-13.md). Proposal: divide into "pure" and "not-pure" null checks. The "pure" null checks will affect both branches, but the "not-pure" will only affect one branch. Precisely: when a "pure" check is used, the nullability state is split for both branches, while a non-pure check does not have a "maybe-null" state after split. Proposed deliberate checks: 1. `x == null` 2. `x != null` 3. `(Type)x == null` 4. `(Type)x != null` 5. `x is null` 6. `s is string` (when the type of `s` is `string`) 7. `s is string s2` (when the type of `s` is `string`) 8. `s is string _` (when the type of `s` is `string`) Other checks: 1. `x is string` 2. `x is string s` 3. `x is C { Property = 3 }` 4. `TryGetValue` (`[NotNullWhenTrue]`) 5. `string.IsNullOrEmpty(s)` (`[NotNullWhenFalse]`) 6. `x?.ToString() != null` Follow up question: What about `?.` and related operators (e.g., `??`)? An example would be ```C# void M(string s) { if (s?.ToString() == null) { // is `s` maybe null? } } ``` A corresponding rewrite would be: ```C# void M(string s) { if (s == null || s.ToString() == null) { // s would be maybe-null after this point } } ``` **Conclusion** Pure: 1. `x == null` 2. `x != null` 3. `(Type)x == null` 4. `(Type)x != null` 5. `x is null` 6. `s is string` (when the type of `s` is `string`) All conditional access (`?.`, `??`) included Not-Pure 1. `x is string` 2. `x is string s` 3. `x is C { Property = 3 }` 4. `TryGetValue` (`[NotNullWhenTrue]`) 5. `string.IsNullOrEmpty(s)` (`[NotNullWhenFalse]`) 7. `s is string s2` (when the type of `s` is `string`) 8. `s is string _` (when the type of `s` is `string`) Switch statement/expression: switch statement treated as essentially the equivalent of an `if/else` rewrite. No decisions for the switch expression yet. ================================================ FILE: meetings/2019/LDM-2019-02-25.md ================================================ # C# Language Design Notes for Feb 25th, 2019 ## Agenda Semantics of `base()` calls in default interface implementations and classes ## Discussion The exact semantics of `base()` are still unresolved. Consider the following legacy `base` call. ```C# class A { virtual void M() {} } class B : A { // override void M() { } } class C : B { override void M() => base.M(); } ``` The behavior for `base` is to find the "nearest" implementation and call that implementation using a direct call, meaning if B.M is uncommented it will be called, while if it commented out then A.M will be called. Notably, if `B` is uncommented at compile time, but at runtime the `B.M` override is not present *the runtime will call A.M.* This is because the runtime will continue looking through base classes for a matching signature if the target method is not present. Most importantly, the runtime *does not* yet have this behavior for `base()` calls in interface implementations. This is because there could be multiple paths to search down and the current IL encoding does not provide a root definition to search towards. For example, ```C# interface IA { void M(); } interface IB : IA { void IA.M() // If this gets removed, the IC.M call will fail { base(IA).M(); } } interface IC : IB { void IA.M() => base(IB).M(); } ``` At the moment, we do not have the time to implement an entire new IL form for `base()` calls, so we have to implement a behavior in absence of that feature. Choices: 1. No `base()` call 2. `base()` call can only target the original definition of the method 3. `base(T).M()` call is a direct call to `T` and an error if the method doesn't exist 4. `base(T)` starts searching in `T`, but in the compiler looks at the bases for a unique, most derived implementation. For feature evolution, we then have three more choices later. 1. Stay as-is 2. New opcode/behavior for `base()` 3. New opcode/behavior for `base.` and `base()` **Conclusion** For the first choice, let's do (3). The emitted code will be a direct call to that method. It is expected that the runtime will throw an exception if an implementation is not present in that type. For binding, in classes the signature used will be the closest override, while for interfaces the signature will be the member definition. ================================================ FILE: meetings/2019/LDM-2019-02-27.md ================================================ # C# Language Design Meeting for Feb. 27, 2019 ## Agenda 1. Allow ObsoleteAttribute on property accessors 2. More Default Interface Member questions ## Discussion ### Allow ObsoleteAttribute on property accessors ObsoleteAttribute, ConditionalAttribute, and CLSCompliantAttribute are currently disallowed on property accessors. VB allows the first three, but provides a warning for `ClSCompliant`. The question is whether or not to loosen this restriction. Allowing `Conditional` seems very dangerous because the "arguments" to the method are not evaluated if the condition is false. Logically, this would imply that the right-hand side in a property assignment expression is not evaluated, but this seems very likely to lead to bugs and confusion. We don't see a reason to disallow `Obsolete` or `Deprecated`, and don't particularly care about `CLSCompliant`. **Conclusion** Allow the change for `Obsolete` and `Deprecated`. Leave everything else as-is. ### Collision of lookup rules and decisions for `base()` Example: ```C# interface I1 { void M(int) { } } interface I2 { void M(short) { } } interface I3 { override void I1.M(int) { } } interface I4 : I3 { void M2() { base(I3).M(0) // What does this do? } } ``` The tricky part here is that both `M(short)` and `M(int)` are applicable to `M(0)`, but lookup rules also say that if we find an applicable member in a more derived interface, we ignore members from the less derived interfaces. Combined with the rule that overrides are not found during lookup, when looking in `I3` the first thing we find is `I2.M`, which is applicable, meaning that `I1.M` does not appear in the list of applicable members. Since we concluded in the previous meeting that an implementation *must* exist in the target type, and `I2.M` is the only applicable member, the call `base(I3).M(0)` as written is an error, because `I2.M` does not have an implementation in `I3`. **Conclusion** The decision from the previous meeting is affirmed and we conclude that the lookup rules will not be changed. It's not clear what the new lookup rules would be and it would be difficult to find when to apply them. In general, we think having a single set of lookup rules will reduce confusion in an already complicated feature area. ### Semantics of `base(T).Member` We also affirm that for `base(T).Member`: * `T` can be a class or interface * All members are available on `base(T).Member` including fields * A definition or implementation must exist in `T` *Aside: The compiler will never emit a call to a member in metadata that is inaccessible.* This decision essentially falls out of existing binding rules for the given expression. ### Accessibility in interfaces We previously agreed to support at least `protected`, `private`, and `public`. What about `internal`, `protected internal`, or `private protected`? In addition, what is the meaning of `protected`? **Conclusion** Allow all accessibility. `protected` specifically seems useful for compatibility with other languages which allow it, and to allow interfaces to create helper methods for their derived implementations. There are seem to be two possible definitions of `protected`: `protected` members are visible only in deriving interfaces, or `protected` members are visible in both deriving interfaces and implementing classes. The preferred definition is `protected` members being visible in all deriving interfaces and implementing classes. ### Accessibility of overriding interface members There are two issues here: what is the language rule around calling overriding implementations via `base` and how to implement that rule via accessibility in the CLR. We like the rule that you should always be able to call an overriding implementation through `base()` as long as you can see the definition. The problem is that the obvious implementation (copying the accessibility of the definition) doesn't quite work with `InternalsVisibleTo` (which is technically not described in the language). Proposal 1: If the implementing member is in the same assembly as the definition we can copy the accessibility. Proposal 2: For all explicit interface implementations, emit the `protected` accessibility. **Conclusion** The language rule is confirmed. We think Proposal 2 works with the language rules, is simple, and doesn't expose anything that we would regret. Let's go with that. ================================================ FILE: meetings/2019/LDM-2019-03-04.md ================================================ # C# Language Design Notes for March 4th, 2019 ## Agenda 1. Nullable user studies 2. Interpolated string and string.Format optimizations ## Discussion ### The 'default loophole' `var y = default(SomeStructWithNonNullableFields);` For the above, no warning is produced. The same is true for other `default` expressions. Is that OK? **Conclusion** Let's continue to track the nullability of the fields and not produce a warning. If we were to produce a warning it seems likely that we would produce warnings that are fundamentally unactionable because the user doesn't control the struct definition. ### User study for nullable design Today we went over the results from some user studies which simulated upgrading a code base with the non-nullable reference types design enabled. There were 5 sessions, with two simple, but non-trivial, code bases: 1. Telegram bot API 2. Jil, a JSON serializer Problems: - People had a fair amount of problems with member definitions, rather than bodies - We haven't published much information on how nullable interacts with definitions - Interface implementation and overriding are hard to fix since there are multiple places that need to change for every nullability change - Object initializer pattern is a persistent problem and the warnings don't seem to be providing much value - Constructor chaining is also a problem, where fields are piecewise initialized - Worse, when looking at the diagnostic, it was hard to get to the member, but when you're at the member, it's hard to verify this one was the problem because the diagnostic also does not occur - Almost everyone expected the warnings to be on the field/property - Nullable value types are a problem because they assumed that the problems were in nullable reference types, but the problems/solutions were subtly different - Diagnostic quality: generics were confusing, especially with tuples - Code fixes - People definitely want code fix to/from a nullable reference - They want a "fix-all" but it's not clear what that is - Some people wanted to see all the warnings, but then narrow down, especially via a code fix - Some people looked at the process as "satisfying the compiler" and others looking at "code hygiene" or "possible bugs" - There were at least a few null-safety bugs and no one found them in the time allotted, but probably would have - People who used the feature for overall code hygiene were more excited about using the feature ### `params` and string interpolation We discussed a new proposal for improving the performance of certain string.Format calls and interpolated string expressions. Proposal: https://github.com/dotnet/csharplang/blob/master/proposals/format.md Summary: Using certain APIs, especially `params` and `string.Format`, can be a source of large performance problems. For certain projects (MSBuild) that are "string manipulation" programs, this can even be the main performance bottleneck for those programs. Unfortunately, these APIs are also very convenient and, in many cases, the preferred way of accessing certain string manipulation functionality in the .NET framework. We'd like it if these features could be less heavyweight. Proposals: 1. Allow `params` to have any of the types `Span/ReadOnlySpan/IEnumerable` Advantages: - The callee cannot stash the parameter, so we can re-use an allocation - Sometimes users do this themselves because it's demonstrably more efficient Consequences: Overload resolution needs to change. New tie breaking rules: - `ReadOnlySpan` - `Span` - `T[]` - `IEnumerable` 2. Allow any standard `Format` overload resolution for interpolated strings This feature builds on (1). If interpolated strings could use new `Format` overloads that have `ReadOnlySpan` arguments, interpolated strings could share in the performance improvements. 3. `ValueFormattableString` Support new `ValueFormattableString` type as a target of a string interpolation. This would be a breaking change when people upgrade to the new framework with the new type, but the older compiler always chooses the `string` overload (because it doesn't understand `ValueFormattableString`) and whenever they upgrade the compiler the call suddenly changes to `ValueFormattableString`. If those two methods do anything different, that would be a breaking change. This feature also does not support using a custom type instead of `ValueFormattableString`. That could be desirable for a number of different scenarios, but would probably make the backwards compatibility constraints even more difficult. We should carefully evaluate the tradeoffs in this feature and see what we can do to enable the most scenarios without taking unacceptable breaks. 3. `stackalloc` the `Span` for `params` It's not possible for C# to generally predict whether or not it's OK to stackalloc arguments. We're interested in enabling this functionality if provided with a "maybe stackalloc" helper from the runtime, and we'd like to keep that possibility open, but we will not do anything here until such a feature is available. ================================================ FILE: meetings/2019/LDM-2019-03-06.md ================================================ # C# Language Design Meeting for March 6th, 2019 ## Agenda Open issues: 1. Pure checks in the switch expression 2. Nullable analysis of unreachable code 3. Warnings about nullability on expressions with errors 4. Handling of type parameters that cannot be annotated 5. Should anonymous type fields have top-level nullability? 6. Element-wise analysis of tuple conversions ## Discussion ### Pure checks in switch expression Example: ```C# void M(object x) { _ = x switch { 1 => x.ToString(), {} => x.ToString(), // is this a "pure" null test? } } ``` The original proposal was that certain tests, like `is {}` are "pure" null checks because there is no reason to perform such a test unless you are checking if the input is null. The switch expression was not decided, but it may be confusing to have a pattern check for null in an `is` expression, but not produce the same nullable semantics for the same pattern in `switch`. However, it's possible that the user didn't intend `{}` to be a null test in the previous example, but just a "catch-all" case. If we use the original interpretation of "pure null test" then this would not meet the bar, as there is a meaning which could not be a null test. However, this would produce a different meaning for `{}` in an `is` expression, as opposed to a `switch`. **Conclusion** `{}` is proving contentious as a "pure" null check in a switch. One alternative which we can all agree on is removing `{}` as a pure null check in the `is` expression, as well. This would remove the problem of consistency all together. ### Nullable analysis of unreachable code ```C# static void M(bool b, string s) { var t = (b || true) ? s : null; t.ToString(); // warning? } ``` **Conclusion** Diagnostics will not be produced because of nullability analysis on expressions which are non-nullable. This conforms to what we do for definite assignment, where everything is treated as defintely assigned in unreachable code. We accept a proposed rule where all expressions which are unreachable are non-nullable (even `null`), in service of the above principle. ### Warnings about nullability on expressions with errors Should we provide warnings about nullability in cases where one of the symbols being considered has an error, like a variable which was declared but never initialized? **Conclusion** We will not provide nullable diagnostics on symbols with errors, because we do not know exactly how the types will be altered to remove the errors, so the warnings may not be useful and are less important than the errors already reported. ### Handling of type parameters that cannot be annotated `e?.M()` used a statement is fine and we shouldn't care about the return type. Similarly, `e ?? M()`. The main unfortunate case is `default`, and the most annoying case is probably implementing code like `GetFirstOrDefault()` where the only way to implement it is to suppress the warning. Moreover, if the method is annotated with `MaybeNull` it seems like you have to mention that the value could be null twice: once in the signature of the method, and once in the body. **Conclusion** We like the list as is. We considered using annotations like `MaybeNull` in things like `return` statements, but aren't willing to go that far right now. More broadly, we may want to consider whether the annotations should apply to the implementer of a method, instead of just the users of the method, but we are not going to do so now. ### Should anonymous type fields have top-level nullability? ```C# static T Identity(T t) => t; static void F(string x, string? y) { var a = new { x }; a.x.ToString(); // ok a = new { x = y }; // warning? a.x.ToString(); // warning Identity(a).x.ToString(); // warning? } ``` Our primary concern here is LINQ query expression, which functionally produce anonymous types that are then consumed through further functions. **Conclusion** Yes, we want to track the inferred type of the anonymous type, so for ```C# void M(string x, string? y) { var a = new { x }; a = new { x = y }; } ``` `a` would be of type `{ string! x }` and the subsequent assignment would produce a warning. ### Element-wise analysis of tuple conversions We discussed the behavior of nested nullability in tuple expressions for the following example: ```C# // current behavior: static T Id(T t) => t; static void M(string? x, string y) { (string, string) t = (x, y); // warning: (string?, string) mismatch t.Item1.ToString(); // warning: Item1 may be null var x = Id(t); // what is the inferred type of x? } ``` The warnings are present regardless of the design. The only question is if the type of `x` is `(string, string)` or `(string?, string)`. If tuples behaved like the underlying `ValueTuple`, the inferred type would be `(string, string)` since the tracking of the `Item1` field does not change the type of the container. However, if we pretended that the elements of the tuple were individual local variables, we would infer `(string?, string)` because the tracked state of a local variable is used in type inference. **Conclusion** The inferred type is `(string, string)`. Aside from difficulties in nailing down exactly what the semantics of "tracked like locals" means (presumably it wouldn't apply to tuple fields of a second type?) there is a simplicity to making tuples, constructed types, and anonymous types behave roughly identical through type inference. ================================================ FILE: meetings/2019/LDM-2019-03-13.md ================================================ # C# Language Design Meeting for March 13, 2019 ## Agenda 1. Interface "reabstraction" with default interface methods 2. Precedence of the switch expression 3. `or` keyword in patterns 4. "Pure" null tests and the switch statement/expression ## Discussion ### Interface reabstraction We previously specified our intent to permit reabstraction, where you could mark a derived implementation as "abstract" and force further implementors to provide an implementation. We allow this in interfaces, so one argument for allowing is simply maintaining feature parity with classes, as we do in many other areas. Another argument is that it would be very useful in interfaces because you would be able to provide an interface with a stronger contract than the base implementation. For instance, if you were implementing an interface that has a method which sorts a list, and you would like to provide an implementation which requires a sort with a stronger contract, like a stable sort, then you would want to mark the sort implementation as abstract. It may not be possible to write an implementation yourself and the implementation in the base may not be suitable because it doesn't implement the stronger contract (e.g., it implements an unstable sort). There's an open question of how this would be implemented in the runtime and how expensive it would be. **Conclusion** The feature seems reasonable and if there were an easy, cheap implementation we would probably take it. However, we don't know if that's possible. We'll take a work item to investigate and see if it's feasible. We'd also like this to work for classes, but don't see it as strictly required if only interfaces are possible. ### Precedence of the switch expression It's currently at the same precedence of the relational operators (like `<`, or `is` and `as`). There are some problems with this precedence, though. For example, ```C# x = e switch { .. } + 1 // syntax error ``` This is because the `+` binds tighter than the switch, so this `switch ({ ... } + 1)`. *Relational* precedence is also weird because of the following: ```C# x = a + b switch { ... } // this is `(a + b) switch { ... }` x = a & b switch { ... } // this is `a & (b switch { ... })` ``` If we were to change to *primary expression* precedence, it would be `(b switch {...})` for both examples. One question is what happens with `await`: what does `await e switch { ... }` do? If we look at the `switch` expression as similar to the conditional expression, this would produce `(await e) switch { ... }`. More broadly, the switch looks a like a more complex conditional expression, so allowing expressions that are allowed on the left hand side of the conditional could be desirable for the switch as well. So, `a + b switch { ... }` is `(a + b) switch { ... }`. However, one major difference between the conditional and the `switch` is that the switch can have expressions with arbitrary type, but conditional requires a `bool`, which allows for more complex expressions. Here are some examples that may motivate the decision: ```C# x + e switch {} * y ((x + e) switch {}) * y _ = await e switch { ... }; _ = a ?? (b switch { ... }); _ = (a ?? b) switch { ... }; _ = a ?? b switch { ... }; _ = 1 + o switch { int i => i, _ => 0 }; _ = a && b ? c : d; _ = a ? b : d ? e : f; _ = (string)s switch { ... }; _ = -x switch { ... }; _ = await x switch { ... }; ``` Looking at the examples, it's seeming like the space makes a big difference in readability. When there's a space between the operands, it's less obvious what the switch applies to, so binary operators are more ambiguous between `a ?? (b switch { ... })` and `(a ?? b) switch {...}`. However, the parenthesis for the first example look a bit stranger than the second, and some people feel that it looks more natural to the switch to bind to only `b`. There's also a fairly strong feeling that the unary operators, like `-` and cast, should bind more strongly. **Conclusion** It seems like a new precedence level between unary and multiplicative is the right fit. For places where either interpretation could be possible (`await`), this doesn't feel like an unreasonable decision. For places where one interpretation seems preferred (other unary operators and binary operators) this choice fits for both. ### Patterns with `or` Should we make ```C# switch (e) { case X or: } ``` a warning on `or` as an illegal variable name? This would assist parsing because we would not have to do any parsing lookahead to figure out if this is a designation, vs an `or` pattern (that doesn't exist yet). **Conclusion** Unfortunately, we already shipped this, so it would be a breaking change. Since we can resolve this through lookahead, we don't think this example is severe enough to warrant a new warning. If we see something more complicated to resolve, we may reconsider. ### Where to produce warnings for "null test" in switch ```C# switch (s) { case null when s.Length == 1: break; case null when M(s = "foo"): break; case _ when s.Length == 2: break; } ``` We have decided `null` is a "pure" null test and we will update the null state for `s`. The question is where we update that state: 1. To the entry of the switch and all previous cases? 2. To the branch of the switch only? 2. Just to the forward paths of the flow analysis? One problem with relying just on the flow analysis is that disjoint checks could be reordered and affect the diagnostics produced, even though the bug would be present regardless of the ordering, since the check would actually be verifying the nullability of the input, which is immutable across the switch arms. The other problem is what variable state to update. If we copy values from the switch and test against the copy, that would be a separate state that would need to be tracked, since the original expression could be modified within the switch. **Conclusion** Let's look at only using forward flow propagation, but define the ordering as looking at the patterns of each arm in order, then use the following flow graph. Notably, if there was no declared variable for the switch expression, we will synthesize one. ================================================ FILE: meetings/2019/LDM-2019-03-19.md ================================================ # C# LDM Notes for Mar 19, 2019 ## Agenda MVP Summit Live LDM w/ Q&A Topics: 1. Records 2. "Extension interfaces"/roles 3. Macros 4. IAsyncEnumerable 5. "Partially automatic" properties 6. More integration with reactive extensions ## Discussion We grouped the discussion into topics to try to maintain a bit of shared state in the discussion. ### Records *Q: How "extensible" will the design be? Could the user provide some sort of template that the compiler would implement for their record, more than the equality and data access features we are initially thinking of?* We're not clear on what kind of templating we could offer, but there are two problems we foresee with generalizing the feature: 1. Even simple equality is actually a bit tricky to write and be correct for subtyping. 2. Generalized templating is tricky to do in a type safe way. One alternative is something like hygenic macros, but we're not willing to go that far in the language at the moment. We definitely are not interested in allowing for custom syntax forms. *Q: Do we not want inheritance? If that's the only thing blocking records?* 1. Inheritance isn't the main thing blocking records. It's mostly lack of design time to work out the last few issues, namely what the simplest syntax means, support for nominal and structural construction, and incorporating the design with discriminated unions. 1. We think inheritance is important for "extensible" records and it's hard to add later. *Q: Will record syntax be supported for structs?* Yes, almost certainly. We're also looking into discriminated unions for structs as well, although it's more difficult because the size of the struct is more complicated. *Q: Deep vs shallow immutability? Value-equality is more natural if there's deep immutability, since two things which may be value equal originally may not be so if a nested value is changed, but the top level equality is only calculated shallowly.* Not currently on the table, records will only have shallow immutability be default, if they are immutable by default. We probably have no mechanism to figure out if equality is broken, either, because the language doesn't currently have a good way of reasoning about this transitevely. ### Extension interfaces/roles *Q: Is this duck typing?* Maybe, it depends on what you mean by duck typing. It's certainly a way to implement an interface after the type has been defined. One of the design goals can be phrased, "here's a class, here's an interface, let's make them fit together", but it's not restricted to what's stated in the class, because we feel there's often a small gap between the interface and class that needs to be filled out. It's also not the case that any type which structurally matches the interface "magically" implements the interface -- all our current designs are nominal and explicit. This is partially because it fits with how C# works as a whole, but also that interface implementation is a CLR language concept that isn't as loose as some other duck typing designs. *Q: Constructors on interfaces?* (Meaning no body, just for the constraint) It's not currently a part of "static members on interfaces" and it seems very similar to proposals to extend the `new()` constraint? When we looked at it in the past we thought the cost/value prop is probably questionable as a standalone feature, but maybe as part of the broader interface feature. The biggest mark against it is there's a workaround of using a delegate that returns the given interface, which generally works pretty well. *Q: What scope would the "extension interfaces" be in?* We'll probably import most of the scoping rules of extension methods. *Q: What about extension fields? Using ConditionalWeakTable?* This consequences are subtle and we'll probably favor having the user do this explicitly if that's what they mean. *Q: Scala-like "implicit" parameters?* We looked at it, but we're concerned it may be too subtle. ### General metaprogramming *Q: Source generators?* We tried a maximal approach for source generators, but the tooling was not able to keep up. There may be something here, but we're not going to do anything until we can guarantee investment across the whole stack. *Q: Can we skip the tooling?* There isn't really a difference between generated C# and standard C#, so there's no clear line to "cut off" the tooling. It's not the author that we're primarily worried about, it's all of the downstream users, who are strongly expecting current features to continue to work. *Q: What are new languages the LDM looks to for ideas?* We generally look at everything we can find, and the LDM has people with their own interests, so we tend to have a mix of perspectives. Some examples: We've looked at Rust and Go for perf-centric designs and "zero-cost abstraction" ideas. We look at Scala for lot's of ideas on merging functional and OO. They're not necessarily the language we want to be, but they've traveled a lot of the same ground. ### IAsyncEnumerable *Q: How does ConfigureAwait/Cancellation work?* Right now we have ConfigureAwait and WithCancellation methods for IAsyncEnumerable that allow you to add functionality at the `await foreach` site, e.g. `await foreach (var x in iasyncenum.ConfigureAwait(false))` What we don't currently have a mechanism for is flowing a token from the `foreach` to the generated awaits in the iterator method. For instance, ```C# var iasyncenum = IterMethod(token); await foreach (var x in iasyncenum) { ... } IAsyncEnumerable IterMethod(CancellationToken token) { yield return 0; token.ThrowIfCancelled(); await Task.Delay(1); yield return 1; } ``` Note that you can flow a token through manually, but then you must control calling the iterator and executing the foreach -- there's no way to pass a new cancellation token during the `await foreach`. There's some feedback that this could be useful, so we'll take another look at it. *Q: Plans to add an ability to await an event?* We're not quite sure how this would work. In general, libraries have some support for this pattern, so we don't see a huge need right now. *Q: Plans to support "partially automatic" properties that let you access the generated backing field?* There's been some talk about allowing a field to be declared inside the property that's only accessible there, e.g. ```C# public int Property { private int _field; get => _field; set { _field = value; } } ``` We haven't had a full discussion about this idea yet. ### More integration with Reactive Extensions in the language? For instance, IObservable? Our current view is that `IAsyncEnumerable` is the mechanism to link Rx into the language. The problem with stronger language integration is that it will require configuration for various buffering/back-pressure options. We think that's better solved by library authors and hope that the current design will strike a good balance. ================================================ FILE: meetings/2019/LDM-2019-03-25.md ================================================ # C# Design Review Notes for Mar 25, 2019 ## Agenda We brought in the design review team to look at some of our recent and open decisions in C# LDM. 1. Nullable reference types: shipping annotations 2. Pattern-based indexing with `Index` and `Range` 3. Cancellation tokens in async streams # Nullable reference types: shipping annotations Up until recently we were planning to add a new type of file, a "sidecar" file, that could be an external source for describing nullability annotations for existing libraries. This has some significant advantages: It works "down-target", and it can get you nullability without breaking open files that are not maintained, or that you do not have access to. On the other hand, this would be a new file format that we'd need to understand in all compilers (not just C#), and plumb through extensive amounts of tooling. If we're lucky, lack of nullability annotations is mostly a temporary, transitional problem. But a new file format would be a new permanent layer of complexity that would be around forever. The fix might be worse than the problem. We therefore recently decided to drop sidecar files, at least for now. The review team agreed with this. Instead we should double down on annotating the .NET Core libraries themselves. To this point, we consider it highly plausible that we will not get all the annotations done for the .NET Core release timeframe (though feedback was that we should try!). Instead we are going to have to roll them out over a period of time, maybe a year after initial release. Feedback is that we should message clearly that our core libraries won't be done with this transition, and that new warnings are likely to hit with every release until we are. We expect the warnings to be broadly useful, but if folks can't live with the churn they should opt out. We're better off focusing on quality than quantity of annotations we ship in each go-around. Once shipped, it's important that we don't change our minds, much as with other surface area. We'd prefer not to add too much user-facing mechanism for this, because again, some day we'll be done, but the mechanism can't be removed. That said, we can consider features for more granular opt-out, instead of just "don't give nullable warnings." E.g. we could have attributes that say "treat a given imported member, type or namespace as null-oblivious when accessing in my code." We do have more local "fixes": there's `!` and pragmas. It's a delicate balance, though. The more you drive opt-outs into details of the code, the more likely you are to accidentally leave them in place, and the harder it is going to be to eventually remove them. # Pattern-based indexing with `Index` and `Range` Adding indexers to existing types to consume the `Index` and `Range` types is too invasive, and we are planning to embrace a more pattern-based approach. For `Index` the compiler can synthesize the behavior from int-based indexers and access to a `Length` or `Count` property. For `Range` there is typically no existing behavior, except in recently added types that have a `Slice` method. If we base a pattern for `Range` indexing on `Slice`, then people can add extension methods to existing types. Both patterns are open to some compile time optimizations, when the `Index`es and `Range`s are directly given to the indexers as index and range expressions. In those cases we won't necessarily need to construct the `Index` and `Range` values but can take compiler shortcuts in terms of the constituent expressions. The reviewers were supportive of this direction and we will go forward with it. # Cancellation tokens in async streams In the current state of design, there is no way for an `async` iterator method to get hold of the `CancellationToken` that the consumer passed to the enumerator. We discussed options for "naming" the cancellation token in the body of the iterator. There could either be a specially marked parameter (the value of which gets supplanted on each enumeration of the `IAsyncEnumerable`), or a magic variable that's automatically in scope, similar to `value` in a property setter. We agreed on the latter approach, using the variable name `cancellationToken`. This is the name most commonly used when cancellation tokens are passed as parameters. ================================================ FILE: meetings/2019/LDM-2019-03-27.md ================================================ # C# LDM Notes for Mar 27, 2019 ## Agenda 1. Switch expression syntax 1. Default interface implementations 1. Reabstraction 2. Explicit interface abstract overrides in classes 3. `object.MemberwiseClone()` 4. `static int P {get; set}` semantics 5. `partial` on interface methods 2. `?` on unconstrained generic param `T` ## Discussion ### Switch expression syntax Confirmed that we like ```C# e switch { a => b, c => d, } ``` ### Default interface methods #### Reabstraction Implement the interface method with an abstract in an interface, forcing re-implementation of deriving interfaces. We previously wanted to see a cost for handling it in the runtime and we know have a proposal for that: https://github.com/dotnet/coreclr/pull/23313 Given that, do we want to allow reabstraction? **Conclusion** Assuming the proposed approach, it seems cheap enough. The only question is if the metadata approach is one we want to add to metadata. Right now we're going to say yes to the feature, assuming that the runtime teams can produce the feature. We also want to put this under the runtime feature flag to prevent exposure to older compilers. We should also do the same for features like static members in interfaces. #### Explicit interface abstract overrides in classes Allow abstract interface implementions in a class as well? Not an abstract class method implementing an interface method. **Conclusion** No, we don't see any great use cases. #### Is `object.MemberwiseClone()` accessible in an interface? In principle, maybe yes, but this member seems particularly problematic. Let's say protected members of object are not accessible. #### Is `static int P { get; set; }` is an auto-property with a compiler-generated backing field? This seems consistent with the idea that `static` means `sealed` by default, but a little strange given that the same syntax without `static` implies virtual for instance members. The main problem will be if we allow `static` virtual/abstract members to be declared in future versions of the language, where this rule doesn't necessarily make sense. **Conclusion** Let's keep the current semantics for now `static int P {get; set;}` -- this declares a static auto-property. The same goes for static field-like events. We may revisit this entire area in the future. #### Confirm that `partial` implies `private` and no access modifier is permitted? **Conclusion** Confirmed. ### Nullable reference types Right now we don't allow the `?` annotation for type parameters that we don't know to be non-nullable. There are numerous customer scenarios where people want to do this and are prohibited. Proposal: `T?` is similar to the constraint `object?`, where T remains essentially unconstrained. ```C# class C { T? M(T p1, T? p2) { if (p2 != null) M(p1, null); // error on the `null` because T may be `int` if (p2 != null) M(p1, default); // OK, because if T is `int`, default is valid } } ``` The goal is to provide value to the consumer that they must deal with null. A use case would be ```C# IEnumerable FilterNull(IEnumerable e) { foreach (var i in e) { if (i != null) { yield return e; } } } ``` This would make `T?` a replacement for the `MaybeNullAttribute` and would provide value for places you could not use the `MaybeNullAttribute`, like nested inside generic types. Moreover, attributes are not required to be propagated for overrides or interface implementations, so it's very possible for implementations to drop requirements of their abstract specification. **Conclusion** It does seem like our current approach has significant limitations. We want to look more closely at the customer scenarios, especially around overriding and interface implementation to decide what more we want to do. ================================================ FILE: meetings/2019/LDM-2019-04-01.md ================================================ # C# Language Design Notes for April 1st, 2019 ## Agenda 1. Pattern-based Index/Range translation 2. Default interface implementations: Is object.MemberwiseClone() accessible in an interface? ## Discussion ### Index and Range patterns The current design for Index has a high implementation overhead for types that currently have `int` indexers and all of the implementations sum to delegation to the `int` indexer. Also, full support can only be added by type authors because there are no "extension indexers" in the language. There's also a small overhead for using the Index type, as opposed to doing a "direct" translation into the `int` indexer. Range is similar to the previous. Proposal: contextual conversions for Index and Range members In short, there would be a new contextual language-defined conversion from `Index` to `int` for any instance member where the receiver is "indexable". One significant limitation is the conversion could be more applicable than expected, meaning that a member could be called with an Index that takes an int, but the member does not intend to be called with an Index, e.g. a member that takes a length, not a index, could be called with a System.Index type due to the containing type being "indexable". The "contextual" conversion also seems very complicated, especially given that C# is already very complicated. *Q: Why only support extensions only up to the type already being *"indexable"? Could we add support for an extension Count() or similar?* **A**: Adding general collection functionality is broader than just a different form of indexing. That seems like an instance of a more general problem in C#. There are other proposals, like roles, that provide the general purpose extensibility needed for the above. *Q: What if Count is implemented slowly or improperly?* **A**: This is a violation of the framework design guidelines. Count should be cheap and O(1). We're willing to double down on this guideline. **Range** Note: the Slice method is preferred to the Range indexer if the Slice is present. This does violate the general principle we have that exact type matches are always preferred over anything else and it makes adding a Slice method a breaking library change, since it will be preferred over the Index indexer. It would also allow a library to add an extension Slice to override the type's implementation, which is generally not desired. The general argument of recognizing Slice seems useful and allows people to add extension support for slicing, which we like. Note: multi-dimensional arrays are not supported for either #### Alternative Proposal We like the general approach of the prior proposal, but think adding contextual conversions is a step too far. Contextual conversions would be both difficult for the implementation and for the user to understand. For Index, if a type is "indexable" (has a Count or Length, and an indexer that takes an `int`) then a synthetic indexer will be added to the type that takes an Index and implements a translation to the indexer. For Range, we do the same thing, but for "rangeable types". A type is "rangeable" if it has a Count or a Length, and a Slice(int,int) method. *Q: Is the 'indexable' pattern based on the constructed type or the original definition?* Unknown, revisit later. *Q: Do we want to support Slice(Range) or Slice(int)?* Probably not -- not worth the complexity. Note: for the counter-proposal, the length should only be evaluated once, and after the arguments are evaluated. Warning or error for writing your own Range indexer? **Conclusion** We think the first proposal went too far, but think the alternative is workable. Let's try to flesh out more of the details of that proposal and then consider it again for inclusion in C# 8. ### Revisit `object.MemberwiseClone()` There are two things we'd like to consider: potential uses for the feature and consistency with the rest of the language rules. Example of use case: ```C# interface IPoint { public int X { get; protected set;} public int Y { get; protected set;} public IPoint WithX(int x) { var tmp = MemberwiseClone(); tmp.X = x; return tmp; } } ``` One argument also levelled against the feature is that it may be unexpected that an interface could call MemberwiseClone(). A counter argument is that it may be unexpected if a base class calls MemberwiseClone, but that is the current behavior in classes. On the other hand, many people may have an impression that interfaces and class are essentially different type hierarchies and they meet at the implementation. There is also another protected member, the destructor, which is on object and is questionable to provide to the interface itself. **Conclusion** The only thing we strongly agree upon is that many of the members of object should not have been there in the first place. Beyond that, we aren't particularly swayed by either the advantages of having the access or the dangers of misuse. We'll slightly prefer our earlier decision: protected members of object are not accessible in interfaces. ================================================ FILE: meetings/2019/LDM-2019-04-03.md ================================================ # C# Language Design Notes for Apr. 3, 2019 ## Agenda 1. Ambiguous implementations/overrides with generic methods and NRTs 2. NRT and `dynamic` ## Discussion ### Ambiguous implementations/overrides with generic methods and NRTs ```C# interface I { void Foo(T value) where T : class; void Foo(T? value) where T : struct; } class C : I { void I.Foo(T? value) { } } ``` This was valid code in C# 7 and would always match the Nullable overload because T? could only mean Nullable\. Now, however, `T?` is ambiguous. The ambiguity also cannot be fixed because we do not allow you to write the constraints. Possible fixes: 1. The existing code means the same thing as before, namely that `T?` always means `Nullable`. If you would like to use the nullable class form, we allow exactly one constrain on the override/implementation: `where T : class`. 2. Same as (1), but also allow the constraint `where T : struct`. 3. No new syntax, do matching in two passes. First, look if the old form (`where T : struct`) is present and use it if present. Second, expand to look for both forms if no match was found. These fixes would address the problem where we currently just choose the "first" one. Note that this is not the only situation where you observe ordering dependence for OHI, but it also appears that that ordering dependence appears in the CLR, so we're not considering this fix to address all of the complexity here, just to not make things worse. Option 4: When you are trying to match an implementation against two signatures, you prefer the one that, for each type parameter, if it has any `T?` instantiations, each of those type parameters has `struct` constraints. In other words, for each place where the overriding member has a `?`, we look for a signature that has a struct type parameter in at least the same places as every other candidate. Follow-on question: why not allow implementations and overrides to specify constraints? The biggest problem is that C# does not allow you to specify certain constraints when you actually need to in order to correctly override. VB solves this problem by allowing anything, specifically for OHI (overriding, hiding, and interface implementation). We could also allow a *subset* of constraints to be specified, but we would have to define what we mean by subset. The main problem we have with (4) is we're worried that the preference rule would be confusing. Fundamentally, allowing a bare `T?` to mean either `Nullable` or nullable reference based on context of what is defined in the base is worrying. **Conclusion** We like option 2. This would maintain backwards compatibility and is a simple explanation for what `T?` means in OHI. ### Dynamic invocation and nullable warnings Do we want to report or track nullability for values of type `dynamic`? In some dynamic scenarios we don't know the nullability, but the simple assignment of null to a local of dynamic type must be null. It seems like there could be value in providing at least some null tracking. For a method which simply returns a `dynamic?` we could produce a maybe null dynamic value. For invocations, we could always produce a `dynamic!` that is not maybe null. This seems like a reasonable middle ground. **Conclusion** Let's track nullability for `dynamic`. Tracking dynamic values seems somewhat useful. We would do the same value-based flow analysis as we do for other types. Some scenarios with `dynamic` would not produce warnings, but if a warning is produced it seems likely to indicate a real problem. ### Dynamic invocation of static methods with dynamic arguments For static methods, we currently do no type checking for the dynamic arguments, but we do check if any of the candidates are possible. Would we provide null checking in addition to this? For all argument types, including the dynamic arguments? **Conclusion** Since we already do some validation for static invocations, and we can provide some useful static information here, it sounds good to do it if possible. ### Nullability of return type of dynamic static invocations We currently do no checking on the return type (the return type is always dynamic, even if all the candidates return void). Adding nullability does not seem useful given the lack of checking that we already do. **Conclusion** The return type is always `dynamic!`. ================================================ FILE: meetings/2019/LDM-2019-04-15.md ================================================ # C# Language Design Notes for Apr. 15, 2019 ## Agenda 1. CancellationToken in iterators 2. Implied nullable constraints in nullable disabled code 3. Inheriting constraints in nullable disabled code 4. Declarations with constraints declared in #nullable disabled code 5. Result type of `??=` expression 6. Use annotation context to compute the annotations? 7. Follow-up decisions for pattern-based Index/Range ## Discussion ### Proposal for CancellationToken in iterator method The proposal is that we allow a parameter of type CancellationToken to an iterator to be decorated with an attribute that specifies that the parameter is to be used in the state machine for cancellation if none is provided. **Conclusion** Approved. We will also allow the token in WithCancellation to override the token provided to the iterator. We will mitigate potentially surprising behavior by making the attribute named `DefaultCancellation` or similar. ### Implied constraints of type parameters in #nullable disabled code Consider the following declaration: ```C# #nullable disable interface I { T M(T arg); } ``` If you provide an implementation, ```C# #nullable enable class A : I { public T M(T arg) where T : object { } } ``` this will provide a warning because currently the implied constraint on `I.M` is `object?`. The proposal is that the implicit constraint should instead be "oblivous `object`", meaning that an implicit interface implementation will not provide a warning, and an explicit interface implementation will inherit the oblivious constraint. **Conclusion** Yes, the implied constraint in a disabled context is oblivious. We previously did not consider context when deciding the implicit constraint, but seeing this example, we have decided differently for disabled context. ### Inheriting constraints in #nullable disabled code ```C# #nullable enable public class A { public virtual void F2(T y) where T : class? { } } class B : A { #nullable disable public override void F2(U y) { ... } } ``` What is the implied constraint for the type parameter `U`? **Conclusion** The constraint is inherited from the definition, which is `class?`. Since you cannot write a new constraint in the override, there's no point in doing anything but inheriting. ### Declarations with constraints declared in #nullable disabled code ```C# #nullable disable class A where T2 : class where T3 : object { #nullable enable void M2() { T1 x2 = default; // warning? T2 y2 = default; // warning? T3 z2 = default; // warning? } } ``` Proposal 1: The declarations using the type parameters are treated as unconstrained. All lines require a warning by previous decisions. Proposal 2: The declarations using the type parameters are treated as oblivious. None of the lines produce warnings. **Conclusion** Proposal 2 seems to fit with the philosophy of oblivious, where we do not provide warnings and trust the user to know the correct nullability of their types. Proposal 2 accepted. ### Result type of `??=` expression Right now the result type of `??=` is always the type of the LHS of the assignment. This is consistent with all other assignment operators in the language. However, there's been some desire for a closer analogy to the `??` instead of to the assignment operators. That change would mean the following: ```C# int? b = null; var c = b ??= 5; // b is int? // c is int ``` **Conclusion** We will accept the design change to be closer to the behavior of `??`. This means that we will no longer maintain the principle that an assignment can be replaced with the variable being assigned in an expression context. While that is unfortunate, we think the use cases are good enough to justify the drawback. The result of the `??=` expression will be the result of an implicit conversion from the RHS to the underlying type of the LHS, if one exists. Spec note: if possible it would be cleaner to specify this in terms of `??`. ### Use annotation context to compute the annotations? The current LDM decisions have a tentative decision to prohibit inferring oblivious in type inference, as a part of the principle that type inference does not infer "unspeakable" types. The proposal is that we change the spec to remove this requirement. We do not seem to have any definitive examples of where this rule would reduce confusion or provide a lot of value, and it seems to be fairly complicated both to implement and also to spec the results of all of the situations in which oblivious states can enter into type inference. **Conclusion** While the principle is valuable, it doesn't seem to carry over to this area completely. It's agreed that we will remove this part of the spec and keep our current implementation. ### Follow-up decisions for pattern-based Index/Range A small note that there were the following changes or refinements for pattern-based Index/Range: - All members in the pattern must be instance members - If a Length method is found but it has the wrong return type, continue looking for Count - The indexer used for the Index pattern must have exactly one int parameter - The Slice method used for the Range pattern must have exactly two int parameters - When looking for the pattern members, we look for original definitions, not constructed members ================================================ FILE: meetings/2019/LDM-2019-04-22.md ================================================ # C# Language Design Notes for April 22, 2019 ## Agenda 1. Inferred nullable state from a finally block 2. Implied constraint for a type parameter of a partial? 3. Target-typed switch expression 4. DefaultCancellationAttribute and overriding/hiding/interface implementation ## Discussion ### Nullable Reference Types #### Inferred nullable state from a finally block See https://github.com/dotnet/roslyn/issues/34018 > I don't think our current inference design interacts with finally blocks well: ```C# C? c = null; try { c = SomeNonNullComputation; } finally { if (c != null) c.Cleanup(); } c.Operation(); // undeserved warning ``` > We infer from c != null that c might be null. That inference leaks out to the enclosing construct. The result is a warning when c is used after the try-finally statement. This will be a common pain point. > I don't think indirect inferences from inside a finally block should leak out to the enclosing context. An inference from an actual assignment in the finally block should indeed leak out, though. This problem is partially caused by the limitations of the flow analysis being non-monotonic and our simple path-independent analysis that uses the conservative result of a union of the end of the try and the end of the finally, which doesn't distinguish between different paths through the try-finally. Proposal: Modify the analysis so that inferred branches do not modify the state outside of the finally. Thus, the state from the inferred `else` will not propagate out of the `finally`. **Conclusion** We're a little worried about the additional computational complexity of path-dependent flow analysis, and if `finally` is the only place it would improve precision, it seems to be of limited value. The proposal will fix a rather obvious flaw in the existing analysis, is relatively simple, and will probably not make other scenarios significantly worse. Accepted. ### Implied constraint for a type parameter of a partial? See https://github.com/dotnet/csharplang/issues/2450 > What is the implied type parameter constraint in ```C# #nullable disable partial class C { } #nullable enable partial class C { } ``` > If there were only the first declaration, the constraint would be "oblivious object". If there were only the second declaration, the constraint would be "nullable object". > Is this an error? We've been moving code to CoreFX recently and we've hit cases where we can't necessarily move all of the partials to use nullable at the same time, so an error could make things much more difficult. Proposal: We already have rules about merging two type declarations with differing nullability for type inference. The rule about invariants seems to fit well for this situation. The merged result will be the same as the one produced for type inference. Q: Will a warning be produced if constraints don't match? Where will it be produced? Q: Will an error be produced if the conflict is two different annotations in enabled code? Q: Would the proposal apply only to partial types? Or to partial methods as well? **Conclusion** We like the proposal to use the invariant matching from type inference and merging. A mismatch between two non-oblivious candidates produces an error. No warnings are produced. For partial methods, the constraints must match and we produce the same warnings/errors as we would with mismatched parameter types. For the result, we use the implementation signature inside the implementation, and the declaration signature for the callers. ### Target-typed switch expression It seems like we would like this to work: ```C# byte M(bool b) => b switch { false => 0, true => 1 }; ``` Proposal: 1. If there is a common type, that is the natural type of the switch expression. 2. There is also a new implicit conversion from expression, to type `T` if every expression type in the arms can convert to `T`. Unfortunately, we cannot do the same thing for the conditional expression: ```C# void M(byte b) { } void M(long l) { } void Test(bool b) => M(b ? 1 : 0); // The above calls M(long l). If we use the above proposal for ?: // then it will call M(byte b) because byte // is a "better" overload than long ``` However, we could do a modification: the target typing exists only when the conditional expression does not have a natural type. **Conclusion** Proposal accepted for the switch expression; do it before shipping C# 8.0. We also like the modification to the conditional expression. We would like to do it in C# 8.0 if possible, but if not we will not tie the switch expression changes to the conditional expression changes. ### DefaultCancellationAttribute and overriding/hiding/interface implementation Proposal: produce a warning if the attribute is placed anywhere aside from a parameter with type CancellationToken on an async iterator method. That means that abstract methods would have a warning. **Conclusion** Accepted. Also, - We will not warn on applying the attribute to multiple CancellationToken parameters inside an async iterator. - We *will* warn if there is at least one CancellationToken parameter to an async iterator and none of the parameters have the attribute applied. ================================================ FILE: meetings/2019/LDM-2019-04-24.md ================================================ # C# LDM Notes for April 24, 2019 ## Agenda 1. MaybeNull and related attributes ## Discussion There are two proposals on the table: - https://github.com/cston/csharplang/blob/MaybeNull/proposals/MaybeNull.md - https://gist.github.com/MadsTorgersen/e94bc6802b96ce9cc65bc3dd39b7f6a2 The proposals are primarily focused on providing nullable information for the caller of methods where certain nullable contracts are outside the ability of our current annotation syntax to express. We've also gathered the following information on nullable contracts as they currently exist in CoreFX: > We’ve annotated most of corelib at this point. I just quickly went through looking at some of the ~600 TODO-NULLABLE follow-up comments we have for examples of public API signatures that are currently less-than-correct and that would benefit from further ability to annotate/attribute. I did not include things that just impact locals or fields or other non-API stuff. > Generic methods or method on generic type that might return default(T), e.g. > - Array.Find(T[] array, Predicate match). Returns default(T) if there isn’t a match. > - Lazy.ValueForDebugDisplay. Returns default(T) if the Lazy hasn’t yet been initialized. > - Marshal.PtrToStructure(IntPtr). If you pass in IntPtr.Zero, you’ll get back default(T). > Generic methods accepting an unconstrained T with argument that should not be null > - Marshal.GetFunctionPointerForDelegate > Try- methods on generic type that outs default(T), e.g. > - ConcurrentQueue.TryDequeue(out T) > - IProducerConsumerCollection.TryTake(out T) > - WeakReference.TryGetTarget(out T target) > Try- methods with non-generics, e.g. > - Version.TryParse > - Semaphore.TryOpenExisting > Try- methods that return a struct wrapper around a reference type that’ll be non-null if returning true > - MemoryMarshal.TryGetArray(…, out ArraySegment) > Interfaces where T should be nullable on some methods but non-nullable on others, e.g. > - IEqualityComparer: Equals(T x, T y) should have nullable arguments, but GetHashCode(T obj) should have a non-nullable argument > Fields/properties of type T that start life as default(T), and maybe become default(T) again > - AsyncLocal.Value > - ThreadLocal.Value > - StrongBox.Value > Array slots that need to be settable to default(T) > - Pretty much every collection we have, nulling out a slot when an element is removed > Methods where whether the return value can be null is impacted by whether an argument is null > - RegistryKey.GetValue > - Path.GetFullName > - Path.ChangeExtension > - Path.GetExtension > - Path.GetFileNameWithoutExtension > - Convert.ToString(string?) > - Delegate.Combine > Methods where whether the return value can be null is impacted by whether an argument is true/false > - Type.GetType(…, bool throwOnError) > Methods where one argument will be non-null if another is non-null > - Volatile.Write > - Interlocked.Exchange > - Interlocked.CompareExchange (this one’s more complicated still) > Methods that accept an delegate and object?, but where the argument to Action will be non-null iff the object? is non-null > - Task.Factory.StartNew > - ExecutionContext.Run > - SynchronizationContext.Post > - CancellationToken.Register > - ThreadPool.QueueUserWorkItem > - Task ctor > - IValueTaskSource.OnCompleted > Ref arguments that will be non-null upon return > - Array.Resize(ref T[]); > - LazyInitializer.EnsureInitialized(ref T) (this one’s complicated if T is a nullable value type) The first question is whether or not the annotation of the method changes, e.g. ```C# public static class Enumerable { [return: MaybeNull] public static T FirstOrDefault(this IEnumerable src) {...} public static T Identity(T t) => t; } ``` Here `FirstOrDefault` does not produce a `string?`, but the flow state of values returned from calls is a string with a MaybeNull state. Following through, for `Identity(FirstOrDefault(...))`, the inferred type of `T` for `Identity` would be `string?` because the flow state is used to construct the annotation of the substituted type argument. As written, if you were to provide a `FirstOrDefault` override for an analogous `FirstOrDefault` instance method, you could not write `string?` for the return type, despite the `MaybeNull` attribute. The proposal does not attempt to address modifying what the methods accept, only what they produce, meaning that providing `MaybeNull` on a parameter does not indicate that the input can be nullable. However, we think `MaybeNull` may be common or impactful enough to consider syntax, like allowing `T?` on an unconstrained type parameter or `T : object?`, to express `MaybeNull`. Importantly, this would not address all the patterns we've found while annotating CoreFX, like `Interlocked.CompareExchange` or `Debug.Assert`. The other problem is that the list of attributes which are needed to represent all patterns is unbounded for all the code, but even the set of attributes needed for just what we know is common is quite large. **Conclusion** We think the best approach right now is to try to address ~80% of the common cases using a mixture of attributes and special casing for extremely complicated contracts like `Interlocked.CompareExchange`. For `MaybeNull` and `T?` specifically, we are conflicted between the potential value that `T?` can provide, especially in annotating nested types, and the additional complexity in both language and implementation. One thing we're particularly worried about is the design work in adopting `T?` and revisiting some previous decisions. We're going to examine that work and if it's low in comparison to the `MaybeNull` attribute work, there's substantial support for implementing the feature. If we do, implement `T?`, we do not want to include the `MaybeNull`, specifically. ================================================ FILE: meetings/2019/LDM-2019-04-29.md ================================================ # C# LDM for April 29, 2019 ## Agenda 1. Default interface implementations and `base()` calls 2. Async iterator cancellation 3. Attributes on local functions ## Discussion ### Default interface implementations and `base()` calls 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 hierarchy and use that instead. For example, ```C# class A { public virtual void M() { } } class B : A { public override void M() { } } class C : B { public override void M() { base.M(); } } ``` if `B.M` is not present at run time, `A.M()` will be called. For `base()` and interfaces, this is not supported by the runtime, so the call will throw an exception instead. We'd like to add support for this in the runtime, but it is too expensive to make this release. We have some workarounds, but they do not have the behavior we want, and are not the preferred codegen. Our implementation for C# is somewhat workable, although not exactly what we would like, but the VB implementation would be much more difficult. Moreover, the implementation for VB would require the interface implementation methods to be public API surface. #### Conclusion Cut `base()` syntax for C# 8. We intend to bring this back in the next major release. ### Async iterator cancellation We’ve discussed but not settled on a behavior for when you have: ```C# IAsyncEnumerable enumerable = SomeIteratorAsync(ct1); IAsyncEnumerator enumerator = enumerable.GetAsyncEnumerator(ct2); ``` In some cases, the answer is easy: * If ct1 == ct2, it doesn’t matter, use ct1. * If ct1 == default, use ct2. * If ct2 == default, use ct1. But it leaves the hard case, where `ct1 != default && ct2 != default && ct1 != ct2`. There are several options for how to handle that: 1. Use ct1, ignore ct2. 1. Use ct2, ignore ct1. 1. Throw from GetAsyncEnumerator (the point at which we can see both). 1. Use a new ct3 that combines ct1 + c2 (it’ll be canceled when either is canceled). The current bits do (2). There was previous support for (3). But in our most recent discussion, (4) seems useful. Pros for (4): * It’s intuitive and explainable. No matter what token you pass where, the implementation will respect it. Canceling a token will request cancellation of the enumeration, regardless of where you pass it. * It composes naturally. The code creating the iterator can pass a token. The code enumerating the iterator can pass a token. Both are respected. * Code reviews / debugging will be easier. In our experience diagnosing production hangs, in multiple cases it was because some code in the Azure SDK didn’t pass along a CancellationToken it was supposed to. Once we knew where to look, it was very obvious just from looking at the source that the CancellationToken passed into method A wasn’t passed to method B. If we do (1) or (2), it’ll be very difficult to spot such issues, as the developer will correctly be passing along the token, but the compiler-generated implementation will ignore it, in code not visible anywhere. Cons for (4): * Compiler-generated code. There’s more of it, with an additional BCL dependency (CancellationTokenSource). * Slightly larger object size. The state machine will need a CancellationTokenSource field that represents the combined source. The field (but not the instance) will be necessary even in the common case where there’s only one token and combining isn’t necessary. * More work in GetAsyncEnumerator. In the case where either or both tokens are default, it would just add one or two comparisons. In the case where both are non-default and need to be combined, it’d involve some allocation and additional work, but pay-for-play. #### Conclusion Agreed on (4). Produce an error if you have the EnumeratorCancellationAttribute on more than one CancellationToken. Warn if you have a CancellationToken parameter with no attribute. ### Triage Remainder of time left over for triaging C# 8.0 features. New tentative scheduling: https://github.com/dotnet/csharplang/projects/4 ### Attributes on local function parameters We realized that attributes are not permitted on local function parameters, which means that you cannot use the `EnumeratorCancellation` attribute in local function iterators. **Conclusion** Allow attributes on local function parameters and type parameters. ================================================ FILE: meetings/2019/LDM-2019-05-13.md ================================================ # C# Language Design Meeting for May 13th, 2019 ## Agenda 1. Close on desired rules for warning suppressions and `#nullable` interacting ## Discussion ### Warning suppressions and `#nullable` interacting The question is what project-level and source-level controls to expose for users to have a pleasant onboarding experience with the nullable feature. We previously leaned towards more flexibility, but want to consider streamlining the design in light of early feedback. We considered two dimensions of control: (1) whether the types are annotated or not (ie. what is the meaning of `string`?), and (2) whether nullability warnings are produced. At the project-level at least, all four permutations of those options (disable, enable, annotations-only, and warnings-only) are useful. | - | Annotations on | Annotations off | | ------------ | -------------- | --------------- | | Warnings on | enable | warnings | | Warnings off | annotations | disable | Annotations-only are useful for library authors who want to provide annotations for their APIs before they are ready to review their code. Warnings-only are useful for application consumers who want to benefit from annotations provided by libraries, but are not ready to annotate their own APIs. Both options offer a stepping stone towards enabling the feature fully. #### The need for both project-level and source-level controls Our starting model included the four settings at the project-level (`enable`, `disable`, `annotations` and `warnings`), as well as in source (with `#nullable (enable | disable | restore) [ warnings | annotations ]`). We observed that, in source, existing warnings cannot be enabled, they can only be suppressed. We considered whether this model could work for nullability warnings too. We don't think so: it creates a significant hurdle to adoption in existing projects. It is much easier to opt-in a few files than to opt-out all but a few files in a project. For nullability annotations, we considered the converse: should the only way of enabling nullability annotations be in source? Although this avoids language semantics (the meaning of `string`) being affected by compilation options, it seems undesirable in the long-term. We don't want every file to require some opt-in with `#nullable enable` in new or fully migrated projects. **Conclusion**: It is useful to control both nullable annotations and warnings at the project-level and in source. #### Clarifying the interactions A scenario was brought to light by the BCL team: when updating library, it is useful to temporarily disable nullability warnings at the project-level. This could be solved with the existing `/NoWarn` option, if it were extended to support `/NoWarn:nullable` (where `nullable` is a shorthand for the set of all nullable warnings, including the W-warning/`8600`). We iterated on this, trying to clarify how `#nullable enable warnings` would interact with `#pragma warning ...`. Two alternatives emerged: 1. `#nullable enable warnings` and `#pragma warning enable nullable` could be synonyms, 2. `#nullable enable warnings` could be controlling an independent flag that combines with the suppressed/unsuppressed state of a warning to determine whether the warning is produced at a certain position in source. The first design doesn't offer a very clear mental model when it comes to the order of precedence of various controls (project-level defaults, project-level `/NoWarn`, `#nullable ...` and `#pragma warning ...`). It also makes it harder to enable and disable all but a few nullability warnings: if you suppress one warning, then use `#nullable disable` and `#nullable enable` on a code block, you must re-iterate the suppression. Let's now spell out the second design: 1. There is a nullable warning context as a separate "bit". It can be set by default at project-level, and overridden with `#nullable` directives. It needs to be on to get nullable warnings at a given point in the source. 2. Individual warnings continue to be able to be individually disabled at the project-level with `/NoWarn` and disabled or restored in source with `#pragma warning ...`. 3. Additionally there is a "virtual warning identifier", `nullable` that can be used in `/NoWarn` at the project level, and that individually disables all warnings related to the nullable feature. This design allows common cases to be expressed simply: - enable nullable warning context at the project-level with `` set to `enable` or `warnings`, then adjust in source with `#nullable` directive, - enable nullable warning context in source with `#nullable` directive, - `/NoWarn` continues to suppress warnings, including all nullable warnings using the `nullable` virtual identifier and the W-warning with `8600`, except when a specific warning is enabled in source with `#pragma warning enable `. **Conclusion**: We're going to pursue the second design, based on "nullable warning context" bit. We tentatively decided to remove support for the `nullable` group from the `#pragma warning ...` directive. #### Names of options We considered some alternative naming schemes, to try and clarify the options: `warnings`/`annotations` `warningsOnly`/`annotationsOnly` `analysisOnly`/`annotationsOnly` **Conclusion**: There is a strong desire to use the same labels for the project-level (`...`) and source-level (`#nullable enable ...`) settings. We decided to keep the current names for now: `warnings` and `annotations`. We plan to revisit that. #### Removing support for `#pragma warning enable ...` The `enable` verb in the `#pragma warning` directive seems a general feature, orthogonal to the nullable feature. We feel we need to review it and confirm how it interacts with various mechanisms for suppressing warnings. **Conclusion**: We will leave this feature for now and revisit it later, including the `nullable` group identifier. #### Removing support for `safeonly` group identifier Is the recent decision to remove `safeonly` and `safeonlyWarnings` still holding, despite some concerns raised? **Conclusion**: Yes, `safeonly` is okay to remove at this point. `/NoWarn:8600` has the same effect. ================================================ FILE: meetings/2019/LDM-2019-05-15.md ================================================ # C# Language Design Meeting for May 15th, 2019 ## Agenda 1. Refining nullability signatures in APIs ## Proposal: Refining nullability signatures in APIs This proposes a set of language-level mechanisms (mainly in the form of attributes) that would provide for accurate annotation of the large majority of CoreLib members, and also eliminate a good number of warnings in the CoreLib implementations. The names of attributes need to undergo API review. The attributes count as “nullable annotations” (with the exception of the Flow attributes that aren’t directly related to nullability) and yield a warning if applied where the annotation context is disabled. They are part of the contract of the member, and as such they need to be respected “covariantly” by overrides and implementations that are also in an enabled annotation context. That is: an override can loosen a restriction or tighten a promise, not the other way around. ### Simple pre- and postconditions These are applied to an input and output, and specify an “override” to the type, in that they may limit to not-null or extend to maybe-null regardless of what the type says. These alone can take care of the bulk of CoreLib TODOs regarding imprecise signatures. It is important that both preconditions and postconditions are separately expressible, with individual attributes, since some annotatable entities are both input and output (e.g. ref parameters, fields and properties), and it is common in CoreLib for a restriction to apply only to the input or to the output. #### Simple preconditions These apply to anything that takes input: value, in and ref parameters, fields, properties, indexers, etc: - `[AllowNull]`: Allows null as the input, even if the type disallows it - `[DisallowNull]`: Disallows null as the input, even if the type allows it On invocation, preconditions are applied after any generic substitution, and are affected by the null-state of the input. `[AllowNull]` prevents any top-level nullability warning on the input, whereas `[DisallowNull]` emits a warning if the input state is “may be null”. Internally to the member, the preconditions may affect the initial null-state of the input (parameter, value variable, etc). #### Simple postconditions These apply to anything that yields output: out parameters, return values, fields, properties, indexers, etc: - `[MaybeNull]`: The output may be null, even if the type disallows it - `[NotNull]`: For outputs (`ref`/`out` parameters, return values), the output will not be null, even if the type allows it. For inputs (by-value/`in` parameters) the value passed is known not to be null when we return. On invocation, postconditions are applied after generic substitution, and affect the null state of the output. `[MaybeNull]` changes the null state of the output to “may be null”, whereas `[NotNull]` changes it to “not null”. Internally to the member, returning or assigning a value to an output element thus annotated may get or avoid warnings based on the null-state of the value. **Examples of simple pre- and post-conditions** ```csharp public class Array { // [AllowNull] on a non-nullable ref to express that nulls may be passed in, // but that nulls will not come out. public static void Resize([AllowNull] ref T[] array, int newSize); // Alternatively, [NotNull] on a nullable ref to express that nulls // may be passed in but will not be passed out. public static void Resize([NotNull] ref T[]? array, int newSize); } public class Array { // Result can be default(T) if no match is found [return: MaybeNull] public static T Find(T[] array, Predicate match); } public class TextWriter { // [AllowNull] on the setter of a non-nullable property to say that // the setter accepts nulls even though the getter will never return null. public virtual string NewLine { get => …; [AllowNull] set => …; } // Alternatively, [NotNull] on the getter of a nullable property to say that // the getter returns non-null even though the setter accepts null. public virtual string? NewLine { [NotNull] get => …; set => …; } } public class StrongBox { // Public field that defaults to default(T) public [MaybeNull] T Value; public StrongBox() {} } public class ThreadLocal { // Even when T is non-nullable, the getter will return default(T) // when accessed on a thread where a value hasn’t been set. [MaybeNull] get => … set => … } public interface IEqualityComparer { // Regardless of the nullability of T, GetHashCode doesn’t // allow nulls, but Equals does (whether [AllowNull] is needed // on the equals parameters is debatable). bool Equals([AllowNull] T x, [AllowNull] T y); int GetHashCode([DisallowNull] T obj); } internal static void SafeHandleHelper { // Null shouldn’t come in, but null can (and will) come out. public static void DisposeAndClear([DisallowNull] ref SafeHandle? handle); } ``` ### Value nulls There are places in CoreLib where only reference nulls need be prevented, but the large majority will throw on both value and reference nulls. It is not worth the complexity to distinguish these two cases; instead [DisallowNull] always prevents both value and reference nulls. The negative impact on the few places that can handle value nulls (such as IEqualityComparer.GetHashCode) is minimal. ### Nonnullable constraint For generic dictionary-like types in CoreLib, input elements of the key type should never be null. It is possible to use `[DisallowNull]` in most places where the key type is given as input, but it doesn’t work everywhere (e.g. doesn’t transfer to composed-in types, such as the key collection), and it diminishes the user value, because warnings aren’t yielded on generic instantiation with a null-unsafe key type, but only when members are called that take it as an input. For this reason, there should be a generic constraint, `nonnull`, that prevents the type argument from being nullable. Like the `[DisallowNull]` it applies to both nullable value types and nullable reference types, and yields a nullability warning when violated. While technically “correct”, we think object would be confusing as the name of the constraint, because it puts readers in mind of reference types. Additionally, since the nullable reference types feature is purely a manifestation of the language, the constraint should not result in an IL constraint that the runtime enforces, and since object is a type and types used as constraints are emitted to the IL as a constraint the runtime is aware of, it’s strange to use object but then not have it emitted. It should instead be nonnull and not be emitted as a base type constraint. Examples of non-nullable constraint: ```csharp public static class Marshal { // The method fills in the provided instance from the pointer, and thus // T shouldn’t be null. public static void PtrToStructure(IntPtr ptr, T structure) where T : nonnull // Alternatively, [DisallowNull] could be used. public static void PtrToStructure(IntPtr ptr, [DisallowNull] T structure) } // TKey on Dictionary, IDictionary, ConcurrentDictionary, etc. should not be null public class Dictionary : IDictionary, IDictionary, IReadOnlyDictionary, ISerializable, IDeserializationCallback where TKey : nonnull ``` ### Unconstrained “T?” Because nullability can be expressed on both input and output (with `[AllowNull]` and `[MaybeNull]`) in the few cases in CoreLib where it is needed in both directions it can be expressed using both attributes together. There is therefore no real need for the ability to say `T?` on an unconstrained `T`. Conversely, having unconstrained `T?` doesn’t obviate the need for `[AllowNull]` and `[MaybeNull]`, as they are needed for single-directional scenarios. Having `T?` would allow for the elimination of some warnings in generic code, notably around propagation and storage of `default(T)` values. However we consider that a separate potential improvement that could be added to the language later. ### Interdependent postconditions There is a long and varied list of APIs where the nullability of an input or output depends on other input or output of a method in various ways. It is not reasonable to express all of these patterns with attributes. Some are common patterns and should be expressed as attributes. Some occur on a short list of very commonly used members, and can be hardcoded into the compiler. A remaining long and thin tail will have to be left inexpressible. #### Conditional postconditions These are applied to parameters of bool-returning methods, and express that a given nullability applies to the parameter only when the method returns a given value. - `[MaybeNullWhen(bool)]`: If the method returns the `bool` value, the parameter may be null, even if the type disallows it - `[NotNullWhen(bool)]`: If the method returns the `bool` value, the parameter will not be null, even if the type allows it These can be applied to both input and output (i.e. to all kinds of parameters) because they may represent either a test on input, or an assignment to output. On the outside these are realized by affecting the conditional null-state (not null when true/false) after the invocation of the member. On the inside the conditional nature cannot be faithfully enforced. Instead, the most lenient of the two states is enforced unconditionally. The core libraries only have examples of `[MaybeNullWhen(false)]` and `[NotNullWhen(true)]` but we should offer all four combinations for generality and symmetry. Examples of conditional postconditions: ```csharp public class Version { // If it parses successfully, the Version will not be null. public static bool TryParse( string? input, [NotNullWhen(true)] out Version? Version); } public static class Semaphore { // Not just for parsing… public static bool TryOpenExisting( string name, [NotNullWhen(true)] out Semaphore? result); } public class Queue { // With unconstrained generics, we use the inverse public bool TryDequeue([MaybeNullWhen(false)] out T result) } public static class MemoryMarshal { // It applies as well with constrained generics. public static bool TryGetMemoryManager( ReadOnlyMemory memory, [NotNullWhen(true)] out TManager? manager) where TManager : MemoryManager } public class String { // Not just for outs… also relevant to inferrig nullability of // input arguments based on the return value. public static bool IsNullOrEmpty([NotNullWhen(true)] string? value) } ``` #### Nullness dependencies between inputs and outputs Another common pattern is an output being non-null if a particular input is non-null. We can codify that with another attribute: - `[NotNullIfNotNull(string)]`: Applied to a return, ref, or out to indicate that its result will be non-null if the parameter specified by name is non-null On the outside, these attributes are realized by giving the annotated output a not-null state if the input specified by name is not-null. On the inside this does not realistically seem enforceable. Examples of dependencies between inputs and outputs: ```csharp class Path { [return: NotNullIfNotNull(nameof(path))] public static string? GetFileName(string? path); } class RegistryKey { [return: NotNullIfNotNull(nameof(defaultValue))] public object? GetValue(string name, object? defaultValue) } class Interlocked { public static T Exchange([NotNullIfNotNull(nameof(value))] ref T location1, T value) where T : class? } class Volatile { public static void Write([NotNullIfNotNull(nameof(value))] ref T location, T value) where T : class? [return: NotNullIfNotNull(nameof(location))] public static T Read(ref T location); } class Delegate { [return: NotNullIfNotNull(nameof(a))] [return: NotNullIfNotNull(nameof(b))] public static Delegate? Combine(Delegate? a, Delegate? b) } ``` #### Equality postconditions Certain members are equality tests on their inputs, and if the result is true (or, in the case of `!=`, false) the nullability of the two parameters is equal. Between the language and CoreLib, there is a small fixed set of equality methods and operators, and people are unlikely to add new ones. Therefore it is not worth creating attributes for these, and they should instead be hardcoded into the compiler. Examples of equality: - `Object.ReferenceEquals` - `Object.Equals` - `IEqualityComparer.Equals` - `EqualityComparer.Equals` - `IEquatable.Equals ` #### CompareExchange Of the remaining members with special null behavior, the one that sees by far the most use is Interlocked.CompareExchange. It’s nullability contract is way too complex to reasonably express through attributes, but should be hardcoded into the compiler due to the amount of usage that the method sees. There are two relevant overloads: ```csharp public static extern object? CompareExchange( ref object? location1, object? value, object? comparand); public static T CompareExchange( ref T location1, T value, T comparand) where T : class? ``` The most important case to infer here: If comparand is a constant null and value is non-null, location will be non-null on return. That covers the very common case of lazy initialization: ```csharp if (_lazyValue == null) Interlocked.CompareExchange(ref _lazyValue, new Value(), null); return _lazyValue; ``` Additionally as a bonus: If `location1` and value are both non-null on enter, `location1` and the result value will be non-null on return. ### Flow attributes Since nullability analysis is affected by control flow, it is valuable to signal when a method affects control flow, even if it is not directly related to nullability of the method’s inputs and outputs. Specifically, if a method does not return, unconditionally or conditionally, the compiler can benefit from this information to reduce unnecessary warnings. - `[DoesNotReturn]`: Placed on the method. Code after the call is unreachable - `[DoesNotReturnIf(bool)]`: Placed on a bool parameter. Code after the call is unreachable if the parameter has the specified bool value Examples of flow attributes: ```chsarp public static class Environment { [DoesNotReturn] public static void FailFast(string message); } public class ExceptionDispatchInfo { [DoesNotReturn] public void Throw(); } internal static class ThrowHelper { [DoesNotReturn] public static void ThrowArgumentNullException(ExceptionArgument arg); } public static class Debug { public static void Assert([DoesNotReturnIf(false)] bool condition); } ``` ## Discussion The guiding priorities for this proposal are how the attributes: 1. affect the caller, 2. affect OHI, 3. affect the implementer. There is a desire for the attributes to also affect the implementation side (for example, `[MaybeNull]` on a method return would mean fewer warnings on return statements), but we did not focus on that as much. ### Simple pre- and postconditions There is some redundancy for non-generic cases. For example, you could use `[AllowNull] string` or `string?`. We'll need to provide some guidance. Open question: should those attributes be applied to properties or to accessors? ### `nonnull` constraint The `nonnull` constraint cannot be combined with a `struct` constraint, because `struct` constraint already implies non-null. `T?` is only allowed if `T` is constrained to `class`, a class type, or `struct`. `T?` is disallowed if `T` is constrained to `class?` or `nonnull`. ### Conditional postconditions Note that `[MaybeNullWhen(false)]` and `[NotNullWhen(true)]` are different in generic case (see `TryDequeue` in examples). ### Nullness dependencies between inputs and outputs We cannot use `nameof` in `[NotNullIfNotNull(nameof(parameter))]`, because the parameter is only in scope inside the method body. In overrides, implementers will have to use the parameter name of the implementation (`[NotNullIfNotNull("overriddenParameter")]`), not of the overridden method. Open question: what if the name passed to this attribute doesn't match a parameter name? Is that an error or warning? ### Equality postconditions If an equality returns true and one of the parameters has non-null state, then we can set the state of the other parameter to non-null. We'll need to revisit the rules for recognizing an equality method. We may be able to use a more general pattern, instead of a list of well-known members. We should also consider whether nullability analysis of an equality should account for a `null` literal ### Flow attributes Open question: would we also need a `[DoesNotReturnWhenNull(nameof(parameter))]`? ================================================ FILE: meetings/2019/LDM-2019-07-10.md ================================================ # C# LDM Notes ## Agenda 1. Empty switch statement 1. `[DoesNotReturn]` attribute 1. Revisiting the `param!` null-checking feature ## Discussion ### Empty switch expression Should the empty switch expression (`expr switch { }`) be an error? **Conclusion** We can't really find a reason to disallow this. ### DoesNotReturn The current design for the `DoesNotReturnAttribute` indicates that the method annotated with it is unreachable after a call to it for the purpose of flow analysis. The questions are: 1. Confirm that it only implies unreachability for nullability 2. Confirm the name, which doesn't mention nullability **Conclusion** Yes to both questions. If we want to add general-case unreachability later we will do it through a different mechanism. That would require altered code generation (to ensure both verifiability and that the code after is truly unreachable) and is out of scope for the current feature set. ### `param!` We previously discussed the `param!` feature, which is meant to insert a `throw` if the parameter is null. We'd like to confirm some of the details. Illegal placement: We think there should be diagnostics for the follow constructs: - Warning for default parameter value being null (e.g. `void M(string p! = null)`) - Error on an out parameter - Error on methods without bodies - Including the declaration part of a partial - Warning if the type of the parameter is a nullable reference type or nullable value type - No place to put `!` on the property setter Semantics: - Should work with `notnull` constraint - Confirmed that the null checks should be the first possible code - Including before field initializers, iterator kick-off, constructor chaining - Do we want to use a well-known helper instead of `throw`? - CoreFX uses throw helpers, so it wouldn't be able to use the feature Syntax: It was previously mentioned that the syntax could be confusing because `!` has different meanings in expressions or parameter names. Do we want to change the syntax? Here are some syntax options we discussed: 1. `T p!` 2. `T p!!` 3. `T! p` 4. `checked T p` 6. `nonnull T p` We can't decide on a syntax right now. There are concerns that some of the proposed syntaxes are too verbose and remove the purpose of the feature. There's also a possible follow-on proposal: right now the `!` syntax already has meaning in an expression context, but if we used a different syntax, like `!!`, we could also add a "null-checked expression" which does the same thing for expressions that it does for parameters. These ideas are interesting enough that we think we should hold the feature for the next C# version and discuss these possibilities more. **Conclusion** Some revisions to the feature and some open questions. We want the feature in some form and are committed to resolving these questions for the next release. ================================================ FILE: meetings/2019/LDM-2019-07-17.md ================================================ # C# LDM Notes for July 17, 2019 ## Agenda 1. [Nullability of events](https://github.com/dotnet/roslyn/issues/34982) 2. Triage ## Discussion ### Field-like events This discussion was spurred by the current implementation, which treats field-like events as fields, meaning non-nullable unless annotated, and requiring initialization in the constructor if non-nullable. Based on design decisions around our non-nullable field warnings and the current annotation implementation of events, we recognized a potential problem. For almost every event, the behavior would be nullable, but it seems laborious to require every event to be annotated with nullable, when that is the usage for almost every event. It was also noted that field-like events also imply multiple pieces of generated code: a backing field, which can only be directly accessed in the containing type, and generated add/remove accessors that can be accessed everywhere. Notably, the nullability of the generated field must be compatible with, but not identical to to the generated accessors. Possibilities: 1. What you write is what you get - Both the event and field have the declared type, and if it's nonnullable, you're forced to initialize in the constructor 2. The backing field is always nullable - Would not allow assigning an empty delegate in the constructor and then invoking without a null check 3. Force implementing manually 4. Use attributes, perhaps [MaybeNull] 5. Events are always nullable - This is (2), but the += and -= are also nullable Notably, the `+=` and `-=` generated for field-like events *is* null safe, in that you can pass null as a delegate argument and it does not produce a null reference exception. Even though it's painful, (1) seems to be attractive because it doesn't heavily special case events, and `EventHandler?` accurately reflects the nullability of the event, in that it allows nullable handlers to be subscribed, and invoking the event could cause a null reference exception. A problem is that unassigned non-null events as currently implemented produce a warning that complains about the event being unassigned, when in almost all situations the proper fix is to declare the event type as nullable. (2) would match the usage of almost all fields, but at the expense of inconsistency, since we usually require `?` to annotate nullable items, and events would be special-cased to be nullable by default. It would also disallow the pattern of assigning an empty delegate in the constructor. **Conclusion** For most users, making events nullable seems like the right behavior. (5) would address this in the simplest way, but we really don't like the inconsistency of making types nullable without a '?'. (1) allows events to be annotated as nullable, which is what we expect most people should do. We're sticking with (1). Current behavior is confirmed. ## Triage for the rest of the meeting ### Support XML doc comments on local functions It's especially frustrating that XML doc comments in local functions are an error. It also seems useful in larger methods where you want to see the contract for a local function in intellisense. However, the second reason is also a good reason to allow XML doc comments for locals. **Conclusion** Interested, but we want to see what the possibilities are on other locals. ### Warn on obsoleting overrides of non-obsolete members It might be useful, but is low value. Since it would be a new warning, this is blocked on warning waves. **Conclusion** We will revisit when we get warning waves. ### Proposals for ConfigureAwait with context ConfigureAwait and related issues have been brought up many times. We think it's worth addressing, and should look at possible designs. **Conclusion** Let's look it for a future major release. ================================================ FILE: meetings/2019/LDM-2019-07-22.md ================================================ # C# LDM Notes for July 22, 2019 ## Agenda 1. Discuss the [new records proposal](https://github.com/dotnet/csharplang/blob/856c335cc584eda2178f0604cc845ef200d89f97/proposals/recordsv2.md) ## Discussion We started by going over the proposal. A few comments and clarifications: #### Optional and required initialization The proposal doesn't mention what we consider a problem in existing C# object initializer code: there's no way to specific that members are required or optional. Most notably, a class has no way to force an object initializer to set a member, and no diagnostics will be reported if it is skipped. The new proposal would produce an error if an `initonly` member is not set either in the constructor, in an initializer, or in the object initializer. A suggested implementation would be to emit an attribute that indicates whether a member initialization is required or optional, but we do not mandate a specific implementation strategy. ### Verification The philosophy behind the proposed "initonly" verification rules are that we're broadening the CLR's rules slightly. In the CLR, `initonly` (which is termed `readonly` in C#) makes the claim that the end of the constructor is the publish boundary for a type, so `initonly` fields are settable until the constructor finishes, at which point the object is published. We're widening this slightly, by creating rules that ensure that even after the constructor has finished, values can be set before the item is actually published either via a store or a method call. As long as these rules could be efficiently written for a theoretical verifier, we don't strictly need to have an implementation concurrent with the feature. ### With-constructor It also looks like some of the compilicated codegen around the body of the `With` constructor may be unnecessary. The default implementation may be completely satisfied by `object.MemberwiseClone()`. However, a With-constructor would likely still be necessary, as `object.MemberwiseClone()` is protected, and we would like to allow for customizability. ### Mutability and code generation For classes, it would also be good to warn if mutable members are included in a `data` class, because structural equality is dangerous in the presence of mutability (e.g., if a class is changed after being added to a dictionary, it can "disappear" from the dictionary). ### Primary constructors and attributes The proposed use for primary constructors is short and simple data types. It was noted that many serialization frameworks require a large set of attributes to specify serialization details. For the primary constructor feature, it's an open question of how attributes will work. There is a proposal for putting attributes on the parameters with an additional target, e.g. ```C# data class C([property: MyAttribute]int X); ``` However, this harms the concision of the feature and may mean that primary constructors are not as useful for serialized types. ### Criticisms of new proposal vs original One major difference between the new proposal and the old proposal is that it implies the existence of a record without a primary constructor. The original motivation for this was to avoid encoding the constructor's positional API (constructors are dependent on argument order) into record construction and use. Thus, to differentiate the two proposals we can refer to them as "nominal" records (records V2) and "positional" records (records V1). * Primary constructors as a whole: it's not clear if the pieces are actually that useful outside of the records proposal. * Primary constructors + initialization. If people like object initializers, one possible solution is what is proposed here. Another possible solution is to make an object initializer for positional records as a simple syntactic transformation to a record constructor. * Source/Binary compatibility. Positional records do not solve the problems of adding or re-ordering members, but it is solvable by manually writing back-compat constructors when adding members. * Use cases: it seems like the common case for a few fields (like the UserInfo example) would be more the positional record case. Instead, the common case for nominal records would be big records, like Roslyn's CompilationOptions. * Validation: there's no way to validate the consistency of the whole object in this proposal. For instance, many records have requirements that if one field is set, another is set as well, or that they have compatible values. Unfortunately, this is also true of "positional" records, since the With-ers would set values after the construction of the copy completes, meaning that no validation run in the constructor would validate the "With"-ed members. One way to add this functionality would be to enforce a call to a well-known validation method after construction is done. ================================================ FILE: meetings/2019/LDM-2019-08-26.md ================================================ # C# Language Design Notes for Aug 26, 2019 ## Agenda Triage of newly championed issues. Features that we may consider are placed in the milestone where we will look at them again. Listed here by target milestone. # Milestone 8.X We are unsure if there will be an 8.1 release or we will go straight to C# 9.0. In the latter case we will triage this bucket and move things to 9.0 or later. ## #883 Zero and one-element tuples We don't have a good syntax for one-tuples. But in deconstruction and pattern matching there is less of a syntax problem. What does `(3)` mean? It is a constant 3. If you want a tuple literal, you can just give it a name - that is unambiguous. `(_: 3)` might become the common pattern, though `_` isn't a discard for tuple element names. Could consider splitting the feature and doing only one-element tuples in literals and deconstruction. # Milestone 9.0 This is the next major release (as C# 8.0 is a done deal at this point). Putting features in this milestone doesn't mean that we will do them here! It just means that we will consider them in this timeframe - possibly to be rejected or pushed out at that point. ## #146 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 Could work with conjunctive patterns. An alternative would be a range-like syntax. ## #1792, #1793, #1502 Let's keep ironing out annoying limitations in this space ## #1881 Should think also of UTF8 For `Memory` etc we would probably continue to require you to go through `Span` etc. ## #2585 Fits well with a "math" push, and should align with that. # Milestone X.0 These would be major features, and we don't expect to consider them for C# 9.0. ## #339 This is a very fundamental feature, and we're not sure we have compelling scenarios. Possibly the next thing to look at after "type classes" in the advanced type features category. ## #1047 This is probably in pattern matching's future somewhere, though we won't be ready for it in 9.0. ## #538 Probably requires runtime work, and the scenarios don't currently justify that. ## #2545 (Blocked) We keep not moving on this. We'd need a good understanding with EF # Milestone X.X These would be minor features which we don't expect to consider until after 9.0. ## #2608 Fine, but doesn't rise to priority # Rejected ## #301 This is subsumed by "extension everything" and the other proposals in that ilk. We do understand and support the scenario, but don't want the specific feature. ## #1033 Rejected. The assignment should happen explicitly in the body, and the compiler can warn if you forget. ## #1586 It shipped and it's in unsafe. We're ok with it. ## #2383 The language is what it is at this point, and it is not worth doing work to "fix" it. However we should add a warning wave warning on the initializers saying they won't ever be called. ================================================ FILE: meetings/2019/LDM-2019-08-28.md ================================================ # C# Language Design Notes for Aug 28, 2019 ## Agenda 1. Triage ## Discussion ### Hiding with optional parameters If you define a method with optional parameters with the same name as a method in a base class, we do not provide a warning that the method is hiding the method in the base class, and if you put the `new` keyword, that provides a warning. Do we want to provide a warning in a warning wave that the method is hiding and some mechanism of silencing the warning (presumably allowing `new`). **Conclusion** If warning waves are here, we're comfortable taking this. ### `params Span` and string formatting There are a lot of important performance implications here. We think it should be done soon, for the latest major release. **Conclusion** Agreed, next major release. (C# 9.0) ### Allow `sizeof` in safe code There have been requests for this over the years, but we're not sure of the actual value of the feature. **Conclusion** Rejected, until we hear compelling use cases. ### `Tail return` recursive calls We could do "immediate" recursion (directly calling the containing method) without depending on the runtime because we could implement it using compiler rewrites, but it does seem like we would want to support mutual recursion or even just tail-dispatch. This would require us to either accept the limitations in the CLR where a `tail` is not always done in constant space, or require a CLR feature that guarantees constant space However, we've done without tail calls for a quite a long time and there are some workarounds. **Conclusion** Let's talk about this later, for a C# 10.0. ### Variant method overrides We previously pushed out overrides with variance (covariant returns, contravariant parameters) due to requiring quadratic stubs to be emitted for the runtime. Now we can explore runtime changes to support variance directly, where the runtime would allow overrides to differ with "compatible" signatures, instead of exact matches, which is what is required right now. **Conclusion** There's a lot of existing use cases for this feature, and there's some feeling that it may be required for the records feature. Discussion with the runtime says that it would not be very expensive, and is tractable for the next major release. We're scheduling this for C# 9.0. ### Exponentiation operator (`**`) This seems to be the major missing mathematical operator in C#. We like it as part of a broader improvement to mathematical generalization that we're planning in the "shapes" proposals. **Conclusion** C# 10.0, matched up with shapes. ### `base(T)` with runtime changes This is a follow-up feature for C# 8.0, so we'd like to do it as quickly possible. **Conclusion** Next major release, C# 9.0. ### Switching on `ReadOnlySpan` with constant strings We aren't ready to commit resources to it, since we haven't found blocking issues, but we'd take the improvement whenever it's ready. **Conclusion** Any time. ### Permit a fixed field to be declared inside a readonly struct We'd like to address performance issues soon if they're impactful enough. **Conclusion** Keep it in C# 9.0, pending confirmation of performance impact. ### Safe fixed-size buffers This one has some compelling perf scenarios, but we don't have a clear design nailed down. **Conclusion** We'd like to take in a major release, but we need a solid design first. ### Comparison, `and`/`or`/`not` patterns These seem useful and they are most useful with the `and`/`or` patterns. **Conclusion** This doesn't seem very big and we think it could fit in any release. C# 9.0 for now. Same for `and`/`or` patterns. ### Dictionary Literals Initialization of collection data structures, including immutable data structures, is a known issue, in both performance and ergonomics. This proposal doesn't really address these problems and special cases Dictionary, even though we know the problem also exists for other types. **Conclusion** The proposal in its current form is rejected. We'd rather try to address more problems in a single feature, potentially after the records feature, which has proposals around initialization of immutable types. ### No-arg constructor/non-defaultable value types We're not very excited about it, but there are valid use cases. We previously pulled the feature because there was a runtime bug that did not call the constructor in certain cases. That bug has now been fixed. **Conclusion** ### Target-typed conditional expression We know it's useful and we have a design that we think would work. **Conclusion** We would like to do this for C# 9.0 ### Surrogate pairs in Unicode-escaped code points in identifiers The language spec already says this is allowed. **Conclusion** If we have a viable implementation, we'll take it whenever. ### Permit conditional operation with `int?` and double operands This is separate from target-typing because it would change the common type algorithm, but it also interacts with the target-typing, so we probably need to do them together. **Conclusion** Discuss before C# 9.0 ================================================ FILE: meetings/2019/LDM-2019-09-04.md ================================================ # C# Language Design Meeting for September 4, 2019 ## Agenda 1. `[AllowNull]` on properties ## Discussion [https://github.com/dotnet/roslyn/issues/37313](https://github.com/dotnet/roslyn/issues/37313) ### AllowNull and flow analysis Some questions have come up concerning `AllowNull` and property analysis, like in the following example: ```C# class Program { private string _f = string.Empty; [AllowNull] public string P { get => _f; set => _f = value ?? string.Empty; } static void Main(string[] args) { var p = new Program(); p.P = null; // No warning, as expected Console.Write(p.P.Length); // unexpected warning } } ``` The tricky part here is that we are tracking flow state for properties, meaning if `null` is stored in a property, we treat that property as having a null state. Although there is an attribute, the flow state of the variable wins, meaning that we think there is a `null` inside `P`, even though the stated type is `string`. Similarly, if you invert the attribute and use `[NotNull]` on a `string?`, the flow state will win again. There a couple ideas to address the problem, including 1. Don't flow-track non-nullable fields/properties. 2. Have `[AllowNull]` suppress both the warning, and suppress setting the flow state to `null` 3. Have the `[NotNull]` attribute suppress the flow state (which it currently doesn't), and require the property be written with a nullable type (`string?`) and have `[NotNull]` on the getter. 4. Stop tracking when any nullability attributes are present on properties. 2 and 3 are somewhat related and we could do both, in theory. The main problem is that it greatly complicates the user's understanding of when flow tracking is enabled, and there are also potentially downstream affects for type inference if we allow the rule for (2) to apply to parameters. For (1) it seems plausible, since we would still have a warning on assigning a null to a non-nullable member. This would remove the flow-based subsequent warnings, but the user would still be warned at the point where the problem happens. However, it doesn't seem to solve the problem for generics, e.g. ```C# class C where T : new() { private T _f = new T(); [AllowNull] public T P { get => _f ?? throw new Exception(); set { if (value is null) { throw new Exception(); } return _f; } } } ``` Here the generic property `P` cannot be marked nullable because it is unconstrained. However, 2 & 3 also have a problem around `MaybeNull`. If a property is annotated `MaybeNull`, then the attribute would override the state, meaning that a null check is useless. If you check for null it would not matter, because when you read the property again, the attribute would override the state and the result would still be maybe null. An idea to address this is a combination of (4) and (3), where we have special attribute behavior for properties, and in that case `NotNull` has precedence over nullable state, but nullable state has precedence over `MaybeNull`. In addition, `AllowNull` modifies the state transition to use the declared state if the input is null, while it otherwise uses the non-null state. **Conclusion** The proposal starting point is: NotNull wins over tracked state, which wins over MaybeNull. AllowNull transforms an incoming maybe null state to the declared state. There's an action item to go investigate how this will play into the rest nullability, and an open question of whether to treat fields like properties, or like local variables. ================================================ FILE: meetings/2019/LDM-2019-09-11.md ================================================ # C# Language Design Meeting for Sep. 11, 2019 ## Agenda ## Discussion ### Nullable attributes and flow state interaction We started this discussion with an email with a proposal based on follow-up research from the previous meeting. ### Attribute interaction proposal > Allowed inputs and outputs: > > First of all, I’ll try to make rules based only on “allowed inputs” and “allowed outputs” of a > property. I’ll use shorthands ?, ! and T for “nullable”, “nonnullable” and “unknown – depends on > T” respectively. > > For all the different sensible combinations of attributes, here are the “allowed inputs” and “allowed outputs”: > | | Allowed input | Allowed output | > |----------------------------|---------------|----------------| > | string | ! | ! | > | [AllowNull]string | ? | ! | > | [NotNull]string? | ? | ! | > | [MaybeNull]string | ! | ? | > | [DisallowNull]string? | ! | ? | > | string? | ? | ? | > | [DisallowNull][NotNull]T | ! | ! | > | [NotNull]T | T | ! | > | [AllowNull][NotNull]T | ? | ! | > | [DisallowNull]T | ! | T | > | T | T | T | > | [AllowNull]T | ? | T | > | [DisallowNull][MaybeNull]T | ! | ? | > | [MaybeNull]T | T | ? | > | [AllowNull][MaybeNull]T | ? | ? | > There should be no surprises to anyone there. Now let’s use these to define the different behaviors around properties: > Ordering of states: T is stricter than ? and ! is stricter than both T and ?. > This is a measure of relative permissiveness of states. > Initial state: The initial state of a property is its allowed output. > This corresponds to us knowing nothing about the property yet, beyond what it tells us through a combination of its type and its postconditions. > State after null check: > > - On the non-null branch of a null check the state of the property is !. > - On the null branch of a pure null check the state of the property is ?. > - Elsewhere the state of the property is unchanged. > > These rules reflect the general benefit of a null check, as well as the overriding effect of a pure null check even of a nonnull property. Note that this rules out "dangerous" properties, meaning properties that may change outside the scope of the nullable analysis, as in fields which may be changed by a different thread, and thus the null check is unreliable. We don't consider this scenario to be in scope of our current design and if we decide to address this, we must create a new attribute or some other mechanism. There's also some problem with the state after null checks, namely that the type may not support the `?` state. For instance, in the following unconstrained generic, ```C# T M(T t) { if (t != null) t.ToString(); return t; } ``` we should not produce a warning on the return, since the state should match the legal state of `T`, which is `T`, not `?`. Similarly, for non-Nullable value types, the state cannot be `?` after a null check, since the value cannot be null. The rule should use the `T` state. > State after assignment: > > The state of the property after an assignment is > > - Its initial state if the state of the assigned value is at least as strict as the allowed input, but no stricter than the allowed output > - The state of the assigned value otherwise > > This rule reflects that a property is expected to “take care of things” when the state of an assigned value is valid as input but not as output. It does so by assuming that the resulting state in such situations is something that’s valid as output. > > This is probably the only rule that would differ from the rules for fields, which would continue to always use the state of the assigned value. > Warnings on assignment: A warning is yielded if the state of the assigned value is less strict than the allowed input. > This is the same rule as all other input positions. We like this new "state after assignment" rule and think it can be implemented now. However, when looking into the solution, we found that this is the current behavior for properties when annotated: ```C# using System; using System.Diagnostics.CodeAnalysis; #nullable enable class C where T : class? { public C(T x) => f = x; T f; T P1 { get => f; set => f = value; } [AllowNull] T P2 { get => f; set => f = value ?? throw new ArgumentNullException(); } [MaybeNull] T P3 { get => default!; set => f = value; } void M() { P1 = null; // Warning P2 = null; // No warning P3 = null; // Warning f = P1; // No warning f = P2; // No warning f = P3; // BUG?: No warning! } } ``` That last line does not look right. Similar to `default(T)`, you could be producing a potentially nullable value, when the substituted type may not permit it. We think the three state domain outlined above will solve the problem, but that would be too extensive to change to perform in such a short period of time. Alternative: whenever you introduce a value (by calling a property or a method), that produces a generic type annotated with `[MaybeNull]` in a substituted generic method, that would produce a warning. This matches our current behavior for `default(T)`. For example, ```C# T M() { _ = (new List).FirstOrDefault(); // this would now produce a warning } ``` **Conclusion** Let's implement the "state after assignment" rule as defined and implement the "Alternative" proposal outlined above. We will consider updating to use the "three state domain" above later, which may have some further changes. A sample of the expected behavior follows: Non-Generic ```C# using System; using System.Diagnostics.CodeAnalysis; class Widget { string _description = string.Empty; [AllowNull] string Description { get => _description; set => _description = value ?? string.Empty; } static void Test(Widget w) { w.Description = null; // ok Console.WriteLine(w.Description.ToUpper()); // ok if (w.Description == null) { Console.WriteLine(w.Description.ToUpper()); // warning } } } ``` Generic ```C# using System; using System.Diagnostics.CodeAnalysis; class Box { T _value; [AllowNull] T Value { get => _value; set { if (value != null) { _value = value; } } } static void TestConstrained(Box box) where U : class { box.Value = null; // ok Console.WriteLine(box.Value.ToString()); // ok if (box.Value == null) { Console.WriteLine(box.Value.ToString()); // warning } } static void TestUnconstrained(Box box, U value) { box.Value = default(U); // 'default(U)' always produces a warning when U could be a non-nullable reference type Console.WriteLine(box.Value.ToString()); // ok box.Value = value; // ok Console.WriteLine(box.Value.ToString()); // ok if (box.Value == null) { Console.WriteLine(box.Value.ToString()); // warning } } } ``` ## More triage ### Top-level statements and member declarations We have a variety of different use cases and experimental products (C# Interactive Window, Jupyter projects, try.net, etc) that use the current "C# scripting" language, which is already effectively a dialect of C#. There's a fair amount of concern that if adoption continues, we may produce a fracturing of the C# language. However, adding top-level statements and reconciling scripting in C# proper would be an expensive feature, in both design and implementation. It also doesn't directly impact many of the designs we're currently considering. But there is also significant cost to doing nothing. We have not considered the semantic for many features in C# 8, or even if they should work in scripting (`using` declarations, notably). There is a significant ongoing cost here, either in considering all our designs for the scripting dialect, or in risk that not doing design/implementation work will cause bad experiences for products using the C# scripting code. **Conclusion** We'll schedule this for 9.0, to at least examine options. ### Primary constructors This occupies the same design space as records, which is scheduled for 9.0, so we at least need to consider this feature while implementing records. **Conclusion** Moving to 9.0. ### Negated-condition if statement Issue #882 This overlaps significantly with a "is not" pattern. We're not confident this feature has significant value, after the "is not" pattern is implemented. **Conclusion** Move to X.X to consider after "is not" has shipped and see if there are significant use cases that are not addressed by the "is not" pattern. ### Allow `default` in deconstruction Issue #1394 The primary use case is `(x, y, z) = default;` instead of naming each variable individually. There are some issues around the written specification, specifically on what target typing `default` has. **Conclusion** From a consistency perspective it seems like this should work, regardless of the complexity in details of the specification. We'll take this Any Time whenever we have a solid specification and implementation. ### Partial type inference Issue #1349 Nothing that's related to type inference is a tiny feature, but this is pretty small as type inference changes are concerned. We think the hardest problem will be agreeing on the syntax. Agreed that it could be useful, though. **Conclusion** We'll take this Any Time. ### Declaration expressions Issue #973 Somewhat related is "sequence expressions". This is useful for declaring variables inline in an expression. There are places where statements are not possible, and this requires refactoring. **Conclusion** We don't think there's value in half measures here. We think going all the way to sequence expressions may have value, but then declaration expressions do not. ================================================ FILE: meetings/2019/LDM-2019-09-16.md ================================================ # C# Language Design Meeting for September 16, 2019 ## Agenda 1. UTF-8 strings (https://github.com/dotnet/corefxlab/issues/2350) 2. Triage ## Discussion ### UTF-8 Strings Motivation: UTF-8 is everywhere, and converting to and from the .NET native string type (UCS2/UTF-16), can be expensive and confusing. #### Language impact **Literals** Current proposal is to emit the data as UTF-16, and use a runtime helper to project to UTF-8. The main question for the language is if this encoding limits the usability in some way, and whether it blocks us off from introducing an optimal strategy later. There are two things to consider: the cost in the compiler, and the cost to the language. At the moment we don't see anything that would be considered a breaking change in the language. **Enumeration** The proposal doesn't include any default enumeration. It's worth considering if this violates C# user expectations. There are different forms of enumeration available by calling properties, but none of them are the default. Should there be a default? One problem is that users who see enumeration may expect indexing, which is not cheap, and does not match expectations. Another problem is that there is almost always a better operation than enumerating a UTF-8 string. It seems like adding this more likely to encourage a user to write a bug, or inefficient code, than to help them. **Why language support** The main advantages of language support are: * O(n) string concatenation (calling utf8string.Concat with all `n` arguments) * String literals **Target-typing** Target-typing of existing string literals is possible but produces "bad" behavior for seemingly the most common scenario: ```C# void M(string s) { ... } void M(Utf8String s) { ... } M("some string"); // This would call the `string` version, because backwards compatibility requires // "" always be a `string` first, and a `Utf8String` second ``` **Syntax** As always, there's debate about the syntax. The "u" prefix is somewhat unsatisfying because UTF-8, UTF-16, UCS-2, *and* `unsigned` all begin with "u". The same complaints hold for the proposed `ustring` contextual keyword. However, there are no enthusiastically favored alternatives. In addition, the value of the `ustring` contextual keyword seems questionable. If the brevity is important, then it seems important enough that the framework could call it `ustring` or `utf8`. One argument is that Utf8String could be a new "primitive" type, and we should add a keyword for all primitive types. However, this is not proposed as a primitive type at the same level, so that weakens support. In addition, the original aliases were all added at the inception of the language, so we have no precedence for adding either primitive types or aliases. **Conclusion** We think the feature is valuable and probably worth some language support. We think a literal syntax (like the "u" prefix) is good, but we don't like target typing. We're also not convinced of the need for a contextual keyword. ### More triage #### Null parameter checking Issue #2145 ASAP, 9.0 ### CallerArgumentExpression Issue #287 We could potentially use this to implement "Null parameter checking" as a method call, instead of new syntax. Thus, consider for 9.0. ### Relax ordering constraints about modifiers (especially `ref` and `partial`) Issue #946 9.0 ### Zero- and one-element tuples Issue #883 In terms of language value, zero- and one-element tuples are different features. Zero-element tuples are most useful as a unit type, which is not necessarily the unit type we would choose to standardize on (e.g. against System.Void), while one-element tuples are simply useful as a wrapper type and don't have an obvious competitor. We need to revisit the decisions here. Moving to X.X ### Mix declarations and variable in deconstruction Issue #125 Not an urgent feature, but useful for completeness. On the other hand, it's always very clear whether or not the deconstruction is using existing variables, or creating new ones. Any time ### Discard for lambda parameters Issue #111 Any time ================================================ FILE: meetings/2019/LDM-2019-09-18.md ================================================ # C# Language Design Meeting for September 18th, 2019 ## Agenda Triage: 1. Proposals with complete designs: - https://github.com/dotnet/csharplang/issues/1888 Champion "Permit attributes on local functions" - https://github.com/dotnet/csharplang/issues/100 Champion "Target-typed new expression" 2. Target typing and best-common-type features: - https://github.com/dotnet/csharplang/issues/2473 Proposal: Target typed null coalescing (??) expression - https://github.com/dotnet/csharplang/issues/2460 Champion: target-typed conditional expression - https://github.com/dotnet/csharplang/issues/881 Permit ternary operation with int? and double operands - https://github.com/dotnet/csharplang/issues/33 Champion "Nullable-enhanced common type" ## Discussion ### Attributes on local functions Issue #1888 The only major concern about the feature is whether or not the attributes are guaranteed to be emitted to the lowered method, assuming there is a lowered method. For all current lowering strategies, there is a specific place to put the attributes. The proposal is that we always emit attributes for our current scenarios, but don't guarantee in the language that all attributes will survive rewriting. There is one exception to the above, which is static local functions. Static local functions are specified to always emit a static method with the same signature as the local function (although there are bugs about this today). Attributes on static local function would have a similar guarantee, meaning that they are always emitted to the lowered methods, on the exact same parameters they were applied to. **Conclusion** Proposal accepted, attributes will be allowed on local functions. `extern` will also be allowed on static local functions, and will be a valid P/Invoke target. ### Target-typed new Issue #100 We like the feature and already have a design. However, we want to do a quick review that we still like the design, taking into account all the changes we've made to the language since it was proposed. **Conclusion** Accepted, pending review. ### Target-typed conditional expression and nullable-enhanced common type Issue #2460 and issue #33 The main problem is that providing a target-typing for the switch expression may have made issue #33 a breaking change. In general, additional target-typing (on top of a natural type) works by calculating the target type if there is no viable natural type. By allowing more natural types, it reduces the ability to use target-typing, which may allow more resulting types than the natural type inference. Overload resolution is a good example. If you have two methods ```C# void M(int? x) { ... } void M(short? y) { ... } ``` and a call `M(e switch { ... => 1, ... => null })`, when we do overload resolution we consider the target type for the `switch`, which consists of the target type for each of the candidates. For target type, `short` is a better target for `int`. If we improved the natural type, then the natural type of `1` would be `int`. Then, if we improve the natural type of `e switch { ... => 1, ... => null}` to be `int?`, then a different overload (`M(int? x)`) will be chosen. There's also a pretty clear trade-off here. Target-typing may allow more viable types, but it falls over in the presence of type inference or if the target is so broad as to be useless (like if the target-type is `object`). Additionally, the proposal for target-typing the conditional expression (`?:`) will have different semantics from the target-typing for the switch expression, to avoid a breaking change. It may be desirable to unify the behavior of things like `?:` and switch expression, even if that means that the switch expression would be more limited, due to the backwards compatibility constraints of `?:`. **Conclusion** Let's do some work to see if we can alter the interaction of target-typing and common type inference to add the proposed new common types without introducing breaking changes with target-typing. The hope is that such a rule could be general enough to apply to other potential breaks in the future (in case we find more potential improvements to common type). In addition, we would like to know the effect of changing switch expressions to prefer the common type, if one exists. We hope that any difference would be uncommon in practice, but it would be useful to know if there are common instances where this would break. This would be used to decide if we would adjust the behavior of the switch expression to match the proposed behavior for the conditional expression. ================================================ FILE: meetings/2019/LDM-2019-10-21.md ================================================ # C# Language Design Meeting for Oct. 21 ## Agenda 1. Records 2. Init-only members 3. Static lambdas ## Discussion We'd like to make some progress on records, in both design and implementation. One development in our view of records is to consider them not as a feature in and of itself, but a shorthand for a set of features aimed at working with a collection of data. Specifically, we have a proposal to consider a guiding principle: a record should only be capable of generating code that the user could write themselves. To that end, we can look at the set of features we're considering incorporating into records, and differentiate between them based on the uncertainty of their design. The features which may have generated behavior are: 1. Automatic structural equality 2. With-ers (per type?) 3. Object initializers for readonly (per member) 4. Primary constructors + deconstruction Some features, like with-ers and object initializers, have many open issues. Some features, like generation of structural equality, have widely agreed upon semantics, once the set of members that are part of the structure are decided. It's proposed that we take a subset of the features, namely primary constructors and structural equality, and consider them to have designs ready for implementation. Primary constructors still have open semantic questions, especially around their meaning outside of a records, but all of the proposed records specify some type of primary constructor with very similar semantics. An example of the common semantics would be the following, ```C# data class Person(string Name, DateTime Birthday); ``` which would generate two record members, `Name` and `Birthday`, structural equality on those two members, and a corresponding constructor/deconstructor pair. The `data` modifier, as well as the primary constructor, are not necessarily final syntax, but the semantic decisions downstream of this stand-in don't heavily depend on the specific form of syntax chosen and could be easily changed. **Conclusion** This looks like a reasonable starting point. Records and the components should definitely be designed together, to ensure they fit together well, but it's worth highlighting the more settled pieces as we go. #### Init-only A brief discussion of the init-only fields feature. One clarification: the init-only properties would be allowed to have setters. Those setters can be executed in the object initializer, or in the constructor of the object. There is a proposal to allow them also to be set in the constructors of base types, which has no opposition at the moment. There's also a problem with "required" init-only members as currently proposed. The current design specifies that "required" init-only members (members without an initializer) would produce an error on the construction side if the member is not initialized in an object initializer. For example, ```C# class Person { public initonly string Name { get; } } void M() { var person1 = new Person() { Name = "person1" }; // OK var person2 = new Person(); // Error, Name was not initialized } ``` Unfortunately, the proposed emit strategy for `initonly` members would only provide an error in source, not in metadata. If a consumer compiled against a previous implementation of a type, and a required `initonly` member was added, no error would be provided if the consumer were not recompiled. Instead, the member would silently be set to the default value. A simple alternative is to drop "required" `initonly` members entirely. Setting an initonly member would be optional, as it is for public readonly members today, and if it is not set it would retain its default value. The recommendation for adding required members would be the same as it is today: use a constructor parameter, which cannot be skipped. We could also attempt to repair the situation by lowering the required `initonly` members into constructor parameters, but this seems undesirable for many reasons, including that it's complex to implement, it risks creating collisions with constructor overloads with the same type, it creates a mismatch in the number of parameters between source and IL, etc. It does not seem worth going down this path. ### Static lambdas Static lambdas were elided mostly for time reasons and we're interested in bringing them back. The main hang-up on the syntax is adding modifiers before the lambda syntax, but we already crossed that bridge with `async`. The only semantics question is whether or not a static lambda guarantees that the emitted method will be static in metadata. This is true for static local functions, but there are some important scenarios, like `extern` local functions, which depend on that and could not be implemented for lambdas. In addition, there have been numerous performance improvements in lambdas and local functions that have taken advantage of their unspecified metadata characteristics, and at least one significant performance optimization for lambdas would be lost by requiring them to be static. **Conclusion** The `static` keyword will be allowed on lambdas and it will have the same capturing rules as static local functions. It will not require that the lambda be emitted as static. ================================================ FILE: meetings/2019/LDM-2019-10-23.md ================================================ # C# Language Design Meeting for Oct. 23, 2019 ## Agenda 1. New primitive types (`nint` and `nuint`) ## Discussion ### Native int Proposal: https://github.com/dotnet/csharplang/issues/435 #### Static members The current spec states that `nint` and `nuint` are reserved keywords in a type context but there are contexts, like qualified names, that allow types but are not type contexts. Since `nint` and `nuint` have static members, we want users to be able to access them, so they should be allowed in qualified names. #### IntPtr members A follow-up question is what to do about existing members on the underlying IntPtr type. There are some members, like Add and Subtract, which may be misleading because they are not equivalent to `+` and `-` due to over/underflow checking. There are two views here. On the one hand, the proposal to use IntPtr means that some implementation details for IntPtr will inevitably leak through. Since users will sometimes want to use an IntPtr as an `nuint` to do pointer arithmetic, any methods on IntPtr may be useful and this would just be making barriers. On the other hand, `nint` is being added as a type for conceptually different reasons. It shouldn't be assumed that everything that applies to IntPtr should be applicable to `nint`. There's a follow-up question as whether we would exclude everything on IntPtr except for an include list, or include everything except for an exclude list. These may seem very different, but after more thought it's likely they're very similar. Since the framework would probably very rarely add new members to IntPtr after the language change (like with System.Int32), what we pick now is probably what will be present for a very long time, and anything new would probably be done with consultation from the language team. Our tentative conclusion is that we should exclude some members. The following are questionable: * Zero (use the `0` literal) * Size (use sizeof) * Add, Subtract (part of the point of this type is to use "proper" C# arithmetic) * ToInt32, ToInt64, ToPointer (use the language-defined conversions) #### Signed bitwise operations Unfortunately, IntPtr is "signed" but is often used to represent pointers, which are unsigned according to framework guidelines. In the current design, IntPtr has an identity conversion to `nint`, which could cause the unfortunate scenario ```C# IntPtr M(IntPtr p) { nint x = p; x >> 1; return x; } ``` This would use signed `>>`, which is probably incorrect if the value is assumed to be an unsigned pointer. This is not currently a problem with IntPtr because IntPtr does not define any bitwise operators. Unfortunately, keeping an identity conversion is an important part of compatibility. We would like to support users who are currently using IntPtr, but would like to use `nint`, to go from `IntPtr[]` to `nint[]`. Without an identity conversion, we don't see how this would be possible in the language today. Adding an entirely new concept in the language to support this without identity conversions is probably not worth it. Unless we can find a simple improvement to the design, we will probably have to accept this behavior. #### Overriding, hiding, and implementation (OHI) Since IntPtr and nint are the same CLR underlying type, they will be regarded as having the same signature. The compiler is able to see the difference. Should we regard them as identical for the purposes of OHI? This is similar to scenarios like tuple names, which are represented as attributes and thus not visible as part of the CLR type. However, in OHI we provide an error if tuple names change in an override because this could be the source of a bug (e.g., if the names were accidentally swapped). In this case we think IntPtr and `nint` are similar enough that we should regard the OHI behavior similar to `dynamic`/`object`, where it is allowed. For use as an enum underlying type, we think `nint`/`nuint` should be allowed as long as it's not too much implementation work. The legal values would be the same as the valid constants for the underlying. For sizeof, we think it should be supported in unsafe code, just like `sizeof(IntPtr)`, but with no special behavior in safe code. ================================================ FILE: meetings/2019/LDM-2019-10-28.md ================================================ # C# Language Design Notes for Oct. 28, 2019 ## Agenda 1. Discard parameters in lambdas and other methods 1. Enhancing the common type algorithm ## Discussion ### Discard parameters We wanted to talk about discard parameters in lambdas, and also potential expansions into other parameter lists like in local functions and regular methods. The first hurdle is whether any form of this feature is worth the complexity. The simplest case is lambdas, because the parameter names are not visible to the caller so they are only visible to the implementation. The alternative to discards is to give throw-away names, like `_1`, `_2`, and `_3`, or to use anonymous method syntax (`delegate { }`) to ignore all parameters. As for value, this is a fairly commonly requested requested feature ever since we introduced discards. The cost is increased complexity in the language (understanding that discards are legal as lambda parameters), but there's also an argument that not having the feature causes more complexity in the language. Specifically, the anonymous method syntax is almost entirely obsolete compared to the lambda syntax, but is still commonly used in this exact scenario. If usage of this feature decreases the usage of anonymous method syntax, that could be a decrease in required understanding of the language. For discards in local functions and method parameter lists, the cost/value ratio is not nearly as clear. The fundamental limitation is that parameter names for methods and local functions are always public surface area to the caller. We find the cost in complexity in resolving these questions higher than the feature is currently worth. If we find that it becomes a highly desired feature later, we would reconsider this decision. Lastly, we had a question of how the scoping would work regarding `_` in both the enclosing scope and inner scope of lambdas. ```C# void M() { int _ = 0; Action a = (_, _) => { _ = 1; // Is this a discard, or does it capture the local above? }; } ``` We considered various options, like making `_` always be a discard in a lambda body when the lambda parameters are discards, but we have a different precedent for non-lambda scopes like the following: ```C# void M() { int _ = 0; { _ = 1; // This assigns to the variable _ } } ``` We decided a better alternative to making complex scoping rules to prevent confusing code is to push for a warning wave to make using `_` as an identifier a warning. Essentially, if the above behavior is confusing, the confusing aspects are best resolved by always using `_` as a discard, rather than special language rules. The scoping behavior is confirmed that multiple `_`s in a lambda parameter list are discards. There are no other modifications to variable scopes i.e., they are not introduced in the lambda body scope, but they also hide nothing from the enclosing scope). ### Common Type Specification Proposal: https://github.com/dotnet/csharplang/issues/2823 This is revisiting a previous design question around whether to improve the common type specification and also to target type various expressions. In the last discussion we wanted more investigation on the impacts of adding target-typing and the consequences to improving the common type algorithm in the future. After investigation, there's a proposal to resolve questions about changing common type inference by looking back to an earlier rule: never infer a type that was not one of the input types. This is a rule that mainly comes from where to draw the line on complexity of type inference. This is a simple rule, but it's arguable that there are certain enhancements that don't open up to question the entire space of inference, but still satisfy simple requests, like assuming that null and simple integer types can be inferred as nullable. This would be similar to the language specification for nullable lifting operations on binary operators. However, that still leaves the fundamental problem we approached in the beginning: improving inference is a breaking change after target-typing is introduced. The proposal introduced is to not improve type inference in the future and consider this an acceptable outcome, given that target typing would satisfactorily resolve most of the examples given, and potentially in a clearer way than improving the common type algorithm. This would also have the property of preserving the original constraint of the common type algorithm, where no type is inferred that isn't present in any of the inputs. The biggest drawback here is that the switch expression and conditional expression would behave differently. The conditional expression would have to preserve the common type algorithm for all places it succeeds, for backwards compatibility. One possible way out of this is to separate the notion of common type for backwards compatibility, namely in overload resolution, and the inferred type, as the type used for `var`, where no target type is available. If feasible, this would resolve the issue mentioned at the previous meeting, where we would be unable to improve type inference without creating breaking changes in overload resolution. **Conclusion** Overall, we're in favor of adding target-typing for these expression forms. We should consider if there's anything more we would like to do to make the switch and conditional expressions more similar. ================================================ FILE: meetings/2019/LDM-2019-10-30.md ================================================ # C# Language Design Meeting Notes for Oct. 30, 2019 ## Agenda 1. Function pointer syntax 2. Enhancing Common Type Algorithm (again) ## Discussion ### Function pointer syntax Proposal: https://github.com/dotnet/csharplang/issues/2917 While exploring syntax for function pointers, it turns out that parsing the previously proposed syntax, namely ```C# func*(int, int) ``` can feature exponential parsing behavior in the case of nested function pointers. An alternative is proposed using a generic-style syntax. E.g., ```C# func* cdecl* ``` The proposed changes employ `*<` as being unambiguous in the language today. One suggestion is to use the same trick, but with a minor modification: `func` would always be the first token, and the calling convention would be the second token e.g., ```C# func cdecl* ``` and the set of calling conventions would evolve as the set of CLR-supported calling conventions evolve. There are a couple features which aren't mentioned, and we're not sure if we need to support them, and how we would do so. Mainly, we don't know whether certain features, like attributes, require definitions of named function pointers, instead of anonymous function pointer types. The runtime has proposed using attributes to express configuration, like suppressing native/managed transition. The current syntax doesn't have a place to put attributes, so if we wanted to support that design, we would have to have some definition to place attributes. This would be similar to delegate definitions, where you can define a delegate with a name and attributes, and then refer to the delegate type by name. However, the current runtime implementation doesn't support declaring tokens for the function pointer, so there's no official mechanism to attach attributes to a function pointer. A follow-up question is to ensure that the spec allows us to put the calling convention in the signature during emit as part of the function pointer type. We brainstormed a bunch of possible syntaxes (some more serious than others): ```C# delegate managed *int(int, long) delegate managed *List(int, long) delegate * List managed(int, long) delegate * List(int, long) delegate * managed List(int, long) delegate managed int *(int, long) delegate managed* delegate * delegate* managed delegate* ``` **Conclusion** The new syntax is agreed to be unambiguous, as far as we can see. We agree on three modifications: 1. The optional calling convention should always have a prefix. 2. Putting the `*` near the prefix is better, as it makes the function pointer more recognizable. 3. `delegate` is a better prefix, as it is already a keyword. Our final decision is ```C# 'delegate*' optional_calling_convention '<' type < type , ... > , return_type '>' ``` ### Enhancing Common Type Specification follow-up Proposal: https://github.com/dotnet/csharplang/issues/2823 The previous proposal was to add target-typing as a fallback to existing features, like the conditional expression, and do no more work on enhancing the common type algorithm. This would solve certain problems and not others. In the case where the conditional expression has a common type, but that type is not the one you want (e.g., `b ? 1 : 2`, but you wanted the result to be a `byte`), target typing would not solve this. At the same time, not having all possible improvements doesn't seem to impact the decision for nullable, specifically. We already have special language knowledge and handling of nullable and when language algorithms fail to introspect and find the "obvious" solution, this feels worse than more general failures. **Conclusion** None today. We ran out of time and would like to talk about it more. ================================================ FILE: meetings/2019/LDM-2019-11-11.md ================================================ # C# Language Design Meeting for Nov. 11, 2019 ## Agenda 1. Confirm removal of warning calling a method that returns `[MaybeNull]T` 2. Allow interpolated string constant 3. Enhancing the Common Type Specification 4. Type pattern 5. Simple name binding with target types ## Discussion ### Calling a method that returns `[MaybeNull]T` We previously proposed this as a safety warning, since the value of `T` could be nullable, similar to `default(T)`. The approach of warning on all uses turns out to be more annoying than we thought, producing multiple false positives. There are a couple steps to improving it. First, we'd like to not warn if the expression is returned in a method that is also annotated to return `[MaybeNullT]`. This would require us to use the information from the nullable attributes (or at least `[MaybeNull]`) inside the method. We also have to implement a three flow state design for nullable analysis, since it's not good enough to know whether the flow state is maybe null or not null (since all unconstrained generics are already considered maybe null), we need to know if the state is maybe null, even if the substituted generic does not allow null. **Conclusion** Confirmed that the warning can be removed. This will make it likely that we will produce more warnings in the case that all of these component parts are working, but we're hoping that almost all of these warnings will be real safety issues, not false positives. ### Interpolated string constant https://github.com/dotnet/csharplang/issues/2951 The simplest form of this proposal is to just allow constant strings with `$` in front of them. For example, ```C# const string s = $"abc"; ``` This doesn't really seem very useful, except in refactoring where you had an interpolated string, and made it constant, and it happened to not have any substitution. A more useful version would allow constant interpolated strings if all of the substitutions are constant strings. **Conclusion** Seems reasonable. We'll put it in "any time". @agocke to champion. ### Enhancing the Common Type Specification (again) https://github.com/dotnet/csharplang/issues/2823 We discussed a number of improvements, notably using target-typing as a fallback when there is no common type. The idea to improve target-typing for nullable seems interesting, but we need a more detailed proposal to examine. There's a worry that this will significantly complicate type inference, which needs investigation. There are a few associated questions, namely: 1. Do we improve the common type algorithm to not find a common type if all the component types cannot be converted to it? (e.g., `1` and `null` would have no common type) 2. Do we allow improve common type inference for the cases where there is no target type (e.g., when assigned to `var` or a method that takes a generic `T`) 3. Do we allow target typing when the common type is not convertible to the target? (e.g., `byte b = cond ? 1 : 2` would fall back to target typing because `int` is not convertible to `byte`) There's also the behavioral difference between the old expressions which have target-typing as a fallback, and the switch expression, which currently prefers the target-typing if one exists. Some of the questions above, including (3), would make the two behaviors be very similar, in that target-typing would be present in the few cases that rely on it. **Conclusion** We agree to the following changes: 1. Target-typing as a fallback for the old expressions (array creation, conditional expression, et al.) 2. We want target-typing to succeed in the case where the common type does not convert to the target-type 3. We want to change switch expression to use the natural type if it converts to the target-type, and only use target-typing if the natural type does not convert to the target. We want to continue to investigate improvements to the common type algorithm, including changes to support nullability. ### Type patterns https://github.com/dotnet/csharplang/issues/2925 The first proposal is to simplify some existing constructs and add some simplification to some current behavior by creating a simple "type pattern" without a variable introduction. ```C# void M(object o1, object o2) { var t = (o1, o2); if (t is string) // This is currently legal and we would just change the spec to say that this // is a type pattern, although the `is` would prefer a type in an ambiguity // to preserve backwards compatibility if (t is (int, string)) // This would now be legal as a tuple pattern, containing two type patterns switch (t) { case string: // this would now work, but would be the inverse of `is`, preferring a constant // for backwards compatibility } } ``` This has broad agreement as a good change, and multiple LDM members have been frustrated that the switch statement and expressions often require a `_` in these places. **Conclusion** The first proposal, allowing simple type patterns, is broadly useful right now. Accepted for the C# 9.0 milestone. ### Name lookup for binding simple names with known target type https://github.com/dotnet/csharplang/issues/2926 The second proposal is to adjust binding with a known target type, so you could say ```C# class Outer { public class Inner1 : Outer {} public class Inner2 : Outer {} } int M1(Outer o) => o switch { Inner1 _ => 0, // Binds to Outer Inner2 _ => 1; } ``` or even ```C# Color x = b ? Red : Greed; var y = b ? Color.Red : Greed; ``` **Conclusion** This change feels especially natural in switch statements and expression with enums. It seems useful in the other contexts as well. The extension to nested classes would be particularly relevant to a discriminated union based off of nested classes. There's a lot here in the binding changes. Let's sit on it a bit and take a look with a discriminated unions proposal. Triaged to C# 9.0 to match discriminated unions. ================================================ FILE: meetings/2019/LDM-2019-11-13.md ================================================ # C# Language Design Meeting for Nov. 13th, 2019 ## Agenda 1. Discriminated unions 2. Improved analysis of `[MaybeNull]T` ## Discussion ### Initial discriminated union proposal https://github.com/dotnet/csharplang/blob/master/proposals/discriminated-unions.md Questions around some restrictions: 1. Why can't you have constructor parameters on the enum class type? - No real reason, except worries about inheritance aside from members - Keeping inheritance syntactically close by is considered a feature of the proposal, since the user can easy analyze what all the possible children of the root are - Syntax for the base call in value members would have to be specified 2. How `partial` do we want to allow things? There's good reason to put all the members together 3. Are records short enough syntax that we don't need the "simple" type syntax? - Regardless of brevity, if members are required to explicitly inherit from the root this seems like purely useless work, since doing otherwise would be an error 4. What if you want to define just regular nested classes, that don't inherit from the root type? 5. Scoping for nested enum class members? The proposal for altering binding wouldn't help with nested members, because it only applies to simple names. ```C# enum class Expr { enum class BinaryOperator { Add(Expr left, Expr right), Multiply(Expr left, Expr right) } } Expr M(Expr left, Expr right) { return Add(left, right); // error, no name "Add" found under Expr } int M(Expr e) => e switch { Add _ => 0, // error, no name Add found under Expr } ``` There's also a proposed alternate syntax that would let you put everything directly into the enum class: ```C# enum class Color { Red, Greed; // semicolon delimiting end of enum class cases, and start of regular members public static int HelperField; public static int HelperProperty => 0; public int HelperMethod() => 0; } ``` ## Improved analysis of `[MaybeNull]T` Proposal: https://github.com/dotnet/csharplang/issues/2946 The new proposed abstract domain for nullable analysis would be: ```C# enum { NotNull, MaybeNull, MaybeNullEvenIfNotNullable } ``` If we improve analysis of `MaybeNull` inside the method, do we want to push the new functionality through for all the nullable attributes at the same time? This seems useful from a consumer perspective, in that you get the different warnings all at the same time, but there's also the simple question of resources and scheduling. Similarly, if we do improve `MaybeNull` analysis in the body of the method, do we need to improve the analysis in other places, like overriding, at the same time? Or are we OK with a piecemeal state where `MaybeNull` is enforced in the method body but not in overriding? Or where we enforce `MaybeNull` in overriding, but not other attributes like `DisallowNull`? A broader question is how common `MaybeNull` is in general. Our experience in the .NET Core mscorlib is that there are only 10 occurrences of `MaybeNull`, and even when broadening to LINQ, there are at most a few dozen occurrences, mostly in places that may return a `default` value. In comparison, `NotNullIfNotNull` has ~30 occurrences in mscorlib. On the other hand, `default(T)` is very common and is an instance of `MaybeNull` in a sense. This implies that the flow state of `MaybeNull` is particularly important. An additional question is whether or not to revive the `T?` proposal to deal with the historical problems around `MaybeNull` annotations on locals and other places where attributes are not permitted. **Conclusion** We think improving any aspect of the attributes can be considered independently and we will not require the changes to ship together. We think that proposal is good. Accepted. No statement on `T?`. We will schedule a discussion soon to get a permanent decision on the proposal. ================================================ FILE: meetings/2019/LDM-2019-11-18.md ================================================ # C# LDM for Nov. 18th, 2019 ## Agenda 1. More patterns ## Discussion ### More patterns Parenthesized patterns: - What about ambiguity with a potential 1-tuple pattern? - There was a previous ambiguity here with a parenthesized constant expression, so matching a 1-tuple pattern requires resolving ambiguity, for example by adding tuple names: `x is (Item1: var tupleVal)` Relational patterns: We don't love the syntax. The problem is really that we're using a binary operator in a unary context. On the other hand, it's useful in that it's easy to adjust for closed or open bounds by adjusting the inclusiveness of the operator. The other shaky part of the syntax is support for user-defined operators. One of the fundamental parts of pattern matching is that the semantics are well-understood, meaning that the switch expression expects certain things must be true, like that checks must return the same value for the same inputs and can be called any number of times. We have a few patterns that execute user-defined code, but it seems more likely that they're usually safe (property getters and Deconstruct). For `==` and `!=` specifically, they seem redundant and likely to cause confusion when there is a user-defined operator that is not supported. Consensus is to remove support in relational patterns. The other confusing part is conversion, where the input type does not statically contain a built-in the binary operator. For instance, ```C# object o = (short)1; _ = o switch { < 3 => 0, // This would be false, since `o` is a short, not an int, and the // unconverted '3' is an `int` }; ``` This is already how constant patterns work, so there may be some confusion here already, but we're worried about broadening the problem by adding the `not` pattern in combination with the relational operators. For example, if you match `not < 3`, this would evaluate to true, but not because the value is not less than three, but because it is not an int. This would mean that `>= 3` and `not < 3` would be different, since the type check can play into the check. However, we don't have any better approaches, and the hope is that this will be a relatively rare occurrence. If the input pattern has a statically known built-in operator this would not be a concern. The proposal states that the input type of the expression must contain a conversion that is not a boxing conversion. However, we do support constant pattern checks for unconstrained type parameters, so we need to change the proposal to allow unconstrained type parameters as well. ### Pattern combinators The proposal contains three breaking changes: - `not` is considered a type in C# 8 and a pattern in C# 9. - `and` and `or` are allowed as variable names in C# 8, but are pattern combinators in C# 9 An important question is whether these are allowed and how they are breaking. Changing behavior is probably a bridge too far. We would consider providing an error in the old scenarios and forcing disambiguation. We'll need more discussion on the breaking changes and how we can mitigate them. ================================================ FILE: meetings/2019/LDM-2019-11-25.md ================================================ # C# Language Design Notes for Nov 25, 2019 ## Agenda 1. Allow `T?` where `T` is a type parameter that's not constrained to be a value or reference type # "Unconstrained" `T?` In C# 8.0 we entertained the possibility of allowing `T?` on type parameters `T` that are unconstrained, or constrained only in a way that they can still be instantiated with both value and reference types. The idea was to accurately express the type of `default(T)`. To this end, `T?` would end up being the same as `T`, except when `T` is instantiated with a nonnullable reference type `C`, in which case `T?` is `C?`. The reason is that for non-nullable reference types `C`, `default(C)` is still null, and hence of the type `C?`, not `C`. `T?` would, for instance, allow the return type of methods like `FirstOrDefault()` to be better expressed: ``` c# public T? FirstOrDefault(this IEnumerable src); ``` In the generic context where `T` is in scope, the meaning of `T?` would be "may be null, even when `T` is not nullable". The expression `default(T)` would no longer warn, but the type of it would be `T?`, and assigning `T?` to `T` *would* warn. In the end we decided not to allow `T?` due to a couple of problems which we'll get back to below. Instead we addressed many of the scenarios with the addition of nullable attributes such as `[MaybeNull]`, which can be used to annotate e.g. the `FirstOrDefault()` method: ``` c# public [return:MaybeNull] T FirstOrDefault(this IEnumerable src); ``` ## Recent changes Default values keep causing grief, and we've recently [(Nov 13)](https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-11-13.md#improved-analysis-of-maybenullt) decided on some changes to handle them: 1. Add a third null-state next to "not null" and "maybe null", which is "maybe null *even* when the type doesn't allow", and make that the null state of `default(T)` for an unconstrained type parameter `T`, as well as results of method calls etc. (such as `FirstOrDefault`) that were annotated with `[MaybeNull]` 2. Remove the "W warning" on assignment of such values to locals of type `T`, but instead subsequently track the new state *for* those locals, which may subsequently lead to warnings if used unsafely 3. Remove warnings when producing such values as a result (e.g. return value) that is itself annotated as `[MaybeNull]` Put together, these three eliminate most of the friction around the use of `default(T)` and `[MaybeNull]`. However, they also get very close to the previously proposed semantics of allowing `T?` on unconstrained `T`. We could take this two ways: 1. Since we mostly succeeded in addressing the problem without `T?`, it further reduces the need for it. 2. This shows that "the type of `default(T)`" is an important concept that should be properly reified in the language instead of tricks with attributes. ## Taking another look at "unconstrained" `T?` The main arguments in favor of a syntax for "the type of `default(T)`" even with the above changes are: 1. By adding "it" as a tracked null state, we're already half embracing it, but by leaving it inexpressible as a type we need to fall back on "tricks" to make use of it 2. `[MaybeNull]` doesn't help when "the type of `default(T)`" is needed in a constructed (array, generic, tuple, ...) type The use of the syntax `T?` to mean "the type of `default(T)`" as two significant problems as well, which ultimately led us to not embrace it for C# 8.0: 1. Its meaning is different from the meaning of `T?` when `T` is constrained to be a value type. 2. Overrides cannot restate their constraint, and `T?` in those today always means the value type kind. ## Problem 1: `T?` and `T?` mean different things The first problem is not technical, but one of perception and language regularity. Consider: ``` public T? M1(T t) where T : struct; public T? M2(T t); var i1 = M1(7); // i1 is int? var i2 = M2(7); // i2 is int ``` The declaration of `M1` is legal today. Because `T` is constrained to a (nonnullable) value type, `T?` is known to be a nullable value type, and hence, when instantiated with `int`, the return type is `int?`. The declaration of `M2` is what's proposed to allow. Because `T` is unconstrained, `T?` is "the type of `default(T)`". When instantiated with `int` the type of `default(int)` is `int`, so that is the return type. In other words, for the same provided `T` these two methods have *different* return types, even though the only difference is that one has a constraint on `T` but the other does not. The cognitive dissonance here was a major part of why we didn't embrace `T?` for unconstrained `T`. ## Problem 2: pseudo-"unconstraint" in overrides for disambiguation In C# overrides of generic methods cannot re-specify constraints, but must inherit them from the original declaration. Before C# 8.0, when `T?` appeared in signatures of such overrides, it was always assumed to mean the nullable value type `Nullable`, because what else could it mean? In C# 8.0 `T?` acquired the possible second meaning of nullable reference type. In order to disambiguate the search for the original declaration, we reluctantly introduced the ability to specify "pseudo-constraints" on the overrides: When `T` was a type parameter constrained to be a reference type, you can now say so by adding `where T: class` to the declaration. If you want the default assumption of it being constrained to a value type to be made explicit, you can also optionally specify `where T: struct`. These helper constraints are only there to help find the right declaring method up the inheritance chain (which may be overloaded). The *actual* constraint that's emitted into generated code is still the one that's inherited from the original declaration, once that one has been identified. Thus, only `class` and `struct` can be used as constraints on an override. Now here comes a third `T?`, the defining characteristic of which is that it is *not* constrained to be either a reference or value type. To distinguish it from the other two in an override of a generic method, we would need a third "constraint" that is actually an *unconstraint* - that specifically says that it is *not* constrained! ## Solutions We have two general approaches to address these problems (beyond the ever-present solution of giving up on the feature): 1. Live with problem 1, and try to explain things as best we can. Find a syntax to express the "unconstraint" of problem 2. 2. Use a different syntax than `T?` to express "the type of `default(T)`". ## Solution 1 A brain storm for solution 1 syntaxes yielded: ``` c# override void M1<[Unconstrained]T,U>(T? x) // a override void M1(T? x) where T: object? // b override void M1(T? x) where T: unconstrained // c override void M1(T? x) where T: // d override void M1(T? x) where T: ? // e override void M1(T? x) where T: null // f override void M1(T? x) where T: class|struct // g override void M1(T? x) where T: class or struct // h override void M1(T? x) where T: cluct // joke ``` Of these we have a vague preference for c, expressing explicitly that there is *not* a constraint, closely followed by b, which is the most general constraint one could state. None of the others resonated. ## Solution 2 A brain storm for solution 2 syntaxes yielded: ``` c# void M1(default(T) x) // X void M1(default x) // Y void M1(T?? x) // Z ``` Solutions X and Y try to be explicit about the type meaning "the type of `default(T)`", but run the risk of implying "the result *is* `default(T)`". Solution Z is mostly just the shortest `?`-like token we could think of that isn't `?`. We unanimously preferred Z. `??` can be read as *may be* (`?`) nullable (`?`). ## Conclusion Comparing the two approaches we do favor solution 2, adopting the syntax `T??` to mean "the type of `default(T)`" (when `T` is unconstrained). We do think that this is the best solution, because it addresses both problems, is terse, and looks reasonable. We will move ahead with prototyping and fleshing it out. We would probably only allow it to be used at all on type parameters `T` that are not constrained to be either value or reference types. That way you can never use either `??` or `?` on the same thing. ================================================ FILE: meetings/2019/LDM-2019-12-11.md ================================================ # C# Language Design Meeting for Dec. 11, 2019 1. Design review feedback ## Discussion We got feedback from the design review that we shouldn't try to conflate too many problems. If we want to make it easier to support structural equality, we should see if it's possible to address directly, without requiring the other features of records. One suggestion was to take inspiration from `VB`, which allows the `key` modifier to be added to VB anonymous types to indicate structural equality with the members used as the keys. We took that advice and looked at a sketch of what that could look like: ```C# class C { public key string Item1 { get; } public string Item2 { get; } } ``` The `key` modifier would be used to control generated equality, such that all members marked `key` would be compared for equality in an `Equals` override (using the same pattern as in the original records proposal). The above code sample certainly looks simple, but unfortunately it's not sufficient for real-world code. Both `Item1` and `Item2` are `get`-only autoproperties, meaning that as-is there is no way to initialize those members. A working example looks more like: ```C# class C { public key string Item1 { get; } public string Item2 { get; } public C(string item1, string item2) { Item1 = item1; Item2 = item2; } } ``` This is significantly longer than the original sample, grows with the number of properties, and is repetitive. The latter is particularly problematic, as repetitive boilerplate is often a source of hard-to-see bugs or typos. Worse, we've seen that when construction becomes laborious, users resort to making their types mutable instead of writing out the constructor, e.g. ```C# class C { public key string Item1 { get; set; } public string Item2 { get; } } ``` This is unfortunate in most code, but it's worrying when combined with structural equality. When used in Dictionaries, mutable types with structural equality are an anti-pattern because the hash code of the type changes, causing the type to "disappear" from the Dictionary. It's one thing if the user opts-in to this risk, knowing that they need to be careful, and a completely different situation if the language encourages a dangerous pattern. **Conclusion** This is an interesting design point that we think we'll incorporate. We've also agreed that we have a hard design requirement: if we provide a feature for easy structural equality, we must provide a more convenient syntax for constructing immutable types in the same release. To do otherwise would be to effectively make a trap for users. ## Nominal vs Positional We also continued to explore the space of nominal vs. positional records. An insight from the previous discussion is that nominal vs. positional records are really about improving syntax for type construction. Positional records are about taking the existing type construction, constructors, and giving them a shorter syntax. Nominal records are about identifying some weaknesses of the existing construction system and providing a new feature to support them. In both cases, though, the proposed features shorten construction by avoiding repeating the member declarations and the assignments. We also had the following notes, in no particular order: * For positional constructors, it's important to consider primary constructors. We have one proposal for primary constructors, but have not had a discussion on whether we want them, and if the syntax is worth taking away from the record syntax. * If the `initonly` keyword is required, even for common cases, this is about as expensive syntax-wise as a setter. It may be a few more characters, but it keeps things on one line, as opposed to the multi-line constructors that are required right now. * There's an opposition between evolving existing data types and picking and choosing features when you need them, but also providing a simple syntax for the most common case. Certainly positional records solve a common case. The question is how common that case is. * Positional records use existing initialization strategy (construction) which is fairly well-understood. The initonly feature, by contrast, will force us to reexamine some assumptions. For instance, constructors take all inputs at once, meaning that you can enforce requirements between them at construction time. `initonly` overrides properties after construction, and one-by-one, so it's not possible, or not obvious, how to provide this functionality. * There are mixed feelings on the requirements of what features will be available individually, and which are separable. Some people feel that addressing the most common case is sufficient for providing the value of the feature, namely that there can be a single "record" which provides immutable construction and value equality, and that is the only way to access these features. Others think that the features need to be adoptable independently: existing types need to be able to adopt generated equality without adopting immutable construction, while immutable construction needs to be available without structural equality. One conclusion is that no proposal will solve all record-related problems. This is fine. We also have general agreement that there should be a convenient shorthand for the combination of most or all of the features (simple immutable construction, structural equality, etc.). Notably we don't all agree on whether or not structural equality will be the most common type of equality for records, but the shortest record form may include structural equality for other language design reasons. ================================================ FILE: meetings/2019/LDM-2019-12-16.md ================================================ # C# Language Design Notes for Dec. 16, 2019 ## Agenda 1. Switch expression as a statement expression 2. Triage ## Discussion ### Switch expression as a statement expression https://github.com/dotnet/csharplang/issues/2860 The proposal being discussed is whether to allow the switch expression without a discard: ```C# _ = a switch { ... }; // becomes a switch { ... }; ``` One of the against is that it makes for a confusing decision in the language as to whether you use a switch expression or switch statement. Right now the guidance is simple: if you are in a statement context, use a statement. If you have an expression context, use a switch expression. Now, for a statement, you could use either a switch statement, or a switch expression in statement form. It's not clear which. One way of resolving this is that these are two parallel features that provide similar features in a slightly different syntax and semantics. Then the answer simply becomes, use whichever one you like better. If the new switch expression form includes exhaustiveness checking, that would be a reason to use or not to use it, aside from the syntax differences. Similarly, the switch statement ability to `goto` another case is a reason to use that form. However, if we accept that, the switch expression feels artificially limited. To provide a satisfactory parallel feature we have to augment the switch expression to allow for statements in the switch arms. Then there is a potential new set of features: block in switch expressions. On the other hand, this feels like feature creep. The original proposal was quite simple: allow users to elide `_ =` and remove the requirements for the arms to have a common type. While we may want to have a number of different new features for switch expression to make it comparable to the switch statement, there's value in doing the feature as-is, and adding those features later. This is contingent on us being fairly confident that the new features can be added without breaking changes, but there's a fair amount of confidence that we know where we would go with the feature. This perspective would require us to keep certain behaviors to ensure that the switch expression keeps its differences from the switch statement. For instance, the new switch expression-as-statement would have to check exhaustiveness if we see it as a strict improvement for the switch expression. Lastly, we all find the proposed switch expression-as-statement requiring a semicolon i.e., ```C# a switch { b => ..., c => ... }; // semicolon required ``` as being extremely ugly. **Conclusion** Rejected as-is. We'd be interested in a new proposal on this topic, addressing many of the concerns that we brought up today. ### Triage #### Definite assignment of private reference fields **Conclusion** Accepted for warning waves v1, wherever that is triaged. #### Remove restriction on yielding a value in the body of a try block Also for async iterators. Issue #2949 **Conclusion** Accepted, Any Time. #### Generic user-defined operators Issue #813 **Conclusion** There's no syntax in the invocation to specify the type arguments, in case inference doesn't succeed, and we think almost any syntax in the invocation location would be ugly. In addition, we don't have a lot of examples of why this would be significantly better than alternatives (like writing a method). Rejected. #### Support for method argument names in `nameof` Issue #373 It looks like there's a significant breaking change if we allow the parameter names to be in scope generally. ```C# const int p = 3; [Attribute(Property = p)] void M(int p) { } ``` If we just allow `nameof` to have special scoping to allow the names in the method declaration to be in scope, then there's no language breaking change. The scoping rules would prefer names in the method header (including type parameters) over rules in the rest of the program. **Conclusion** Accepted, Any Time. ================================================ FILE: meetings/2019/LDM-2019-12-18.md ================================================ # C# Language Design Meeting for Dec. 18, 2019 ## Agenda 1. Nullable issues a. Pure null checks b. Consider `var?` ## Discussion ### Pure null checks We have the following syntax which semantically check for null in the language: * `e is null` * `e == null` * `e is {}` * `e is object` However, for nullability we have a separate notion of a "pure" null check that causes a null branch to appear, even if the variable being tested was declared not null, or vice versa. An example of why this would matter is ```C# var current = myLinkedList.Head; // assume Head is nullable oblivious/unannotated while (current is object) { ... current = current.Next; // assume oblivious } current.ToString(); // only warns if `is object` is a pure null test ``` We previously established that all "pure" null checks contain the word `null` in them, meaning that only `e is null` and `e == null` are pure null checks today. There is a proposal that we should unify all forms that are semantically equivalent, regardless of syntax. There is also a proposal to expend this even to places which are not "pure" checks, i.e. they have semantic effect larger than just checking for null. For instance, we could also check `e is object o`, which also introduces a variable `o`. We came up with the following list of potential checks: ``` e is null // pure e is object // Proposed e is [System.]Object // Proposed e is object _ e is object x s is string // s denotes expr of static type string s is string x o is string x e is {} // Proposed e is {} _ e is {} x e == null // pure e != null // pure e is not null // pure e is not object // etc... ``` All parties argue that other positions are confusing as to why something is a pure null check and something else, that's very similar, is not. It seems like drawing any particular line will always imply that something similar could be confusing. One difference between versions that check between a semantically pure null check, i.e. a piece of syntax that has no other meaning than testing for null, is that if there is a pure null check then any warning is definitely a bug in user code: either the check is superfluous, or there is an actual safety issue. If the check is not pure, there may not be a bug, because the check may not actually be superfluous and this may be a spurious warning. Given that the pure null checking is useful, it's mainly about finding the right balance between helping the user find bugs in their code and finding a set of rules that are also easily understandable. The main argument against broadening beyond our current rule is that "pure checks contain the word 'null'" is a simple rule, and adding warnings in an update is a heavy way to address the issue. On the other hand, we have changed nullable warnings multiple times already, plan to do it again, and have warned people that nullable warnings may be in flux for a time. If the feature is also meant to react to user intent, and if we believe `x is object` is intended by the user to be a null check, then making it a pure null check would be correctly responding to user intent. We could also decide based on whether or not we want to suppress certain patterns. If we believe `x is object` or `x is {}` aren't good ways to test for null, then making them not pure null checks would encourage users not to use it. This did not seem a compelling position for anyone in LDM. **Conclusion** We agree that we should broaden the set of pure null checks. We agree that `x is object` should be a pure null check. Moreover this should be based on the type in the `is` expression, meaning that any type `T` that binds to `System.Object` in `x is T` would be a pure null check. We also agree that `x is {}` is a pure null check. None of `x is object _`, `x is object o`, `x is {} _`, or `x is {} o` are pure null checks. ### `var?` At this point we've seen a large amount of code that requires people spell out the type instead of using var, because code may assign `null` later. An example, ```C# var current = myLinkedList.Head; // annotated not null while (current is object) { ... current = current.Next; // warning, Next is annotated nullable, but current is non-null } ``` One way to deal with this is to allow `var?`, ```C# var? current = myLinkedList.Head; // now current is nullable, but the flow state is non-null current.ToString(); // no warning, because the flow analysis says it's not null ``` This would let people express that the think the variable may be assigned null later on. On the other hand, we could just permit these assignments when using `var`, and use flow analysis to ensure safety. ```C# var current = myLinkedList.Head; current = null; // no warning because var is nullable current.ToString(); // warning, the flow state says this may be null ``` This would allow users to be explicit when they want to make sure not to assign null to a type, but they have to spell out the type. **Conclusion** Make `var` have a nullable annotated type and infer the flow type as normal. ================================================ FILE: meetings/2019/README.md ================================================ # C# Language Design Notes for 2019 Overview of meetings and agendas for 2019 ## Dec 18, 2019 [C# Language Design Notes for Dec 18, 2019](LDM-2019-12-18.md) 1. Pure null checks 2. `var?` ## Dec 16, 2019 [C# Language Design Notes for Dec 16, 2019](LDM-2019-12-16.md) 1. Switch Expression as a Statement Expression (continued) (Neal, Fred) 2. Triage ## Dec 11, 2019 [C# Language Design Notes for Dec 11, 2019](LDM-2019-12-11.md) 1. Design review feedback ## Dec 9, 2019 (not yet transcribed) - https://github.com/dotnet/csharplang/issues/2850 Proposed changes for pattern-matching (continued) (Neal) - https://github.com/dotnet/csharplang/issues/2860 Switch Expression as a Statement Expression (Neal) ## Dec 4, 2019 (Design Review) (not yet transcribed) ## Nov 25, 2019 [C# Language Design Notes for Nov 25, 2019](LDM-2019-11-25.md) 1. Revisit unconstrained `T?` (Mads, Jared) ## Nov 20, 2019 (not yet transcribed) - https://github.com/dotnet/csharplang/issues/2911 Utf8 String Literals (Neal) - https://github.com/dotnet/csharplang/issues/2850 Proposed changes for pattern-matching (continued) (Neal) ## Nov 18, 2019 [C# Language Design Notes for Nov. 18, 2019](LDM-2019-11-18.md) 1. Proposed changes for pattern-matching ## Nov 13, 2019 [C# Language Design Notes for Nov 13, 2019](LDM-2019-11-13.md) 1. Discriminated unions 2. Improve analysis of `[MaybeNull]T` values ## Nov 11, 2019 [C# Language Design Notes for Nov 11, 2019](LDM-2019-11-11.md) 1. Confirm removal of warning calling a method that returns `[MaybeNull]T` 2. Allow interpolated string constant 3. Enhancing the Common Type Specification 4. Type pattern 5. Simple name binding with target type ## Oct 30, 2019 [C# Language Design Notes for Oct 30, 2019](LDM-2019-10-30.md) 1. Function Pointer syntax 2. Enhancing the Common Type Specification ## Oct 28, 2019 [C# Language Design Notes for Oct 28, 2019](LDM-2019-10-28.md) 1. Discard parameters in lambdas and other methods 1. Enhancing the common type algorithm ## Oct 23, 2019 [C# Language Design Notes for Oct 23, 2019](LDM-2019-10-23.md) 1. New primitive types https://github.com/dotnet/csharplang/issues/435 ## Oct 21, 2019 [C# Language Design Notes for Oct 21, 2019](LDM-2019-10-21.md) 1. Records 2. Init-only members 3. Static lambdas ## Sep 18, 2019 [C# Language Design Notes for Sep 18, 2019](LDM-2019-09-18.md) Triage: 1. Proposals with complete designs: - https://github.com/dotnet/csharplang/issues/1888 Champion "Permit attributes on local functions" - https://github.com/dotnet/csharplang/issues/100 Champion "Target-typed new expression" 2. Target typing and best-common-type features: - https://github.com/dotnet/csharplang/issues/2473 Proposal: Target typed null coalescing (??) expression - https://github.com/dotnet/csharplang/issues/2460 Champion: target-typed conditional expression - https://github.com/dotnet/csharplang/issues/881 Permit ternary operation with int? and double operands - https://github.com/dotnet/csharplang/issues/33 Champion "Nullable-enhanced common type" ## Sep 16, 2019 [C# Language Design Notes for Sep 16, 2019](LDM-2019-09-16.md) - Support for Utf8 strings - Triage remaining features out of the [8.X milestone](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+milestone%3A%228.X+candidate%22) ## Sep 11, 2019 [C# Language Design Notes for Sep 11, 2019](LDM-2019-09-11.md) 1. Close https://github.com/dotnet/roslyn/issues/37313 2. Triage new proposed [9.0 features](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+no%3Amilestone+label%3A%22Proposal+champion%22) 2. Finish triaging away [8.X milestone](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22+milestone%3A%228.X+candidate%22) ## Sep 4, 2019 [C# Language Design Notes for Sep 4, 2019](LDM-2019-09-04.md) 1. AllowNull on properties https://github.com/dotnet/roslyn/issues/37313 ## Aug 28, 2019 [C# Language Design Notes for Aug 28, 2019](LDM-2019-08-28.md) 1. Triage language features ## Jul 22, 2019 [C# Language Design Notes for July 22, 2019](LDM-2019-07-22.md) 1. Discuss the [new records proposal](https://github.com/dotnet/csharplang/blob/856c335cc584eda2178f0604cc845ef200d89f97/proposals/recordsv2.md) ## Jul 17, 2019 [C# Language Design Notes for July 17, 2019](LDM-2019-07-17.md) 1. [Nullability of events](https://github.com/dotnet/roslyn/issues/34982) 1. Triage, see https://github.com/dotnet/csharplang/projects/4 - Triage championed features https://github.com/dotnet/csharplang/projects/4#column-4649189 - Triage milestones ## Jul 10, 2019 [C# Language Design Notes for July 10, 2019](LDM-2019-07-10.md) 1. Empty switch statement 1. `[DoesNotReturn]` attribute 1. Revisiting the `param!` null-checking feature ## May 15, 2019 [C# Language Design Notes for May 15, 2019](LDM-2019-05-15.md) - Close on nullable attributes (Mads and Steve) ## May 13, 2019 [C# Language Design Notes for May 13, 2019](LDM-2019-05-13.md) - Close on desired rules for warning suppressions and `#nullable` interacting ## Apr 29, 2019 [C# Language Design Notes for Apr 29, 2019](LDM-2019-04-29.md) 1. Default interface implementations and `base()` calls 2. Async iterator cancellation 3. Attributes on local functions ## Apr 24, 2019 [C# Language Design Notes for Apr 24, 2019](LDM-2019-04-24.md) MaybeNull and related nullable reference type attributes ## Apr 22, 2019 [C# Language Design Notes for Apr 22, 2019](LDM-2019-04-22.md) 1. Inferred nullable state from a finally block 2. Implied constraint for a type parameter of a partial? 3. Target-typed switch expression 4. DefaultCancellationAttribute and overriding/hiding/interface implementation ## Apr 15, 2019 [C# Language Design Notes for Apr 15, 2019](LDM-2019-04-15.md) 1. CancellationToken in iterators 2. Implied nullable constraints in nullable disabled code 3. Inheriting constraints in nullable disabled code 4. Declarations with constraints declared in #nullable disabled code 5. Result type of `??=` expression 6. Use annotation context to compute the annotations? 7. Follow-up decisions for pattern-based Index/Range ## Apr 3, 2019 [C# Language Design Notes for Apr 3, 2019](LDM-2019-04-03.md) 1. Ambiguous implementations/overrides with generic methods and NRTs 2. NRT and `dynamic` ## Apr 1, 2019 [C# Language Design Notes for Apr 1, 2019](LDM-2019-04-01.md) 1. Pattern-based Index/Range translation 2. Default interface implementations: Is object.MemberwiseClone() accessible in an interface? ## Mar 27, 2019 [C# Language Design Notes for Mar 27, 2019](LDM-2019-03-27.md) 1. Switch expression syntax 1. Default interface implementations 1. Reabstraction 2. Explicit interface abstract overrides in classes 3. `object.MemberwiseClone()` 4. `static int P {get; set}` semantics 5. `partial` on interface methods 2. `?` on unconstrained generic param `T` ## Mar 25, 2019 [C# Design Review Notes for Mar 25, 2019](LDM-2019-03-25.md) We brought in the design review team to look at some of our recent and open decisions in C# LDM. 1. Nullable reference types: shipping annotations 2. Pattern-based indexing with `Index` and `Range` 3. Cancellation tokens in async streams ## Mar 19, 2019 [C# Language Design Notes for March 19, 2019](LDM-2019-03-19.md) We held a live LDM during the MVP summit with some Q&A about C# 8 and the future Topics: 1. Records 2. "Extension interfaces"/roles 3. Macros 4. IAsyncEnumerable 5. "Partially automatic" properties 6. More integration with reactive extensions ## Mar 13, 2019 [C# Language Design Notes for March 13, 2019](LDM-2019-03-13.md) 1. Interface "reabstraction" with default interface implementations 2. Precedence of the switch expression 3. `or` keyword in patterns 4. "Pure" null tests and the switch statement/expression ## Mar 6, 2019 [C# Language Design Notes for March 6th, 2019](LDM-2019-03-06.md) 1. Pure checks in the switch expression 2. Nullable analysis of unreachable code 3. Warnings about nullability on expressions with errors 4. Handling of type parameters that cannot be annotated 5. Should anonymous type fields have top-level nullability? 6. Element-wise analysis of tuple conversions ## Mar 4, 2019 [C# Language Design Notes for March 4, 2019](LDM-2019-03-04.md) 1. Nullable user studies 2. Interpolated string and string.Format optimizations ## Feb 27, 2019 [C# Language Design Notes for Feb 27, 2019](LDM-2019-02-27.md) 1. Allow ObsoleteAttribute on property accessors 2. More Default Interface Member questions ## Feb 25, 2019 [C# Language Design Notes for Feb 25, 2019](LDM-2019-02-25.md) - Open issues in default interface methods (https://github.com/dotnet/csharplang/issues/406). - Base calls - We currently have open issues around `protected`, `internal`, reabstraction, and `static` fields among others. ## Feb 20, 2019 [C# Language Design Notes for Feb 20, 2019](LDM-2019-02-20.md) - Nullable Reference Types: Open LDM Issues https://github.com/dotnet/csharplang/issues/2201 ## Feb 13, 2019 [C# Language Design Notes for Feb 13, 2019](LDM-2019-02-13.md) - Nullable Reference Types: Open LDM Issues https://github.com/dotnet/csharplang/issues/2201 ## Jan 23, 2019 [C# Language Design Notes for Jan 23, 2019](LDM-2019-01-23.md) Function pointers ([Updated proposal](https://github.com/dotnet/csharplang/blob/master/proposals/function-pointers.md)) ## Jan 16, 2019 [C# Language Design Notes for Jan 16, 2019](LDM-2019-01-16.md) 1. Shadowing in lambdas 2. Pattern-based disposal in `await foreach` ## Jan 14, 2019 [C# Language Design Notes for Jan 14, 2019](LDM-2019-01-14.md) - Generating null-check for `parameter!` https://github.com/dotnet/csharplang/pull/2144 ## Jan 9, 2019 [C# Language Design Notes for Jan 9, 2019](LDM-2019-01-09.md) 1. GetAsyncEnumerator signature 2. Ambiguities in nullable array type syntax 2. Recursive Patterns Open Language Issues https://github.com/dotnet/csharplang/issues/2095 ## Jan 7, 2019 [C# Language Design Notes for Jan 7, 2019](LDM-2019-01-07.md) Nullable: 1. Variance in overriding/interface implementation 2. Breaking change in parsing array specifiers ================================================ FILE: meetings/2020/LDM-2020-01-06.md ================================================ # 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([MaybeNullWhen(false)]out T t) { return other.TryGetValue(out t); // currently warns } ``` 2. ```C# [return: MaybeNull] T GetFirstOrDefault() { return null; // currently warns } ``` 3. Overriding/implementation ```C# class A { [return: MaybeNull] virtual T M(); } class B : A { 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` is treated as nullable contravariant. It's unfortunate that the CLR doesn't have capability to make `Task` full covariant, but handling even nullable alone would be useful. Moreover, if we later get the capability to mark `Task` 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` 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. ================================================ FILE: meetings/2020/LDM-2020-01-08.md ================================================ # 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 { internal abstract void F(ref U?? u) where U : T; } class B1 : A { internal override void F(ref U?? u) => default; // Is ?? allowed or required? } class B2 : A { internal override void F(ref U?? u) => default; // Is ?? allowed or required? } class B3 : A { internal override void F(ref U?? u) => default; // Is ?? allowed or required? } ``` Our understanding is that this would be: ```C# abstract class A { internal abstract void F(ref U?? u) where U : T; } class B1 : A { internal override void F(ref U?? u) => default; // We think the correct annotation is // void F(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 { internal override void F(ref U?? u) => default; // allowed? // This would not require the `where U : class` constraint because `U??` cannot // be confused with `Nullable` } class B2 : A { internal override void F(ref U?? u) => default; // The correct annotation is // void F(ref U u) // We could allow // void F(ref U?? u) // although it would be redundant } class B3 : A { internal override void F(ref U?? u) => default; // The correct annotation is // void F(ref U u) // We could allow // void F(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 { internal abstract F(ref U?? u) where U : T; } class B1 : A { internal override F(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`. 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() 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`). 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(this IEnumerable 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` 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. ================================================ FILE: meetings/2020/LDM-2020-01-15.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-01-22.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-01-29.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-02-03.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-02-05.md ================================================ # 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) { 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) 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) { 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. ================================================ FILE: meetings/2020/LDM-2020-02-10.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-02-12.md ================================================ # 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 { 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." ================================================ FILE: meetings/2020/LDM-2020-02-19.md ================================================ # 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." ================================================ FILE: meetings/2020/LDM-2020-02-24.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-02-26.md ================================================ # 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`, 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? ================================================ FILE: meetings/2020/LDM-2020-03-09.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-03-23.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-03-25.md ================================================ # 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! ================================================ FILE: meetings/2020/LDM-2020-03-30.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-04-01.md ================================================ # 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] 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. ================================================ FILE: meetings/2020/LDM-2020-04-06.md ================================================ # 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() 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. ================================================ FILE: meetings/2020/LDM-2020-04-08.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-04-13.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-04-15.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-04-20.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-04-27.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-05-04.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-05-06.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-05-11.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-05-27.md ================================================ # 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, ` '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; } ``` ================================================ FILE: meetings/2020/LDM-2020-06-01.md ================================================ # 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`? A: Yes. Implementing `IEquatable` 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. ================================================ FILE: meetings/2020/LDM-2020-06-10.md ================================================ # 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` 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. ================================================ FILE: meetings/2020/LDM-2020-06-15.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-06-17.md ================================================ # 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`, 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? x) where T: object? // b override void M1(T? x) where T: unconstrained // c override void M1(T? x) where T: // d override void M1(T? x) where T: ? // e override void M1(T? x) where T: null // f override void M1(T? x) where T: class|struct // g override void M1(T? x) where T: class or struct // h override void M1(T? x) where T: cluct // joke override void M1(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. ================================================ FILE: meetings/2020/LDM-2020-06-22.md ================================================ # 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` 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`. If it's cheap enough, we may be able to provide `IEquatable` 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` 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. ================================================ FILE: meetings/2020/LDM-2020-06-24.md ================================================ # 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 { static Task F = Task.FromResult(default(T)); // No problem static Task P => Task.FromResult(default(T)); //CS1961 static Task M() => Task.FromResult(default(T)); //CS1961 static event EventHandler 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` 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. ================================================ FILE: meetings/2020/LDM-2020-06-29.md ================================================ # 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 where TSelf : INumeric { abstract static TSelf Zero { get; } abstract static TSelf operator +(TSelf a, TSelf b); } ``` The `TSelf : where TSelf : INumeric` 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. ================================================ FILE: meetings/2020/LDM-2020-07-01.md ================================================ # 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() { 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`, 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() 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. ================================================ FILE: meetings/2020/LDM-2020-07-06.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-07-13.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-07-20.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-07-27.md ================================================ # 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) 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 thingtm". 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. ================================================ FILE: meetings/2020/LDM-2020-08-24.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-09-09.md ================================================ # 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>`, 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. ================================================ FILE: meetings/2020/LDM-2020-09-14.md ================================================ # 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` method was annotated on the return to return `IEnumerable`, 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`, 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. ================================================ FILE: meetings/2020/LDM-2020-09-16.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-09-23.md ================================================ # 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`, which is a very useful type, but it uses a magic BCL-internal type called `ByReference`. 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`. * There is a workaround for this behavior: instead of taking a `ref T` in the constructor, take a `Span`, 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. ================================================ FILE: meetings/2020/LDM-2020-09-28.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-09-30.md ================================================ # 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` | | 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`. 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`) 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` 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. ================================================ FILE: meetings/2020/LDM-2020-10-05.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-10-07.md ================================================ # 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` 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` 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`. This would be acknowledging special behavior for `ReadOnlySpan` 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`. There were two open questions: #### Should we allow `Span` The question is if `Span` should also be allowed as well as `ReadOnlySpan`. All of the same arguments about compat apply to `Span`. 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` is allowed. `Memory`/`ReadOnlyMemory` 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` and `ReadOnlySpan` 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. ================================================ FILE: meetings/2020/LDM-2020-10-12.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-10-14.md ================================================ # 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](#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 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` 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. ================================================ FILE: meetings/2020/LDM-2020-10-21.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-10-26.md ================================================ # 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.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. ================================================ FILE: meetings/2020/LDM-2020-11-04.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-11-11.md ================================================ # 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 `$` 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 `$` 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. ================================================ FILE: meetings/2020/LDM-2020-11-16.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-12-02.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-12-07.md ================================================ # 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. ================================================ FILE: meetings/2020/LDM-2020-12-14.md ================================================ # 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`|`new List { 3, 4 }`| ? | **List patterns fit in here** |Dictionary|`Dictionary`|`new Dictionary { { 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. ================================================ FILE: meetings/2020/LDM-2020-12-16.md ================================================ # 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. ================================================ FILE: meetings/2020/README.md ================================================ # C# Language Design Notes for 2020 Overview of meetings and agendas for 2020 ## Dec 16, 2020 [C# Language Design Notes for December 16th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-16.md) - 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` 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 [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 [C# Language Design Notes for Jan 8, 2020](LDM-2020-01-08.md) 1. Unconstrained type parameter annotation 2. Covariant returns ## Jan 6, 2020 [C# Language Design Notes for Jan 6, 2020](LDM-2020-01-06.md) 1. Use attribute info inside method bodies 1. Making Task-like types covariant for nullability 1. Casting to non-nullable reference type 1. Triage ================================================ FILE: meetings/2021/LDM-2021-01-05.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-01-11.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-01-13.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-01-27.md ================================================ # 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.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. ================================================ FILE: meetings/2021/LDM-2021-02-03.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-02-08.md ================================================ # 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() where T : IHasStatics { int v = T.GetValue(); } UseStatics(); // C satisfies constraint UseStatics(); // 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. ================================================ FILE: meetings/2021/LDM-2021-02-10.md ================================================ # 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 = System.Collections.Generic.Dictionary` 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. ================================================ FILE: meetings/2021/LDM-2021-02-22.md ================================================ # 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 = System.Collections.Generic.Dictionary;`. 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` 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;`), 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. ================================================ FILE: meetings/2021/LDM-2021-02-24.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-03-01.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-03-03.md ================================================ # 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` 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` 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` 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)GetTodo); [HttpPost("/")] Todo EchoTodo([FromBody] Todo todo) => todo; app.MapAction((Func)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)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); ``` ================================================ FILE: meetings/2021/LDM-2021-03-10.md ================================================ # 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` 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. ================================================ FILE: meetings/2021/LDM-2021-03-15.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-03-24.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-03-29.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-04-05.md ================================================ # 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`), 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 where T : I { 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 { 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. ================================================ FILE: meetings/2021/LDM-2021-04-07.md ================================================ # 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.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 ================================================ FILE: meetings/2021/LDM-2021-04-12.md ================================================ # 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 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. ================================================ FILE: meetings/2021/LDM-2021-04-14.md ================================================ # 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). ================================================ FILE: meetings/2021/LDM-2021-04-19.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-04-21.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-04-28.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-05-03.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-05-10.md ================================================ # 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); 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. ================================================ FILE: meetings/2021/LDM-2021-05-12.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-05-17.md ================================================ # 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 ` `, a completely empty line is fine, as is a blank line with ` `. Both are prefixes of the whitespace to be removed. However, a line with ` ` 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}`. ================================================ FILE: meetings/2021/LDM-2021-05-19.md ================================================ # 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. ================================================ FILE: meetings/2021/LDM-2021-05-26.md ================================================ # C# Language Design Meeting for May 26th, 2021 ## Agenda 1. [Open questions in list patterns](#open-questions-in-list-patterns) ## Quote of the Day - "If somebody lays down on the track I'm perfectly willing to be taken hostage" ## Discussion ### Open questions in list patterns https://github.com/dotnet/csharplang/blob/main/proposals/list-patterns.md Today we looked at a number of open questions in list patterns, currently being implemented. #### Patterns allowed after a slice operator We first revisited the question of what to allow after a `..` pattern. Previously we stated that any possible pattern should be allowable in this position; however, there is some concern that this could be confusing to code readers. We forsee the vast majority of patterns in this location being simply capturing the slice into a pattern, and there is a dissimilarity here with other containing pattern contexts. For positional, property, list, and length patterns, nested patterns are all visually inside delimiters (`()`, `{}`, and `[]`, respectively). Here, the pattern would be on the slice element, but _not_ visually inside. There is also some concern that perhaps we should take the position that using a nested pattern here is just generally not good form: we think that such syntax will become quickly unreadable, going against the general goal of the feature. There's also potential for being visually hard to parse: something like `..var x and not null` parses very differently in a pattern (as `..(var x and not null)`) than it does in regular syntax, for something like `..1+2` (which parses as `(..1)+2`). Looking at these concerns gives us 3 general options for how to move forward: 1. Simplify the syntax, and only allow `..identifier`. Users can omit the type entirely. General patterns are not allowed. 2. Only allow `..var pattern`. This has symmetry with other declaration patterns. 3. Allow all patterns. Option 1 is initially somewhat attractive because it will simplify the 99% case here. However, we have some concerns: it's not regular with other declarations, and some users do not like implicitly-typed variables. It also makes it harder to change our minds here in the future, if we ever wanted to add an `all` or `any` pattern. Meanwhile, version 2 suggests a pattern generality that would not exist, and (to the user that runs into this) for seemingly arbitrary reasons. Given our concerns with the first two approaches, we think that, despite earlier concerns, approach 3 is the way to go. We can take a stronger stance in the IDE and our documentation with fixers and best practices to help ensure that nested patterns don't get too crazy here. ##### Conclusion Original design upheld, any pattern is allowed after a slice. #### Multi-dimensional array support Currently, the list pattern specification has an open question as to whether MD-arrays should be supported. One of our goals for this feature was to bring symmetry between object creation and deconstruction/pattern forms, and array/collection initializers work with MD-arrays today, suggesting they should be supported in patterns. However, our current rules depend on types being indexable/countable/sliceable, which MD-arrays are not today. They're also generally not a huge area of investment for either the language or the runtime, so it feels odd to make sure they work here but not also with the rest of the indexing/counting/slicing features. ##### Conclusion Not supported. If we want to make a general MD-array focused release, we would want to revisit all the areas they're currently lacking, not just list patterns. #### Binding rules for Slice This is a double-sided question on how we bind `Slice` methods: should we allow default parameters, and should we allow extension methods for Slice? In general, we think we should follow the same rules as the existing support around slicing, which is no on both questions. We can look at a general feature to loosen the restrictions as a separate proposal. ##### Conclusion No default parameters or extension methods. #### Tests emitted for `{ .. }` The main question here is whether `{ .. }` should emit a check that `Length` or `Count` is greater than or equal to 0, or if it should simply be a no-op. This must be specified in the language, as not doing so would result in `default(ImmutableArray) is { .. }` having undefined behavior (it will throw or not, depending on whether `Length` is evaluated). We think that `..` without a trailing pattern and without any other element patterns is similar in concept to a `_` pattern: `default(ImmutableArray) is { Length: _ }` does not actually evaluate `Length`, and thus won't throw. The `..` is the same: _other_ things cause `Length` to be evaluated, such as the precense of other element patterns or a length pattern. A slice pattern can change the nature of that check (moving it from an `==` to a `>=`), but it doesn't add the check on its own. Similarly, `.._` will not call slice, just like other discard operations. This does mean that, for list implementations that do not follow framework design guidelines and return negative lengths or counts, `{ .. }` will not fail when they do so, but we think that is fine. ##### Conclusion `{ .. }` will not emit a `Length` check, and `{ .._ }` will not call `Slice`. ================================================ FILE: meetings/2021/LDM-2021-06-02.md ================================================ # C# Language Design Meeting for June 2nd, 2021 ## Agenda 1. [Enhanced #line directives](#enhanced-line-directives) 2. [Lambda return type parsing](#lambda-return-type-parsing) 3. [Records with circular references](#records-with-circular-references) ## Quote of the Day * If you make it `..`, does that mean the end is included or not? ## Discussion ### Enhanced #line directives https://github.com/dotnet/csharplang/issues/4747 This proposal covers a set of enhancements for the `#line` directive that solve several problems that the razor team has had over the years with the existing directive causing mismapping of code in the debugger. While the razor team is the main user and driver of this scenario, these enhancements will help any DSL authors that embed C# in their code and want to have a debugging experience. The issue today is that nested expressions don't have sequence points emitted correctly, and putting the expression on a newline is often not a sufficient solution to ensure that they end up correctly represented. We brainstormed a few different ways to determine the span of an expression for this mapping. Inherently, the enhanced directive needs to be able to specify the length of the mapped line, because users might end up putting multiple separate fragments of C# code on the same line in the original text file. To be able to represent that accurately, we therefore need to ensure that the exact length is represented. We considered whether we could do this via the total length of the mapped expression, rather than specifying the end line/column of the directive. However, there are a few concerns with that approach: 1. Files are often written and checked into source control with different encodings/line endings, most commonly differing between `\r\n` and `\n` for line endings. For maps that span multiple lines, therefore, the total character count could end up being wrong after compilation depending on what system created the mapping and what built it. 2. For humans attempting to author and debug generators that use these directives, line and column are easier to map to the real code that they're writing. Every editor includes this information when editing a file based on the current cursor location. This makes understanding what is happening easier. We also considered how the line and column information is defined. Today, C# in general defines specific characters that are newlines (in part to ensure that the existing `#line` directives are well-defined). This doesn't change with the new proposal, and some editors don't always agree on what constitutes a new line (such as vertical tab), this proposal doesn't change these inconsistencies. For character offsets, we define it as UTF-16 characters, following with the rest of the .NET ecosystem here. If the ecosystem ever wants to attempt to change to a different encoding, it will be a much larger effort and adjusting the semantics of `#line` should be trivial by comparison. Finally, we looked at a few different syntax options for the directive, as we felt that just 5 numbers next to each other was unnecessarily difficult to read: 1. `#line (10,20)-(11,22) 3 "a.razor"` 2. `#line (10:20)-(11:22) 3 "a.razor"` 3. `#line 10:20-11:22 3 "a.razor"` 4. `#line 10,20-11,22 3 "a.razor"` 5. `#line (10,20)-(11,22):3 "a.razor"` 6. `#line (10,20) (11,22) 3 "a.razor"` 7. `#line (10,20):(11,22) 3 "a.razor"` 8. `#line (10,20)..(11,22) 3 "a.razor"` We think options without the parens are a bit confusing, because `-` binds more tightly than `,` or `:` in c#, making it look like `10 , (20-11) , 22`. We also think that `,` is the more common line/column separator. Given this, we'll go with option 1. #### Conclusions Existing proposal is accepted with the following clarifications: 1. Column info is described as UTF-16 characters. 2. The syntax is `#line (number , number) - (number , number) number string`. #### Lambda return type parsing https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md In a previous LDM, we decided that lambda return types should go before the parameter list, as in `ReturnType (ParamType param) => { ... }`. This has presented some challenging parsing scenarios. ```cs F(b ? () => a : c); // Conditional expression or lambda that returns 'b?'? ``` This issue is very similar to generics and greater-than/less-than parsing, and can benefit from the same solution: we specifically define a set of characters after the lambda body in the language and use them to determine, at that point, whether the expression is a ternary or a lambda with a return type. For example, if token after the lambda is a `)`, it couldn't have been a ternary. This will take effort, but should be doable. Another parsing problem comes from multiplication: ```cs F(x * () => y) // Is it multiplication, or a lambda with a return type of x*? ``` Here, our saving grace is that `*` binds more tightly than the lambda expression today, so this code does not parse. This should allow us to disambiguate the cases here. Again, this will take effort, but should be doable. While considering these cases, we also thought about lambda naming as a potential easier disambiguation point. We could allow lambdas to be named (perhaps just with a `_` to start), and require them to have a `_` to use the return type. Further, we need to consider this now because the `identifier () => {}` syntax can only mean one thing: `identifier` will either need to be the return type, or the name of the lambda. As we continue to narrow the gap between lambda expressions and local functions, it stands to reason that we may at some point want to give them names (despite the phrase "named lambda" being a bit of an oxymoron). Further, choosing to make the single-identifier case mean the type instead of the name breaks with the precedent we have around lambda parameters, where the name can be specified without the parameter type. On the other hand, we do feel that the return type is the more common thing to want to specify for a lambda. While named lambdas could be useful for recursive scenarios, we don't see this as a driving need, while the return type is explicitly being driven to enable .NET 6 scenarios. Additionally, if we allowed lambda names for recursion we'd sign ourselves up for recursive type inference, as we'd have to start inferring the return type of lambdas that can call themselves. By saying that the single-identifier case is for specifying the return type, we separate that out into a different feature, and we have a natural type to allow as the return type of the lambda for this case: `var`. In order to protect this design space, we'll disallow `var` as the explicit return type of a lambda expression. #### Conclusion We think the existing difficulties should be possible to parse, with a bit of effort. We'll reserve `var` as an explicit return type to protect our design space around recursive type inference in lambdas, if we ever decide to try and tackle that challenge. ### Records with circular references https://github.com/dotnet/roslyn/issues/48646 In the brief time left at the end of the meeting, we wanted to re-examine our previous position around circularity in record types. There are several ways that users can end up with circular data structures in records, in both obvious (and detectable) ways of direct mutable recursion, such as in a doubly-linked list, and indirectly, via nested mutation. Our initial position in records was that this isn't something we want to try and solve in the language, but could be solved via source generators. There are a number of points where users might want to customize aspects of record code generation, such as using a different equality comparison option for strings, or doing sequence/set equality for collections. The question we want to answer, therefore, is whether we should move the cliff to generators up a bit further and add a method of excluding specific fields from record semantics to the language, and whether we should add a new warning wave warning to inform users when they're setting themselves up for direct recursion issues. This warning can't be perfect, however: it cannot catch mutually-recursive types where one type has a mutable field of the other type, particularly when fields of a non-sealed type are involved. For example, record `A` has a mutable field of type `IDisposable`. Record `B` has an immutable field of type `A` and implements `IDisposable`. `A` is constructed, then `B` is constructed with that instance of `A`, then `A`'s mutable field is set to `B`. Unless we warn on every mutable field of a non-sealed type, that case is impossible to detect, and we're cautious of overwarning and then causing users to miss real issues. #### Conclusion A smaller working group will meet to explore this in more depth, and make a recommendation to the LDM about the approach we should take. ================================================ FILE: meetings/2021/LDM-2021-06-07.md ================================================ # C# Language Design Meeting for June 7th, 2021 ## Agenda 1. [Runtime checks for parameterless struct constructors](#runtime-checks-for-parameterless-struct-constructors) 2. [List patterns](#list-patterns) 1. [Exhaustiveness](#exhaustiveness) 2. [Length pattern feedback](#length-pattern-feedback) ## Quote of the Day - "They should probably be told off, I was going to say something else, but they should be told off in code review" ## Discussion ### Runtime checks for parameterless struct constructors https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/parameterless-struct-constructors.md In testing parameterless struct constructors, we've found a new bug with `Activator.CreateInstance()` on older frameworks. This code will print `True, False` on .NET Framework 4.8: ```cs System.Console.WriteLine(CreateStruct().Initialized); // True System.Console.WriteLine(CreateStruct().Initialized); // False (On .NET Framework) T CreateStruct() where T : struct { return new T(); } struct S { public readonly bool Initialized; public S() { Initialized = true; } } ``` This is due to a caching bug in the framework, at it essentially means that `Activator.CreateInstance()` will only correctly invoke a struct constructor the first time it is used, and any subsequent calls in the same process will run the constructor and then zero-init on top of that value, so side-effects (such as `Console.WriteLine`) would be observed, but field initialization would not. This is similar to the bug that sunk parameterless struct constructors the last time we attempted to add them, but .NET Core and 5 do have the correct behavior here. As .NET Framework is not a supported target platform for C# 10, however, we don't think this is a showstopper like it would have been back in C# 6. We think that this is a good place for an analyzer to warn consumers, as the compiler itself doesn't want to try and infer what framework a user is targeting based on the presence of APIs, and runtime feature checks in the compiler are always hard errors and cannot be worked around. #### Conclusion An analyzer will be incorporated into the default analyzer pack to warn users about use of parameterless struct constructors on older, unsupported framework targets. ### List patterns https://github.com/dotnet/csharplang/issues/3435 #### Exhaustiveness We have a couple of test examples that add some exhaustiveness complications: ```cs _ = list switch { { .., >= 0 } => 1, { < 0 } => 2, { Count: <= 0 or > 1 } => 3, }; ``` This switch expression should be considered exhaustive, but it will require understanding that the `>= 0` in the first pattern _can_ apply to element 0, and that the last expression should then handle all the rest of the cases. Another similar case is this one: ```cs _ = list switch { { .., >= 0 } => 1, { ..{ .., < 0 } } => 3, }; ``` While this example is a bit silly, it illustrates the general thing we want to: list patterns on a slice should count towards the containing list pattern for exhaustiveness. While there is the possibility that this means a slice could give bad results (ask for a slice from x to y, get the wrong thing back), we generally consider such types to be intentionally subverting user expectations. A list pattern would not be the only place where such a type mislead a user, and we don't think there's a reasonable way to protect against such types. ##### Conclusion We should make exhaustiveness work for these scenarios. #### Length patterns We've heard from a number of our more heavily-invested users through channels such as Twitter, Discord, Gitter, and GitHub Discussions that our existing plan for length patterns have been generally confusing and/or actively misleading. The major issues that have been raised: 1. While there is symmetry with array size specifiers in construction, that symmetry doesn't carry to any other collection creation. 2. The `[]` syntax is most often used for accessing at an index, which length patterns don't do. 3. It is extremely tempting to do `{}` as the empty list pattern as a reduction of the rest of the patterns. We've brainstormed a few ways to try and address various parts of this feedback: 1. Have a special `length` pattern that can be used in a list pattern: `{ 1, 2, 3, length: subpattern }`. This pattern can only be used in list patterns, so the empty list would be `{ length: 0 }`. This can help with issues 1 and 2, but not with 3. 2. Go back to square brackets, as in the original proposal. This helps with all 3 issues, but reintroduces the new issue that `[]` isn't symmetric with array and collection initializers. 3. Use parens/positional patterns. This seems interesting, but has a problem because positional patterns will check for `ITuple` on inputs today. 4. Require using indexers in nested patterns: `{ [0]: pattern, [2]: pattern }`. This is understandable, but extremely verbose. 5. A more general version of 1: just allow combining list and property patterns into one `{}`. We could potentially have a separator token such as `,` or `;`, which would allow us to have a pretty simple empty list pattern of `{ , }` or `{ ; }`. We didn't get too deep on any particular syntax with the time remaining, but we are convinced that we need to take another look at these. ##### Conclusion A smaller group will hammer out a proposal and bring it back to LDM. We need to take the time to get this right, which may mean that the feature slips and does not make C# 10. ================================================ FILE: meetings/2021/LDM-2021-06-14.md ================================================ # C# Language Design Meeting for June 14th, 2021 ## Agenda 1. [Open questions in CallerArgumentExpressionAttribute](#open-questions-in-CallerArgumentExpressionAttribute) 2. [List pattern syntax](#list-pattern-syntax) ## Quote of the Day - "My first reaction is that I thought we got rid of the C# test team for testing esoteric scenarios" ## Discussion ### Open questions in CallerArgumentExpressionAttribute https://github.com/dotnet/roslyn/issues/52745#issuecomment-849961999 https://github.com/dotnet/csharplang/issues/287 #### VB Support Conclusion: yes, we should support VB here. #### Generated code This question centers around this example: ```cs void M([CallerMemberName]string arg1 = "1", [CallerArgumentExpression("arg1")]string arg2 = "2") { Console.WriteLine(arg2); // What gets printed? } void M2() { M(); } ``` As we see it, there are 5 possible values that a reasonable programmer could expect. 1. `null` 2. The empty string: `""`. 3. The default value of `arg1`, as an expression: `"\"1\""`. 4. The default value of `arg2`: `"2"`. 5. The value filled in for `arg1`, as an expression: `"\"M2\""`. We don't think option 1 is useful here, as the parameter is attributed to not accept `null`, and this would just mean that every use of `CallerArgumentExpression` would be required to handle the `null` case. We also don't think that options 3 or 5 are really correct either: the attribute here is about providing the specific syntax the user used, not the _value_ the user used. There are many ways to express the values given as a constant value: we could just turn `"M2"` into a string, or we could say `"\"" + "M" + "2" + "\""`. Both are technically correct, but neither reflects what the user actually wrote. Finally, for option 3, we think that this is trying to second-guess the user. They provided a default value for the parameter, and if we never respect that value then the default value was useless. Given these, we think the correct approach is option 4. ##### Conclusion Option 4: the default value of the parameter will be used. We will not turn compiler-generated code into equivalent C# expressions. #### Self-referential arguments Consider these examples: ```cs void M3([CallerArgumentExpression("arg1")]string arg1 = ""); // Warning? M3(); // What gets passed? null? ""? ``` ##### Conclusion We think this is absolutely worth a warning in source code, and if in metadata then we should just provide the default value of the parameter. #### Span of the expression Consider this example: ```cs M(arg1: /* Before */ "A" + /* Mid */ "B" /* After */); // What is passed for arg2? ``` There are 3 possible answers for this: 1. The argument expression should refer to the start `arg1:` to the end of the position, either `)` or `,`, depending on whether the argument is followed by another or not. 2. The argument expression should refer from just after `arg1:` to the end of the position, not including the argument specifier. 3. We should ignore any trivia, and just have the expression span from the start of the real C# executable code (the string `"A"`) to the end of the real executable C# code (the end of `"B"`). While there are legitimate argument for 1 or 2, we don't think they provide enough benefit to make up for the fact that they will be including leading and trailing whitespace that we don't believe is useful for the users of this attribute. Given this, we think option 3 is the correct way forward. ##### Conclusion Option 3: we go from the start of real C# executable code to the end of the expression, not including any leading or trailing trivia. ### List pattern syntax https://github.com/dotnet/csharplang/issues/3435 #### Revisiting syntax We've heard a lot of community feedback around our existing proposal for length patterns, which looks like this: ```cs _ = list is [0]; // List has length 0; ``` Top among user feedback is that this syntax is: 1. Confusing. Even among users who tend to give the LDM the benefit of the doubt with syntax choices, we've heard vociferous feedback that this is not clear and that there is not a clear enough parallel to array creation length specifiers to make this obvious. 2. Unnatural for the base case. The traditional recursive pattern that languages with strong pattern matching constructs use is some number of cases that pull out interesting bits, and then a base case to handle the empty list. Unfortunately, `{ }` is _not_ the empty list case, despite being what otherwise appears to be an empty list pattern. While in some cases this happens to work because all that's left to handle is when the input is non-null, we don't think it will lead to clear code. A smaller group met to try and brainstorm some approaches to solving the issue. These are: 1. Return to the original proposal syntax, using square brackets (`[]`) to denote a list pattern. This breaks with the correspondence principle, but it does have stronger parallel with other languages, has a natural base case, and we could potentially add a new creation form that achieves correspondence (and take the time to address things like `ImmutableArray`, which cannot be initialized by collection initializers today). 2. Use a separator at the end of a list pattern, such as `;`: `{ 1, 2, 3; } or { ; }`. This separator would be required, giving a few advantages: 1. Because the separator is always required, the base case looks like the shortest version of the pattern. 2. Allows list and property patterns to be combined into a single block. 3. Gives us an avenue to allow collection and property initializers in the same block, by reusing the same syntax later. 3. Keep the status quo. Users will get used to the syntax. These suggestions led to spirited debate. An unfortunate truth here is that, no matter what approach we take, we have discrepency with some aspect of the language. The semicolon separator approach allows us to mostly keep in line with collection initializers, but the trailing `;` being required is very different and a wart. Square brackets, on the other hand, are _very_ different from the rest of C#. Today, square brackets are used for indexing operations and for specifying the length of an array. Nothing in C# uses them to denote a group of things that is a collection. There are proposals to use these brackets for an improved version of collection initializers though, giving us an opportunity for future fulfillment of the correspondence principle, even if it won't be fulfilled on initial release. Patterns also already have some discrepency with the rest of C#, particularly around `and/or/not` patterns, which aren't words used in the rest of the language. ##### Conclusion We will go with option 1: using square brackets for the list pattern. We still need to decide if and how these can be combined with recursive patterns, but it gives us the most flexibility with regard to future regularity in the language. #### Length patterns Orthogonally, we have also come up with a few suggestions for the length pattern: 1. Recognize a special `length` keyword as a property pattern: `{ length: 10 }`. When a type is Countable, this property is available, and it will bind to `Length` or `Count` as appropriate. 2. Recognize the `Length` and `Count` properties on: 1. Types that are countable 1. Types that are both countable and indexable 3. Keep the status quo. Given that we've chosen square brackets for our new list pattern syntax, option 3 is out. This leaves us with option 1 or 2. We originally wanted special length patterns in the language because we wanted list patterns to work on a type that didn't have a `Length` or `Count` property: `IEnumerable`. While we still want to do this, the implementation work is quite complex and we think that it might not get into the initial version. So, while we're not ruling out 1, we don't think it's necessary quite yet. Option 2 is nice, but it has a couple of wrinkles. First, it's a breaking change, because we specially recognize that the property in question cannot be negative. This can affect flow analysis and introduce warnings or errors about unreachable patterns, and remove warnings about non-exhaustive switch expressions. It's not pretty, but we think we can tie this recognition to a warning wave. It will be the first time a warning wave _removes_ warnings, instead of adding them, but we think it's the right move. Second, what types should we specially recognize here. Countable is a very broad definition in C#: it pretty much just means has an accessible property named either `Count` or `Length`. We think that's too broad for general recognition; while collections should never have negative lengths, the word `Count` or `Length` on its own is not strong enough evidence that the type is a collection. Instead, we think we should require both countable and indexable, the same requirements for using a list pattern in the first place. This will ensure that the type at least behaves like a collection, and while there still might be such types that return negative `Count`s or `Length`s, patterns are only one place where such types will confuse their users and we don't think it's an edge case that should derail the whole feature. ##### Conclusion We will specially recognize the `Count` and `Length` properties on types that are both countable and indexable, assuming that it can never be negative. #### Timing Given that the changes we've made today are specifically driven by community feedback, we feel that this feature needs more bake time than is left in the C# 10 cycle. The feature will ship in preview, either with C# 10 (like static abstracts in interfaces) or shortly after 10 is released. We want to make sure that the course-corrections we're making here help community understanding of the feature, and we don't have enough time before C# 10 is released to implement the changes and get them in customer hands before 10 is declared final. ================================================ FILE: meetings/2021/LDM-2021-06-21.md ================================================ # C# Language Design Meeting for June 21st, 2021 ## Agenda 1. [Open questions for lambda return types](#open-questions-for-lambda-return-types) 2. [List patterns in recursive patterns](#list-patterns-in-recursive-patterns) 3. [Open questions in async method builder](#async-method-builder-overrides) 4. [Email Decision: Duplicate global using warnings](#email-decision-duplicate-global-using-warnings) ## Quote of the Day - "[redacted] is suddenly very blue." "They're only transmitting the blue channel." "They don't like where this is going." ## Discussion ### Open questions for lambda return types https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md #### Method type inference from return types The question here centers on the following scenario: ```cs static void F(Func f) { ... } F((int i) => i); // ok (in C# 9 and 10) F(i => i); // error (in C# 9 and 10) F(int (i) => i); // what should the behavior in C# 10 be? ``` The reason for this is that the way anonymous method inference is worded, we specifically pay attention to types specified in source. The question here, then, is whether we should get information from an explicit return type of a lambda, and if so what type of inference we should make? For the first question, we think the answer is pretty simple: the user explicitly put a type in, so we should see it. Anything else will make the feature feel incomplete, and will likely end up being a breaking change _somehow_ if we were to do it later. For question 2, we looked at the current behavior for parameters. Today, we make an _exact_ inference from explicit lambda parameter types to type parameters, rather than an upper bound inference. This means that the type parameter must exactly match the lambda parameter type, rather than allowing any kind of variant conversion in that position. We could potentially loosen this restriction in a future version (and VB.NET actually does this already), but we do question the merits of the feature a bit: why would a user be using a different type here? The lambda is only being used in this one location, so it should just be using the type it needs. We can squint and see a few potential use cases, but they don't rise to the 100 points for us at the moment. Therefore, to be consistent with parameters, we'll make an exact inference for lambda return types. This also needs to apply for other variance scenarios, such as lambda->delegate type conversions: ```cs Func f = string () => ""; ``` This will also require an exact match, and thus the above code will error. ##### Conclusion Variance is disallowed for explicit return types in lambdas. This means that type inference will make an exact inference from explicit return types to type parameters in that position, and variant conversions will be disallowed for lambda->delegate conversions. #### Parsing ref return types We have a question on whether ref return types will need to be surrounded by parens to make parsing work. For example: ```cs Delegate d1 = (ref int () => x); // ok Delegate d2 = ref int () => x; // Currently parses as ref (int () => x) Delegate d3 = ref b ? ref int () => x : ref int () => x; // What should this mean? ``` This seems like a problem we should be able to fix with some clever grammar rules. Semantically, the idea of `ref`ing a lambda doesn't make much sense, as `ref`s should point to locations, and lambdas are not a location. ##### Conclusion Lets do the fancy grammar work to make this parse like the user would want. ### List patterns in recursive patterns https://github.com/dotnet/csharplang/issues/3435 Last week's discussion left us with a strong conclusion about the general form of list patterns: they will use `[]` as the list pattern syntax, so matching an integer list with the numbers 1-3 would be `list is [1, 2, 3]`. However, we still need to address where a list pattern will be allowed. We have a few proposals: 1. Do not put the list pattern in a recursive pattern at all. To combine a list pattern and a property pattern or type pattern, `and` will need to be used. 2. Put the list pattern after the property pattern in regular recursive patterns, and require that a property pattern is used (even if empty) when combining a list pattern with either a type pattern or a positional pattern. 3. The same as 2, but only require the property pattern for the actually ambiguous case of the empty list pattern. 4. Have a separate list pattern, and also allow the list pattern as an optional trailing component to a property pattern. The effect of this version is a combination of 1 and 2. One concern with the versions that allow property patterns to be directly combined with list patterns is that it recomplicates recursive patterns, after C# 9 decomplicated them by allowing the type pattern to be used on its own. We don't think that the combination of type and list patterns is going to be a common case, and we therefore have concerns about the special disambiguation rules that they would need as a part of recursive patterns as it would just never become a common enough case for users to internalize the rules here. This would make both reading and writing such patterns less straightforward. However, if we're wrong about the commonality of this combination, then requiring an `and` pattern everywhere would also get tedious and repetitive. Ultimately, we think we need to get a version of this into preview first and see if users actually end up needing the combination patterns. To get the best feedback, therefore, we'll go with option 1 for now, and if we hear complaints about needing to use `and` to combine various patterns with list patterns frequently we can revise our approach. Given that we are choosing to go with 1, we also looked at whether to allow a trailing designator after the list pattern. This starts to get to a slippery slope: why allow trailing designators but not property or type patterns? However, it is our job as the LDT to determine how far down a slippery slope we want to go, and stop there. We think that, unlike property or type patterns, designators are going to be an extremely common case for list patterns, particularly in nested contexts, and we already have other feedback from patterns to add more designators in places (such as after a `not null` pattern). Given this, we'll add an optional designator after list patterns. #### Conclusion List patterns will be their own pattern type, not part of recursive patterns, and will have an optional trailing designator. ### Open questions in async method builder https://github.com/dotnet/csharplang/issues/1407 #### Async method builder override validation The first question today is whether we should validate that the return type of a method or lambda marked with `AsyncMethodBuilder` is actually task-like. For example, should this be legal: ```cs [AsyncMethodBuilder(typeof(MyCustomBuilder))] public async int M() => ...; ``` Our options here are: 1. Require that the return type be task-like, even though we won't use the information from that type. 2. Do not validate that the return type is task like. This will even include lambdas with an inferred return type. 3. Do not validate, but require that lambdas marked with this attribute have an explicit return type. We don't like number 2 because the user is saying what builder type the compiler should use, but not actually saying what type the builder is building. This feels backwards. We also think 1 is too restrictive: the user has to make the type a task-like type, but then the compiler discards all the information from that type when actually emitting the method. Given these, our preference is 3. ##### Conclusion We will not validate that the return type is task-like, but when applied to a lambda the lambda must have an explicit return type. #### Async method builder on public members For task-like types, we have a requirement that the builder type must have the exact same accessibility as the task-like type itself, to ensure that users don't run into scenarios where the task-like type is not usable in an async context despite having the attribute. This isn't a concern for the attribute on a method, however, because the attribute doesn't affect consumers of the method. It only affects the codegen for the method itself. These attributes are also not inherited, so if an override would like to use the same builder type it will have to manually apply the attribute itself. Given that, we think that it should be fine if the visibility of the builder type is not the same as the visibility of the return type, so long as both are visible at to the method using those types. ##### Conclusion Accessibility of the builder type does not need to match the accessibility of the return type in method contexts. #### Factory indirection The existing proposal has an indirection for allowing a factory to create the builder, which the compiler then uses in the method body. We don't have a use case for this at the present, so we will remove it from the spec to simplify the proposal. ##### Conclusion Factory indirection is removed. ### Email Decision: Duplicate global using warnings Some early feedback on global usings has suggested that duplicated using warnings can be painful, particularly if a global using and a non-global using are duplicated. Code generation can very easily run into this scenario, and it doesn't have an easy resolution. After discussion, we decided to remove the duplicated using warning for when a global using directive or a regular using directive are duplicated. The compiler will still issue a hidden diagnostic to inform the IDE and analyzers, but there will be no user-facing warning by default. We will still error when the same alias is defined more than once, even if the alias refers to the same type, as we feel that aliases are more like declarations and we believe declarations should be intentional. #### Conclusion Duplicate using warning is removed when a global using is duplicated by another using (global or otherwise). ================================================ FILE: meetings/2021/LDM-2021-07-12.md ================================================ # C# Language Design Meeting for July 12th, 2021 ## Agenda 1. [C# 10 Feature Status](#c-10-feature-status) 2. [Speakable names for top-level statements](#speakable-names-for-top-level-statements) ## Quote of the Day - "You do realize there was a quote of the day there when you said there are still holes in the interpolated strings feature?" _collective groan_ "I keep track of the important stuff" ## Discussion ### C# 10 Feature Status https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md#c-next We spent the first half of today going through the remaining features that are currently being worked on by the compiler team and prioritizing them for what is going to make C# 10, and what will need to be pushed out to early preview for C# 11. Of the things still under C# Next, the current statuses are: - Parameterless struct constructors - will be merged for 17.0p3. This includes integration with record structs. - nameof(parameter) - This is becoming increasingly important as we add new features that reference other parameters in attributes. Nullable already existed, but we're additionally adding `CallerArgumentExpression` and `InterpolatedStringHandlerArgument` in C# 10. However, it is unlikely to be prioritized enough for C# 10. - Parameter!! - This has IDE impact, so while the compiler-side is basically done, it does need some more work. We additionally want more community bake time to let customers actually use the feature before we ship it. We'll be aiming for early C# 11 previews. - Relax ordering of partial and ref - This is not being worked on, and should be removed from the status page. - CallerArgumentExpression - The feature work for this is very nearly done. Should be in for C# 10. - Generic attributes - The feature work for this is complete. Needs a few more tests, then should be merged for C# 10. - List patterns - We're aiming for an early preview in C# 11. - Remaining lambda work - Wave 1 (conversion to Delegate/Expression and attributes) and wave 2 (explicit return types) have been merged. We are currently working on Wave 3 (natural type and synthesized delegate types). Should be in for C# 10. ### Speakable names for top-level statements When we initially shipped top-level statements in C# 9, we made the decision that the type and method that the statements generated should be unspeakable. Our reasoning here was that the only reason the type or method would be needed would be for reflection to obtain the assembly, and users could use another type in the assembly for this. However, as work continues on the lighter ASP.NET feather templates, we are encountering scenarios where there actually _aren't_ any other types in the user's assembly, which makes obtaining the assembly for unit testing very complex. To address this, we considered a mechanism to make the type and method speakable by user code. We have a few main axes of choice for a proposal, several of which are tied together: * Introducing a named type into a compilation could break that compilation. Do we want to make it conditional on whether the user already has a type with the name we choose? * Do we want to tie generating the type to language version or not? * Will we allow the type to be speakable from the current compilation, or only from assemblies that _reference_ the current compilation? * Will the type be partial or not? * Do we want just the type to be speakable, or the type and the method both? * Do we want the type to be public or internal? First, we looked at the breaking change. We believe we would name the type `Program` and the method `Main`, following long C# template tradition, so our real question is "who is going to create a program that is using top-level statements and has a type named `Program`?". We believe this to be a sufficiently small number of users that we are ok with the breaking change. At the same time, we also looked at whether we should only generate the type when the user's language version is set to the appropriate version, meaning that C# 9 would still generate an unspeakable name. After some consideration, we don't think that it's worth it to have separate generation strategies, as it will complicate the code for very little chance of breaking. It does mean that there is the potential for someone to upgrade the compiler and not the language version and observe a breaking change from a different assembly, but we're ok with this. Next, we looked at the various levels of speakability. Only allowing the type to speakable by external assemblies is the minimal change to make, but it feels oddly inconsistent. Similar reactions were had for the method name: while we don't have a specific scenario currently for the method naming being speakable, we feel it would simplify the mental model for either everything to be speakable, or nothing to be speakable. We've always had the view that top-level statements are simply a sugar over a Main method in some type, and now we solidify that view by adjusting the sugar to this: ```cs using System; Console.WriteLine(GetInt()); static int GetInt() => 1; ``` desugars into: ```cs using System; partial class Program { public static void Main() { Console.WriteLine(GetInt()); static int GetInt() => 1; } } ``` By only specifying `partial class`, the type defaults to `internal` visibility, and we allow any other `partial` parts to be explicit about the visibility of the type and change it however they choose. We will also change the signature of main based on the content, as we already do today with `args` and `await`. #### Conclusion We will adopt the above desugaring for top-level statements. This will introduce a break for any programs that have a non-partial `Program` type in the global namespace, or have a partial `Program` type in the global namespace that is either not a `class` or defines a method named `Main` if that program is using top-level statements. ================================================ FILE: meetings/2021/LDM-2021-07-19.md ================================================ # C# Language Design Meeting for July 19th, 2021 ## Agenda 1. [Global using scoping revisited](#global-using-scoping-revisited) ## Quote of the Day - "Any guesses on how many tries it took for me to spell that correctly?" "Well, you misspelled it." ## Discussion ### Global using scoping revisited https://github.com/dotnet/csharplang/issues/3428 The SDK team is currently considering how and where they will generate default global usings for .NET 6, and we want to take another look at our decisions around the [scope of global usings](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-15.md#global-usings). Previously, we decided that global usings should be thought of as being copied and pasted into every file, and that there is no separate outer scope for global usings. However, there are some type names that are relatively common in .NET, such as `Task`, that would likely be part of a default set of SDK global usings (as `using System.Threading.Tasks.Task;` is another way of saying "Use C# 5 features please"). If there is no separate global using scope for these types, however, we'd introduce ambiguities for these types. There are a number of complexities to the separate scope idea that would be equally as painful for users, in potentially subtler ways. In particular, extension method lookup across multiple sets of usings can be tricky, and explaining the intricacies of the lookup rules is complicated, even for language designers talking to other language designers. This would hit Linq hard in particular, as `System.Linq` is on the shortlist of default global usings, but users often add their own versions of the extension methods to customize particular behaviors or performance characteristics for particular scenarios. In these scenarios, users would have to add a manual `using System.Linq;` at the top of their files, which is the same problem as we'd be trying to address by having separate scopes in the first place. We considered a few compromise positions as well, such as considering global usings to be in the same scope for extension method lookup, but different scopes for type lookup. These are complex, both in terms of implementation and in terms of explaining to users, so we don't think this is a good approach. Instead, we think that we have a good tooling story around name clashes already, and we can strengthen the tooling to be aware of global using clashes and introduce global aliases to solve the issue, which is a fairly simple fix for the problem. Finally, we noted that even if we introduced a separate scope for global usings, this wouldn't even fix the issue permanently. The next request would be a way to separate the implicit usings from the SDK, and the user-created global usings. #### Conclusion Existing behavior is upheld. ================================================ FILE: meetings/2021/LDM-2021-07-26.md ================================================ # C# Language Design Meeting for July 26th, 2021 ## Agenda 1. [Lambda conversion to System.Delegate](#lambda-conversion-to-system-delegate) 2. [Direct invocation of lambdas](#direct-invocation-of-lambdas) 3. [Speakable names for top-level statements](#speakable-names-for-top-level-statements) ## Quote of the Day - "You just don't want me to have poor man expression blocks" ## Discussion ### Lambda conversion to System.Delegate https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md We've received a few bug reports from customers on upgrading to a new version of the preview SDK that existing code, targeting .NET 5, was now failing to compile because code that previously chose an extension method with a strongly-typed delegate type was now binding to an instance method that took a `Delegate` instead. While this is a break we intended to make, we wanted to revisit it and make sure that we still believed it was appropriate for these cases. While we are still waiting to hear back from these customers as to what their extension method was actually doing, a survey of code on GitHub shows that the vast majority of these are what we expected: the extension forwards from a strongly-typed delegate to the `Delegate` instance method. Given this, we believe that our original decisions around this feature are still what we were expecting, and that we will keep the behavior for C# 10. However, we are also concerned with not making tooling updates that break our users (beyond bugfixes, such as the ternary type inference issues in C# 9's initial release). This is code that has been compiling successfully for a decade or more, and we don't want to break it when the user has only updated their version of VS, and not actually updated to a new version of .NET (ie, using VS 2022, but still targeting .NET 5 or lower). In order to preserve this, we will bring back our workaround for the 16.10 release, where we changed the binding of this case depending on the current language version. It does mean that there will be a subpar tooling experience for upgrades involving this type of code, but we think missing codefixes are better than code that completely stops compiling. #### Conclusion We will keep the current behavior for C# 10. C# 9 and lower will have a different binding behavior, compiling as they used to. ### Direct invocation of lambdas https://github.com/dotnet/csharplang/issues/4748 Currently, the lambda improvements proposal states that lambdas should be directly invocable. While we think this could fall out for some scenarios, it will not do so for all scenarios. For example: ```cs (() => {})(); // Infer Action, can be invoked (x => x)(1); // Cannot infer a delegate type on its own: would need to take the argument expression into account ``` We don't have a strong scenario for this feature at the moment: our best justification for the feature would be as a poor replacement for expression blocks, and we've received vocal and vociferous feedback from the community on the "poor" part of that phrase. We do think it will be odd that lambdas will be able to be assigned to `var` and then be invoked/used as instance receivers, but cannot be invoked/used as instance receivers directly, but we believe that if we explicitly block this scenario now, we will adequately preserve our design space to remove this restriction later, if we have a better scenario we want to enable. #### Conclusion Lambda and method group natural types will only be allowed to surface in specific contexts: `var`, method type inference, determining the best type of a group of types, and conversion to `System.Delegate`/`System.Linq.Expressions.Expression`. No direct invocation of lambdas or instance member access on lambda expressions or method groups will be allowed. ### Speakable names for top-level statements [Two weeks ago](LDM-2021-07-12.md#speakable-names-for-top-level-statements), we made a decision that top-level statements would be sugar for a partial `Program` type in a `Main` method. However, a [comment on the subsequent discussion](https://github.com/dotnet/csharplang/discussions/4928#discussioncomment-1013469) gave us a separate idea that we had not considered in the meeting, and we wanted to bring up the idea: naming the type that contains the top-level statements after the name of the file the statements are in. There are a few wrinkles to this: what if the file name contains characters that are not legal as C# identifiers? Or what if there is no file name, and the text was passed directly to the compiler not in a file (as is possible using the Roslyn APIs)? We're also concerned by the fragility of this solution: renaming a file could potentially leave dangling partial types and no longer have methods in scope for the top-level statements. Additionally, C# today does not depend on file layout or structure for programs: file names do not have to line up with type names, and namespaces are entirely unconnected from the physical layout of files. This proposal would change that, and we're not sure for the better. However, the idea that we might have a future where we want to combine multiple files, each with top-level statements, into a single program is a distinct possibility. As we move towards the idea of `dotnet run Program.cs`, it's conceivable that a single program might have a few files for different UI stacks (for example, `WebUI.cs`, `ConsoleUI.cs`, and `Desktop.cs`), each of which shares a bunch of common files in the same directory. Users would then choose which to run by just saying `dotnet run Console.cs`, and all 3 of these UI entrypoint files would be combined into a single program, with the entrypoint selected by the file that was run. Given that our specific ask for the speakability feature is being able to get access to the current Assembly (via `typeof(Program).Assembly`), we think that we can protect future design space here by only making `Program` speakable, rather than both `Program` and `Main`. This will allow our future selves a space to design this multi-file experience without worrying about having multiple methods named `Main` in the same type, as we will be able to combine these different partial `Program` types into a single one and name the entrypoints whatever we need to. #### Conclusion We will keep the type name `Program` and make it speakable, but we will not make the `Main` method speakable. ================================================ FILE: meetings/2021/LDM-2021-08-23.md ================================================ # C# Language Design Meeting for August 23rd, 2021 ## Agenda 1. [Nullability differences in partial type base clauses](#nullability-differences-in-partial-type-base-clauses) 2. [Top-level statements default type accessibility](#top-level-statements-default-type-accessibility) 3. [Lambda expression and method group type inference issues](#lambda-expression-and-method-group-type-inference-issues) 1. [Better function member now ambiguous in some cases](#better-function-member-now-ambiguous-in-some-cases) 2. [Conversions from method group to `object`](#conversions-from-method-group-to-object) 4. [Interpolated string betterness in older language versions](#interpolated-string-betterness-in-older-language-versions) ## Quote of the Day - This is the thing that's not weird, so let's handle that ## Discussion ### Nullability differences in partial type base clauses https://github.com/dotnet/csharplang/issues/5107 We looked at inconsistencies and errors when base and interface clauses differ by nullability today. This is particularly problematic for source generators, as generated files are always `#nullable disable`d by default, and if the author just copied the class header over it can cause a compilation error if the original declaration had type parameters and was `#nullable enable`d. The scenarios with base types and interfaces are similar but not identical, as interfaces are allowed to be duplicated in metadata, but base types are not. While the compiler is resilient to importing types with this duplicate metadata, we're not certain that relaxing anything with regards to interface deduplication would be good for the whole ecosystem, as not every tool is written with the same level of error-recovery as Roslyn is. We'll need to look more into the interface scenario before making any more changes. For base types, the proposal suggests that we report warnings when there are differences between nullable and non-nullable type parameters, but not for any other scenario. However, we think that this is a bit too big of an exception. As an example: ```cs #nullable enable public partial class Derived : Base< object, #nullable disable object #nullable enable > {} #nullable enable public partial class Derived : Base< #nullable disable object, #nullable enable object > {} ``` The type parameters only differ by obliviousness in this code, but we'd have to do a merge of all the declarations to get the "true" nullability of all type parameters. We think this is an extremely complex case that gets away from the root of the problem: one declaration is entirely oblivious. Therefore, a narrower, less complex compromise is to simply say that any entirely-oblivious base type declarations are ignored when checking to see if the nullability of base types on all partial parts matches. This carves out the simple case for source generators, and leaves the complex case for manual authors to get correct. #### Conclusion Entirely oblivious base type declarations are ignored when ensuring the nullability of all base type clauses in a partial type match. ### Top-level statements default type accessibility ASP.NET has been testing the changes with [speakable types in top-level statements](https://github.com/dotnet/csharplang/blob/dcbaa815253df779d1ecc206c446c9eb6b059b82/meetings/2021/LDM-2021-07-26.md#speakable-names-for-top-level-statements), and some initial feedback has been that, because the default for types in C# is internal, it requires `InternalsVisibleTo` for all test projects. However, we don't think that this case is important enough to complicate the feature: making it `public` by default is different than anything else in C#, and makes it a much more complex feature to specify, explain, and implement. We also think that the likelihood is that most test projects are going to need IVT to their original at some point, so we wouldn't be saving much in the long run by making `Program` public by default anyway. #### Conclusion Decision from the last meeting stands. ### Lambda expression and method group type inference issues We looked at issues from https://github.com/dotnet/csharplang/issues/5095. The first item we determined to be an implementation bug before the meeting and skipped it. #### Better function member now ambiguous in some cases Another entry in the continuing saga of breaking changes we're discovering from giving lambdas a natural type is a new way in which functions that were previously unambiguous for lambda expressions are now ambiguous: ```cs static void F(object obj, Action callback) { } static void F(int i, Delegate callback) { } // C#9: F(object, Action) // C#10: ambiguous F(0, () => { }); ``` The reason for this is because, in C# 9, the `M(int, Delegate)` overload was not applicable at the call site, and only the `M(object, Action)` was. However, with lambda expressions now having a natural type and being convertible to `Delegate`, we have 2 applicable overloads, with the following conversions: * `M(object, Action)`: implicit boxing numeric conversion from `int` to `object`, identity conversion from `Action` to `Action`. * `M(int, Delegate)`: identity conversion from `int` to `int`, implicit reference conversion from `Action` to `Delegate`. Neither of these sets of conversions is better than the other, so the method is now ambiguous. This was reported from ASP.NET, as they have an unfortunate set of shipped methods that can hit this problem: ```cs static class AppBuilderExtensions { public static IAppBuilder Map(this IAppBuilder app, PathSring path, Action callback) => app; } static class RouteBuilderExtensions { public static IRouteBuilder Map(this IRouteBuilder routes, string path, Delegate callback) => routes; } ``` where both `IAppBuilder` and `IRouteBuilder` are defined on `WebApp` instances. When a user calls `app.Map("sub", (IAppBuilder b) => {})`, this is now ambiguous because each overload has 2 implicit non-identity conversions and 1 identity conversion, and the overloads are ambiguous. We note, however, that these overloads are _already_ ambiguous if, instead of passing a method group or a lambda expression, the user instead passes a variable of type `Action`, with the same ambiguity. There is also a fairly easy workaround for ASP.NET, as they can add a most-specific extension method of `Map(this IAppBuilder, string, Action)`, which will have 2 identity conversions and 1 reference conversion, making it more specific than the other two. We think this is a fine workaround, especially since this API is already ambiguous in many cases, and will not be making any changes here. ##### Conclusion No changes. #### Conversions from method group to `object` Allowing method groups to be convertible to `object` has a potentially deleterious effect on this simple scenario: ```cs static object GetValue() => 0; object o = GetValue; ``` In C# 9, the user would get an error stating that method groups cannot be converted to `object`. In C# 10, this is legal code and will compile. However, we think the likelihood of this being intentional is much lower than the likelihood of it being a mistype. We have a few options for addressing this: - Require an explicit cast to object for such cases. - Let an analyzer detect this case. - Accept it as is. - Have the compiler warn for this case. We think that hard-requiring a cast to `object` is too prescriptive here, as this code is legal and well-formed. However, we also think that either leaving it as is or leaving it to some other analyzer to detect this case is not ideal either. Given that, we think we should warn when a method group is _implicitly_ converted to `object`. Suppressing the warning via an explicit cast or via a standard warning suppression mechanism should be enough to ensure that there is clarity for these cases. ##### Conclusion The compiler will warn on implicit method group conversions to `object`. ### Interpolated string betterness in older language versions https://github.com/dotnet/roslyn/issues/55345 Finally today, we looked at a bug reported by a user when upgrading the compiler and target framework, but setting `LangVersion` back to 9 or earlier. This causes an error for common calls such as `stringBuilder.Append($"")`, as they now pick the interpolated string handler overload instead of the string overload, which then causes a language version error. We solved this similarly to how we dealt with lambda expressions, where we made the better conversion code conditional on whether the language version supported interpolated string handlers. LDM had no issues with this decision. #### Conclusion Decision upheld. ================================================ FILE: meetings/2021/LDM-2021-08-25.md ================================================ # C# Language Design Meeting for August 25th, 2021 ## Agenda 1. [Interpolated string handler user-defined conversion recommendations](#interpolated-string-handler-user-defined-conversion-recommendations) 2. [Interpolated string handler additive expressions](#interpolated-string-handler-additive-expressions) ## Quote of the Day - "Let's have a bio break until next Monday" [LDM proceeds to end an hour early] ## Discussion ### Interpolated string handler user-defined conversion recommendations https://github.com/dotnet/csharplang/issues/5077 We think the scenarios such a recommendation would serve are extremely small, as we don't expect the vast majority of users to use handler types directly. Instead, they will create interpolated string literals and the compiler will generate the handlers for the user. Additionally, while there's often not much protection, we're concerned that some cases without `string` overloads for methods would cause forgetting a `$` to have unintended behavior, particularly when combined with other operators such as `+`. #### Conclusion Recommendation rejected. We will not make strong statements on whether users should define implicit conversions from `string`. ### Interpolated string handler additive expressions https://github.com/dotnet/csharplang/issues/5106 The root of this question is how we apply order of operations and the associative and commutative properties to parenthesized interpolated string addition expressions. For example, given the following code: `$"...." + ($"...." + $"....")`, order of operations would suggest that we evaluate the components of part 1, then part 2, then part 3. Then we combine parts 2 and 3, and finally combine the results of 1 with the results of 2+3. Interpolated string handlers can't operate in this "combine the latter bits then prepend the former" mode. However, by the same token the side effects of this combination are mostly unobservable, and we're very leary of having parentheses be ok in some scenarios but not in others. This means that we have 2 options: 1. Make parentheses always break the magic. A parenthesized interpolated string is just a string. 2. Make parentheses not matter. An expression composed only of additive expressions, interpolated string expressions, and parenthesized expressions counts as single interpolated string for the purposes of handler conversions. Option 1 forces us to try and answer awkward questions such as "What does `CustomHandler c = ($"" + $"");` mean?", and we don't like how it makes parentheses less transparent than they are currently. Therefore, we'll go with option 2. As part of this, we'll also allow nullable suppression operators to be interspersed, as they are similarly "transparent" and shouldn't have any impact on the final code. #### Conclusion Allow parentheses and nullable suppression operators anywhere in the interpolated string additive expression chain. ================================================ FILE: meetings/2021/LDM-2021-08-30.md ================================================ # C# Language Design Meeting for August 30th, 2021 ## Agenda 1. C# 11 Initial Triage 1. [Generic attributes](#generic-attributes) 2. [`field` keyword](#field-keyword) 3. [List patterns](#list-patterns) 4. [Static abstracts in interfaces](#static-abstracts-in-interfaces) 5. [Declarations under `or` patterns](#declarations-under-or-patterns) 6. [Records and initialization](#records-and-initialization) 7. [Discriminated unions](#discriminated-unions) 8. [Params `Span`](#params-spant) 9. [Statements as expressions](#statements-as-expressions) 10. [Expression trees](#expression-trees) 11. [Type system extensions](#type-system-extensions) ## Quote(s) of the Day - "Any proposals for a prioritization strategy?" - "I'm not going to run a ranked choice algorithm in real time" - "By small, I mean large" - "Even Midori wasn't crazy enough to put ref and out into the type system" "Maybe that's what went wrong" - "If you're using ref fields for something other than performance, come talk to me and we can find you help" ## Discussion Today, we start our _initial_ triaging passes for C# 11. It's important to note that, while we talked about a number of potential features for this cycle, it's extremely unlikely we'll be able to get to all of them. These triage sessions are a good way to see what the LDM is currently thinking about, but please don't try to speculate about what's in or out of C# 11 from them. We don't even know that yet. ### Generic Attributes https://github.com/dotnet/csharplang/issues/124 We're shipping this in preview in C# 10 because we found a number of late-breaking incompatibilities with other tools. C++/CLI will straight-up crash if it encounters an assembly with a generic attribute, even if the type with the attribute is entirely unused, and they're not the only tool with issues. We'll need to work with them and others to make sure the ecosystem won't crash around generic attributes before we can remove the preview flag. ### `field` keyword https://github.com/dotnet/csharplang/issues/140 Didn't quite have time for this in 10. Let's get it done! ### List patterns https://github.com/dotnet/csharplang/issues/3435 We have a syntactic design and semantic design for arrays and indexable types, but we will need to some more work for `IEnumerable` support. We hope to have an initial preview soon into the C# 11 development cycle to help get user feedback on the design choices we've made so far. ### Static abstracts in interfaces https://github.com/dotnet/csharplang/issues/4436 We'll need to look through the feedback consumers give us on the initial preview. We already know that CRTP (ie, `INumeric where T : INumeric`) might be better served with `this` type constraint, and if we do want to have such a constraint we'll want it now so that the generic math interfaces can use it where appropriate. ### Declarations under `or` patterns https://github.com/dotnet/csharplang/issues/4018 This is one of our more common pattern complaints. Let's try and get this done. ### Records and initialization We have a number of topics around both records and initialization: 1. Required properties 2. Final initializers 3. Factories 4. Primary constructors 5. Public init fields 6. Immutable collection initializers 7. Combined object and collection initializers 8. Nested properties in with-exprs 9. Event hookup in object initializers We'll look at triaging this list in the next meeting. ### Discriminated unions https://github.com/dotnet/csharplang/issues/113 There is a ton of interest in this, both within the LDM and in the wider community. At the same time, this is a potentially broad topic, broader than records, and we have not yet done the work to fully explore the space and break things into individual parts that can combine into a bigger whole like we did with records. We'll need to start that now if we ever want to ship something in this space (and we do). ### Params `Span` https://github.com/dotnet/csharplang/issues/1757 This was initially going to be part of the improved interpolated strings proposal, but that feature grew and pushed this part out of scope. We'll need to work together with the runtime on this to make sure that we're stackalloc'ing where possible but not unnecessarily blowing out stacks. It might also call for being able to stackalloc an array of reference types. ### Statements as expressions https://github.com/dotnet/csharplang/issues/3086 We see potential for smaller features here that we can ship one at a time, leaving design priority for other features. ### Expression trees https://github.com/dotnet/csharplang/discussions/4727 Historically, our inability to improve this space has been because of concern about breaking our customers. However, recent discussions between EF and members of the LDT have us encouraged, and we plan to look at this. ### Type system extensions There are a number of type system improvements we could look at, from large changes like roles and extension everything to smaller things such as unifying our story around `Task` and `Task`/`Action` and `Func`/etc. This last thing would require some kind of unit-type, and `void` is the obvious choice as it's already the return type of nothing-returning methods. Maybe we could work with the BCL to unify these types and make them transparent? We could also look at generic improvements around other restricted types, such as pointers and ref structs. ================================================ FILE: meetings/2021/LDM-2021-09-01.md ================================================ # C# Language Design Meeting for September 1st, 2021 ## Agenda 1. [Lambda expression conversions to `Delegate`](#lambda-expression-conversions-to-delegate) 2. [C# 11 Initialization Triage](#c-11-initialization-triage) 1. [Required properties](#required-properties) 2. [Primary constructors](#primary-constructors) 3. [Immutable collection initializers](#immutable-collection-initializers) ## Quote(s) of the Day - Starting the meeting with 11 minutes of technical issues - "In my next language, no users" - "Has anyone made that companion book? Javascript, The Terrible Bits" - "Do we actually want to do this feature because customers are asking for it, or do we just think it's interesting as an LDM?" ## Discussion ### Lambda expression conversions to `Delegate` https://github.com/dotnet/csharplang/issues/5124 Today, we do not allow lambdas without a natural type to be convertible to `Delegate` or `Expression`. This can lead to niche but possible cases where _adding_ type information to a lambda actually makes a method call ambiguous, where the typeless lambda was unambiguous. These cases, however, are both niche and usually fixable by adding a new, most-specific overload, as we discussed in the notes [last week](LDM-2021-08-23.md#better-function-member-now-ambiguous-in-some-cases). Making a change here would also break APIs where the user has an instance method that takes a `Delegate` (often not defined in their code) and adds extension methods with strongly-typed delegates to add the necessary information for the compiler to bind their lambdas. This is a fairly common pattern when working with a `Delegate` API, and despite making the language more complex and having weird consequences for niche cases, we think that keeping the existing rule as it is the appropriate compromise to keep existing code working. While we don't think that we would have this complex of a rule if we were redesigning the language, we also think that we've hit the limit of breaking changes we'd want to take in this feature. #### Conclusion We'll keep the conversion rules as they are. ### C# 11 Initialization Triage Continuing from [last meeting](LDM-2021-08-30.md), we're triaging potential C# 11 features, this time specifically around initialization and records. The same disclaimer from last time applies: this is _early_ triage. Please do not try to infer what will be in C# 11 or not from these notes. If we don't know the answer to that, these notes won't help you find it either 🙂. The general list of initialization and record related topics is as follows: 1. Required properties 2. Final initializers 3. Factories 4. Primary constructors 5. Public init fields 6. Immutable collection initializers 7. Combined object and collection initializers 8. Nested properties in with-exprs 9. Event hookup in object initializers Of these, we can subdivide them into a few sets of categories: * Truly new features that enable a new type of expressiveness that doesn't exist today. This is 1, 2, 3, and 6. * Features that build off of others, either in this list or already in the language. This is 4 and 5. * Features that solve pain points in existing features. This is 7, 8, and 9. Of these, there are a few that stand out as being features we're more interested in: required properties, primary constructors, and immutable collection initializers. #### Required properties https://github.com/dotnet/csharplang/issues/3630 We did a big design push on this a little under a year ago. We should revive the proposal, make the changes we talked about at the end of the last design review, and see what we think about it now. #### Primary constructors https://github.com/dotnet/csharplang/issues/2691 We've had a few designs for primary constructors over the years. C# 6 nearly shipped with one design, then we shipped records with a design, and now we need to think about how primary constructors will interact with record/class cross inheritance when we get to that. We really need to come back with a new proposal that looks at the past versions, and that may well be next year. #### Immutable collection initializers This is an idea we've been ruminating on as we designed the syntax for list patterns: we've given up on trying to make the correspondence principle work with the existing collection initializer syntax, but we could potentially make the principle work by design a new collection literal syntax, one that will work with immutable types as the current one does not. A smaller group will look at this and make a proposal for the space. ================================================ FILE: meetings/2021/LDM-2021-09-13.md ================================================ # C# Language Design Meeting for September 13th, 2021 ## Agenda 1. [Feedback on static abstracts in interfaces](#feedback-on-static-abstracts-in-interfaces) ## Quote(s) of the Day - "Computer math never equals real math, it never does" - "If we add default interface methods to LINQ, we get dim sum." ## Discussion https://github.com/dotnet/csharplang/issues/4436 Today, we looked at some of the initial feedback we've gotten on both static abstracts in interfaces and generic math from our blog posts and on our design issues. Some of the channels we looked at: * https://github.com/dotnet/csharplang/issues/4436 * https://github.com/dotnet/designs/pull/205 * https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/ * https://www.reddit.com/r/csharp/comments/p20xap/preview_features_in_net_6_generic_math/ * https://www.reddit.com/r/dotnet/comments/p20jlc/preview_features_in_net_6_generic_math/ * https://www.reddit.com/r/programming/comments/p20j22/preview_features_in_net_6_generic_math/ * https://twitter.com/tannergooding/status/1425223277941719040?s=20 * https://twitter.com/dotnet/status/1428127070530514944?s=20 We've also had discussion on our various other platforms not so easily directly linked, such as the C# community discord (https://discord.gg/csharp), email, and other similar informal chats. Overall, we're pleased to see both the number of people looking at the feature, and that their reactions to the feature are overwhelmingly positive. We also received a good deal of concrete feedback we can examine to see what, if anything, we should change or improve about the feature before shipping for real with .NET 7. ### Keyword confusion Some users didn't immediately connect the idea of `static` and `abstract`, and suggested that we might want to consider a new keyword specifically for this concept. However, we think this is another case of initial reactions wanting new features to stand out. This is particularly exacerbated because `abstract` is not used in interfaces today, but is required here. Despite that, we intend to keep the keywords the way we have them: even if we introduced something special for interfaces, we'd want to use `static abstract` in classes when we support defining such methods there. ### Traits/Shapes/Roles Some of the feedback has been about asking us to go further, into traits/shapes/roles/type expansion du jour. This is only natural: static abstracts represents a major new expressive ability in C#, and these types of expansions are the logical next step. While the feature we're currently working is not going to address those requests, we by no means are done in this space, and will continue to explore more enhancements we can make in this space. ### Missing types We didn't get to implementing the generic math interfaces on all types that would benefit from them in .NET 6, such as `BigInteger` or `Vector2/3/4`. We plan to expand our implementations on these interfaces with .NET 7 to cover more types. ### Direct parsing support in INumber Some users have expressed a sentiment that proper mathematical numbers don't necessarily have serialization/deserialization as a concept, and thus asked for INumber to not include those concepts in the extended interface set. This will be a question for the runtime API reviewers to think on. ### Self types Of the biggest pieces of feedback we've gotten is around confusing on type constraints. In particular, users expect calls like `IParsable.Parse("123", null)` to work, but because of the way we've implemented the type constraints currently, the language cannot understand that `double` is the type that `Parse` needs to be constrained to. We can solve issues like this by implementing a self type, which will also just be a generally-useful feature for the language. We're positive about the feature, but it is going to need a decent amount of design work. In particular, there are interfaces such as `IEquatable` that have been in C# since C# 2.0, and we need to think about if we want to be able to apply such a constraint to those interfaces, how that would work, and what level of breaking change it would be. There is also syntax to be worked out, which makes for everyone's favorite LDMs. ### DIMs for static abstracts We consider the ability to have a DIM for static virtual members a must-have for v1, as we'll need it for versioning, and potentially some of the initial implementation will want to be a DIM instead of being required to be implemented by users. ### `static abstract`s in classes The ability to declare `static abstract` members in classes will almost certainly be in the runtime for .NET 7. However, we don't consider it as important of a ship-blocker as the DIM feature, so it might have less priority than other C# 11 features. ### Checked operators https://github.com/dotnet/csharplang/issues/4665 We think this operator is likely important for semantic correctness in generic math. It's a niche case, and we may want to use the afformentioned DIMs to forward the checked implementation to unchecked, but that will be a decision for the runtime API design team. ### Relaxing shift operator requirements. https://github.com/dotnet/csharplang/issues/4666 Our thoughts on this proposal have not changed since the [last time](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-19.md#relaxing-shift-operator-requirements) we looked at it. ================================================ FILE: meetings/2021/LDM-2021-09-15.md ================================================ # C# Language Design Meeting for September 15th, 2021 ## Agenda * [Feedback from the C# standardization committee](#feedback-from-the-c-standardization-committee) * [Permit pattern variables under disjunctive patterns](#permit-pattern-variables-under-disjunctive-patterns) ## Quote of the Day - "We've so far decided not to decide" ## Discussion ### Feedback from the C# standardization committee https://github.com/dotnet/csharpstandard/issues/366 For the first half of today's LDM, we had a guest from the C# standardization committee on to talk about the ongoing process by TC-49 to produce an update to the ECMA C# specification, the latest version of which covers C# 5. In the csharplang repository, we currently have an initial conversion of an internal Microsoft C# specification of C# 3, which was updated to cover some aspects of C# 6. The ECMA team has converted the C# 5 specification to markdown, and is currently working to integrate the changes from our C# 6 spec into that version. These dual versions of the specification can confuse users. The language reference on Microsoft Docs comes from the csharplang version of the spec, but the TC-49 version of this specification has had a number of bug fixes and is a better markdown conversion overall. Because of this the csharplang version of the spec gets occasional pull requests to update various things, from English spelling and grammar issues to actual spec bugs, but many times those issues have already been fixed in the TC-49 version of the specification. To address this, we plan to remove the csharplang version of the specification when the last PRs for C# 6 are merged into the csharpstandard repo, which should hopefully be soon. Additionally, the standardization committee has draft PRs out for most C# 7 features, and is working on C# 8 specifications as well. Currently, when they run into questions they've been emailing a few specific compiler team members, who hopefully can either answer their questions or forward to the right team member. To facilitate better interactions, we've created an internal alias with the compiler team, the language design team, and the TC-49 members currently working on the draft specifications. The specification changes for C# 8 are big, particularly with nullable reference types, and will require close collaboration between these groups to make sure the spec actually reflects the feature that was implemented by the compiler team. Finally, we did some thinking about how to collaborate moving forward on new language features. We'd like to think about maintaining proposals as branches on the TC-49 specification, to make sure that we are considering the real specification when we think about new language features and ensuring that we can see other proposed changes to the specification as a whole when making new features, as opposed to having to remember what not-yet-specified proposal from a previous language version modified a particular section of the specification while designing a new change to that section. ### Permit pattern variables under disjunctive patterns https://github.com/dotnet/csharplang/issues/4018 We took a first pass over this proposal in the second half of the LDM. At first brush, there are a couple of major points of contention: 1. Should we allow redeclaration, or should we have some form of `into` pattern that would allow the reuse of an existing variable in a pattern? 2. Is variable declaration across multiple expressions ok, or should it only be permissible within a single pattern? Point 1 arises from potential confusion around the double declaration: will users find the multiple declarations intuitive, or will they wonder about "which" of the pattern variables future usages refer to? We've also had requests for an `into` pattern in the past, that would allow a pattern to assign into an existing variable. We're concerned about a generalized version of this pattern because it could have unpredictable effects, particularly when combined with `when` clauses, but a more specialized version that can only use variables declared in the same pattern could be a usable version of this proposal. We also want to think about how this would interact with any potential pattern matching over types themselves in the future, such as extracting the `T` from an `IEnumerable`: if such a thing could be done, it should be doable under an `or` as well, and we will want to have similar syntax forms for redeclaration as here. We also need consider how an `into` pattern or other form of assignment syntax would interact if we ever want to permit non-constants to be used as a pattern themselves. For point 2, we're concerned about the potential large impact throughout the language. This would be very similar to permitting a generalized `into` pattern, where `when` clauses can cause otherwise-matching patterns to not be executed and reassign existing variables. Several members of the LDM feel that we should tackle just within a single expression first, and consider multiple expressions at a later time with more examples of the types of code it would enable. #### Conclusions No conclusions today. We want to see the specification PR updated with more motivating samples, including samples from real code, before we make any conclusions on these issues. ================================================ FILE: meetings/2021/LDM-2021-09-20.md ================================================ # C# Language Design Meeting for September 20th, 2021 ## Agenda 1. [Lambda breaking changes](#lambda-breaking-changes) 2. [Newlines in non-verbatim interpolated strings](#newlines-in-non-verbatim-interpolated-strings) 3. [Object initializer event hookup](#object-initializer-event-hookup) 4. [Type alias improvements](#type-alias-improvements) ## Quote of the Day - "You're kicking in an open door" ## Discussion ### Lambda breaking changes https://github.com/dotnet/roslyn/pull/56341 In the ever continuing saga of new breaking changes introduced by giving method groups and lambda expressions natural types, we looked at a few new breaking changes today to decide what, if any, workarounds we should adopt to try and fix them. #### Breaking changes around overload resolution https://github.com/dotnet/roslyn/issues/55691 https://github.com/dotnet/roslyn/issues/56167 https://github.com/dotnet/roslyn/issues/56319 https://github.com/dotnet/csharplang/discussions/5157 We have a number of reports from users who have been broken by changes in overload resolution, mostly because a set of overloads that used to succeed in overload resolution are now ambiguous. A smaller group met to discuss a number of different potential solutions to the issue. These options were: 1. Leave the breaking changes as-is. 2. Change “method type inference” and “best common type” to not infer from the natural type of a lambda expression or method group. 3. Change “better function member” to treat delegate types with identical signatures as equivalent, allowing tie-breaking rules to apply. 4. Change “better function member” to prefer overloads where method type inference did not infer type arguments from the natural types of lambdas or method groups. 5. Change “better function member” to prefer parameter types that are delegate types other than those used for natural type. (Prefer `D` over `Action`.) 6. Change “better function member” to prefer argument conversions other than “function type” conversions. 7. Change “better function member” to prefer parameter types `D` or `Expression` over `Delegate` or `Expression`, where `D` is a delegate type. Discussion further narrowed our focus to two combinations of the above options: 3+7 or 4+6. 3+7 results in a more aggressive break, while 4+6 is more compatible with previous versions of C#. Given the extent of some of the breaks we're seeing, we think the more compatible approach is the better way to go, so we'll proceed with the PR linked at the start of this section. ##### Conclusion Options 4+6 accepted. #### Method groups converting to `Expression` Another break testing has revealed looks like this: ```cs var c = new C(); c.M(F); // C#9: E.M(); C#10: error CS0428: Cannot convert method group 'F' to 'Expression'. static int F() => 0; class C { public void M(Expression e) { Console.WriteLine("C.M"); } } static class E { public static void M(this object o, Func a) { Console.WriteLine("E.M"); } } ``` We think we would have a solution for this: split our "function type conversion" into two separate conversion types: a function type conversion from lambda, and a function type conversion from method group. Only the former would have a conversion to Expression. This would make it so that `M(Expression)` is not applicable if the user passed a method group, leaving only `M(object, Func)`. This could be a bit complex, but it should resolve the issue. Unlike the previous examples, however, we don't have any reports of this issue. Given the number of reports of the previous breakages we've received, and the lack of reports for this issue, we tentatively think that it's not worth fixing currently. If, after we ship C# 10 for real, we received reports of this break, we know how to fix it and can change course at that time without making a breaking change. ##### Conclusion No changes will be made. #### Lambdas in OHI A final break we looked at today is: ```cs using System; B.F1(() => 1); // C#9: A.F1(); C#10: B.F1() var b = new B(); b.F2(() => 2); // C#9: A.F2(); C#10: B.F2() class A { public static void F1(Func f) { } public void F2(Func f) { } } class B : A { public static void F1(Delegate d) { } public void F2(Delegate d) { } } ``` This is standard OHI behavior in C#, but because the derived overloads were previously not applicable, they were not included in the `B.F1` or `b.F2` method groups, and only the methods from `A` would be applicable. Now that methods from the more derived type are applicable, methods from the base type are filtered out by method group resolution. We think this is both fine and actually desirable behavior. We don't have contravariant parameters in C#, but this is effectively acting like such, which is a good thing. This change is also not customer-reported, but was instead discovered in testing. Given the desirable behavior and lack of reports, we think no change is necessary. ##### Conclusion No changes. ### Newlines in non-verbatim interpolated strings https://github.com/dotnet/csharplang/issues/4935 We have a lot of compiler complexity around ensuring interpolated strings do not have a newline in them, and we don't see a real reason to forbid newlines. We think the origin might have come from the number of different design flip-flops we made on interpolated strings during their initial design. #### Conclusion Language change approved. ### Object initializer event hookup https://github.com/dotnet/csharplang/issues/5176 LDM is not only interested in this change, we're also interested in generalized improvements that can be made in object initializers and with expressions. Compound assignment is interesting, particularly in `with` expressions, and we would like to see what improvements we could make not just for events, but for all types of properties and fields. #### Conclusion Approved. We want to explore even more enhancements in this space. ### Type alias improvements https://github.com/dotnet/csharplang/issues/4284 Finally today, we looked at one of the open questions in this proposal: how should we handle nullable types in using aliases when the alias is used in a `#nullable disable` location. There are largely 2 ways to view using aliases: 1. Syntactic substitutions: the compiler is literally copy/pasting the thing in the alias into the target location. In this view, the compiler should treat the syntax as occuring at the use point, and warn based on that. 2. Semantic substitutions: the using alias is effectively defining a new type. It's not a truly different type, but only the meaning is substituted, not the actual syntax. If we ever want to consider a way to export using aliases, this will be a useful meaning to assume. We also have some (possibly unintended) prior art here: `using MyList = System.Collections.Generic.List;` takes the second approach today, acting like a semantic substitution. The one thing we still want to consider in this space is top-level nullability. We're not sure about allowing a type alias to have top-level nullability when it's an alias to a reference type. There is (very intentionally) no extra C# syntax for "not null reference type" beyond the lack of a `?`, and the next ask if we were to allow aliases to be top-level nullable would be for such a syntax. #### Conclusion Overall, we like the semantic meaning. We still need to consider whether aliases should be allowed to have top-level nullability. ================================================ FILE: meetings/2021/LDM-2021-09-22.md ================================================ # C# Language Design Meeting for September 22nd, 2021 ## Agenda 1. [Open questions in list patterns](#open-questions-in-list-patterns) 1. [Breaking change confirmation](#breaking-change-confirmation) 2. [Positional patterns on ITuple](#positional-patterns-on-ITuple) 3. [Slicing rules](#slicing-rules) 4. [Slice syntax recommendations](#slice-syntax-recommendations) 5. [Other list pattern features](#other-list-pattern-features) 2. [Nested members in `with` and object creation](#nested-members-in-with-and-object-creation) 3. [CallerIdentityAttribute](#calleridentityattribute) 4. [Attributes on `Main` for top level programs](#attributes-on-main-for-top-level-programs) ## Quote of the Day - "That's not just our normal passive aggressive shtick, it's just actively aggressive." ## Discussion ### Open questions in list patterns https://github.com/dotnet/csharplang/issues/3435 https://github.com/dotnet/csharplang/issues/5201 #### Breaking change confirmation First, we looked at the breaking change for `Length`/`Count` on indexable and countable types. We're a bit concerned about making this case a generalized error, even when a list pattern isn't being used: there are potential use cases for a `Length` that returns negative, such as some object modeling a physics concept. We think we'd like to try and downgrade this to a warning instead of an error, for the specific case where a negative length is tested and the location wasn't already subsumed. Some examples: ```cs x is { Length: -1 } // Warning, not subsumbed by any previous case x is { Length: >=0 } // Considered Exhaustive x is { Length: <5 or -1 } // Error on the -1, subsumed by <5. No warning on the <5. ``` ##### Conclusion Try to make it a warning for non-subsumed cases. #### Positional patterns on ITuple Currently, we've implemented subsumption rules that allows list patterns and `ITuple` positional patterns to understand they refer to the same alias. However, we think that this results in weird interactions with regular `ValueTuple` instances. `ValueTuple`s explicit implement `ITuple`'s properties, so when pattern matching on one they would not be considered indexable or countable unless boxed into `ITuple`. This would mean that, unless we were to make `ValueTuple` special in list patterns, there wouldn't be the same correspondence as `ITuple` has. We also don't think that, in general, `Deconstruct` implies an order for an indexer: other than for `ITuple`, these seem to be unrelated concepts. ##### Conclusion There will be no correspondence between positional patterns and list patterns, even for ITuple. If someone comes along with a killer scenario for ITuple later, we can readd this. #### Slicing rules These rules, as stated, matches our previous discussions on the topic. ##### Conclusion No changes. #### Slice syntax recommendations We have two general choices for the slice pattern: ```cs e is [ first, .. var rest ] // Space e is [ first, ..var rest ] // No space ``` We feel that the code reads oddly without the leading space because the pattern being applied itself has a space. It introduces an odd feeling of `(..var) rest`, even though the code is really `.. (var rest)`. ##### Conclusion We're in favor of having a space. #### Other list pattern features For `IEnumerable` patterns, we'd like to introduce them at the same time as the rest of the list pattern feature, even if in a more limited form, to ensure we avoid any potential breaking changes. They may go to preview at different times, and we may need to section off some of the pattern features to ensure we can ship, but we'd like to at least have an initial version. We also do want to make sure that we're generating as good code for this as possible, which may overlap some with params Span work to see whether we can stackalloc. For indexer patterns, we are interested, but they don't need to be this version of C#. We do think that we'll want them in the near future though: we have lists, we should also be able to have dictionaries. For length patterns, we're not seeing the need. We explored them previously and the exploration seems to have concluded that they might be needed for IEnumerable, but if so we can revisit as we look at IEnumerable again. ### Nested members in `with` and object creation https://github.com/dotnet/csharplang/issues/4587 We like the concept of reducing the boilerplate for nested with patterns. However, we're not a fan of the strawman syntax. Multiple members of the LDM misinterpreted it as modifying the nested instance, rather than doing a `with` on the nested property. It would also fall over if multiple properties on a nested instance need to be set. We have a couple of initial ideas: ```cs methodCallExpression with { Method with { Name = "MethodName", BaseType = "C" } } methodCallExpression with { Method = { Name = "MethodName", BaseType = "C" } } // more similar to nested object initializer? ``` #### Conclusion Into the working set, with some syntax modifications. ### CallerIdentityAttribute https://github.com/dotnet/csharplang/issues/4984 Mostly a convenience feature for ASP.NET which will help them structure code in a way more conducive to AOT. It seems in line with our existing caller info attributes, so we'll work with the BCL and ASP.NET teams to prioritize the issue. #### Conclusion Into the working set. ### Attributes on `Main` for top level programs https://github.com/dotnet/csharplang/issues/5045 We do think there needs to be some design work: the feature proposes allowing the `main` modifier in any file. We need to decide whether that's a use case we want to support, and if we do, whether we should allow the target with regular entry points or only with top level statements. If we allowed it with any entry point, we think there are some decent engineering challenges with the way the language is currently structured that would make it more complex than first blush might indicate. Some form of this feature seems reasonable though, so we'll work with the BCL to determine the right scope of the feature. #### Conclusion Into the working set. ================================================ FILE: meetings/2021/LDM-2021-10-13.md ================================================ # C# Language Design Meeting for October 13th, 2021 ## Agenda 1. [Revisiting DoesNotReturn](#revisiting-doesnotreturn) 2. [Warning on lowercase type names](#warning-on-lowercase-type-names) 3. [Length pattern backcompat](#length-pattern-backcompat) ## Quote of the Day - "You're opinionated against opinionated features" "I see the irony, but..." ## Discussion ### Revisiting DoesNotReturn https://github.com/dotnet/csharplang/issues/5231 We wanted to revisit this design decision after a few years and some reports of customer confusion around this attribute. While it's not an overwhelming amount of confusion, it's non-zero. When last this attribute was [discussed](../2019/LDM-2019-07-10.md#doesnotreturn), we decided that we couldn't use it to broadly enforce the concept of a method that does not return: instead, it could only influence nullable analysis. Much like the rest of the features nullable analysis added, it cannot make strong guarantees. Changing this behavior now wouldn't really solve any of the issues with it, as code exists today that is attributed with `DoesNotReturn` that cannot be statically-proven to never return. Instead, we think that https://github.com/dotnet/csharplang/issues/538 would be needed to make progress here: once we have a `never` type, these guarantees can be statically proven, the runtime can abort if an instance of `never` is ever created, and we can introduce a warning wave at that point to suggest that methods attributed with `DoesNotReturn` should have a return type of `never`. This would prevent us from introducing another loose generalized analysis that can't be statically relied upon before later adding a more strict version that can be relied upon. #### Conclusion No changes. ### Warning on lowercase type names https://github.com/dotnet/roslyn/issues/56653 This is an issue that we've been kicking around for a long time to try and protect language design space, and we've recently started to make more breaks in this space. For example, C# 9 introduced `record` as a type specifier, and made it illegal to declare a type named `record` without escaping it. We've heard no complaints about this change, and this has given us more confidence to go ahead with a warning in this area. And important point is that this change is _not_ motivated by style. C# as a language tries hard not to be opinionated on coding styles; we have defaults for formatting that are part of VS and `dotnet new editorconfig`, but these are highly customizable and we try not to punish users for taking advantage of that customizability. Instead, this warning is about trying to ensure that C# has design space to work with that doesn't break users who upgrade to new C# versions. This goal means we're trying to help 2 sets of customers: 1. People who author types with lowercase names. These authors should be informed that they could be causing issues for their users by using a lowercase name. 2. People who use types with lowercase names. These users should be informed that, if a future version of C# adds the type they're using as a keyword, their code could change meaning or no longer compile. Some other points of consideration we brought up: * Should we do this for just public types? * Answer: we are trying to protect users from both themselves and others. This means we want to warn everywhere. * Should we have codefixers to prepend `@` in front of identifiers? * Ultimately this will be up to the IDE team, but we think that each set of customers above should have a different answer here. For set 1, we don't want to encourage this type of naming, even when escaped, so we would not want to see a fixer on type definitions. However, for set 2, they're just using a type someone else defined. We don't want to unreasonably punish them, so a fixer to prepend `@` for these users seems appropriate. #### Conclusion The compiler will issue a diagnostic for types named all lowercase letters. We may simplify that to be just types named all ASCII lowercase characters, to avoid worrying about lowercase characters in other encodings and because we don't believe C# will be interested in adding non-ASCII keywords. ### Length pattern backcompat https://github.com/dotnet/csharplang/issues/5226 There are user tradeoffs here. In our previous discussion, we expressed a desire to have a similar ability to nullable, whereby a missing negative Length test would not count against exhaustive matching, but if one was present it would then cause negative values to need to be fully matched against. However, describing these rules and implementing them is quite complex, to the point that we are concerned both about the code complexity and the explanation of the rules to users. We therefore think that the simpler implementation, where we just consider the domain of `Count`/`Length` for indexable and countable types to be non-negative integers, to be the better approach. While we were discussing this, we also brought up the inherent flaw of structural typing, where types that do not satisfy the contract of indexable and countable but still have the correct shape end up having incorrect behavior. For example, the BCL recently approved a `Vector2` type. That type has an indexer, and it has a property called `Length` that returns an integer. This `Length` property is not the length of the Vector: that's always 2. Instead, this is the euclidean length of the vector, ie the distance from (0, 0). Our knowledge of `Length` and combining subsumption with list patterns will behave incorrectly here. However, we note that this type is _already_ broken with structural typing in C# today. For example, `vector[^1]` would work on the type, but it wouldn't have the expected behavior. Instead of trying to solve something here, we instead think we should see what other uses customers have for opting out of structural typing, and started a discussion about that [here](https://github.com/dotnet/csharplang/discussions/5278). #### Conclusion We will accept the existing breaking change on subsumption for indexable and countable types, and treat Length/Count on types that are both indexable and countable as if they can _only_ have non-negative values. We would like to get this change in preview sooner rather than later so that we can receive feedback on the change. ================================================ FILE: meetings/2021/LDM-2021-10-20.md ================================================ # C# Language Design Meeting for October 20th, 2021 ## Agenda 1. [Open questions in list patterns](#open-questions-in-list-patterns) 1. [Types that define both Length and Count](#types-that-define-both-length-and-count) 2. [Slices that return null](#slices-that-return-null) 2. [Primary constructors](#primary-constructors) ## Quote of the Day - "No fingerprints today, I always struggle with it after a bank robbery, when I file them down" ## Discussion ### Open questions in list patterns https://github.com/dotnet/csharplang/issues/5137 We had 3 open questions from tests on list patterns. Questions 1 and 2 both relate to types with both `Length` and `Count` properties, so our decision applies to both. #### Types that define both Length and Count Our previous definition of a type that can be matched by a list pattern is that the type must be both indexable and countable. Because countable could be one of two properties, `Length` or `Count`, we need to decide what do when a type has both. Our options are: 1. Treat them as equivalent and assume they return the same value. This will affect subsumption. 2. Treat the non-recognized one (`Count` in the case where both `Length` and `Count` are defined) as not being special at all. Conceptually, we think it would be odd to assume that `Count` is equal to `Length`, even though we'll never use it for any form of slicing or length checking. While it might be reasonable to assume they return the same value, we don't think it's an important scenario. We'll have nearly a year of preview on this feature, so we have plenty of time to react to feedback if dogfooding shows that it's an important scenario. ##### Conclusion We will not treat `Length` and `Count` as being connected when both are defined. #### Slices that return null We believe that well-defined slice methods should never return `null` for an in-bounds range, and we'll never generate a call to such a slice method with an out-of-bounds range. However, we also don't think there's a customer for this scenario: a search of GitHub showed no `Slice` methods that return an annotated value, and treating a slice method differently than its annotation would require both implementation effort and customer education. Given that there are no known cases this affects, we don't think it's worth it. Similarly to the first question, if such scenarios are identified during preview, we can correct appropriately. ##### Conclusion If a `Slice` method is annotated, we will treat it as potentially returning `null` for the purposes of subsumption and exhaustiveness. ### Primary constructors https://github.com/dotnet/csharplang/issues/2691 Now that records have been out for a year and we've expanded them to struct and reference types, we think it's time to start looking at primary constructors again. We updated the proposal with the new syntax forms from our records work, removing or modifying components where they make sense. Unlike for records, primary constructors aren't about defining the members that make up a grouping of data: instead, they're purely for defining the inputs to a class. So things like deconstruction, equality, and public properties aren't automatically generated from the parameters. Instead, they become fields, that are then eliminted if the field is never used outside of a field initializer. We think there are a couple of open discussion topics: 1. Should the fields be readonly or not? 2. How important are primary constructor bodies for a generalized feature? For question 1, we think that, for better or for worse, C# is a mutable-by-default language. We were able to change the defaults for `record` types because mutability in a value-based reference type is actively harmful, but we have to consider a number of naming and mutability issues we were able to skirt around in record types. Field naming conventions, for example, are heavily split in C#, with some users using a leading `_`, and some preferring to just use pascalCase with no leading modifiers. We think `readonly` is similar: if users would like to modify the defaults of C#, they can define their own field and do so. If this proves to be the wrong default we can make a change before it ships for real, or potentially invest in https://github.com/dotnet/csharplang/issues/188 to allow putting modifiers on parameters. For question 2, we think that it will be important, but that once https://github.com/dotnet/csharplang/issues/2145 is in the language, a good portion of initialization code will be expressable in just a single initialization expression. It will miss some more complex scenarios, but we think just primary constructors are a good first step. In general, we also want to make sure we're defining the difference between primary constructors and required properties well. With the potential for multiple new initialization features to ship in a single language version, we want to make sure they complement each other well. We think required properties work well for public contracts: DTOs, POCOs, and other similar, nominal data structures that will expose these properties externally. Primary constructors, on the other hand, are for other types, that do not define public surface area based on their parameters. Instead, they simply define input parameters, and can assign them to private fields or otherwise manipulate them as they choose. Some concrete feedback on the proposal: * The exception for calling `this` for copy constructors feels premature, as they don't mean anything to any type except record types. If we generalize `with` in the future, then the exception will make sense, and we can add it then. * We should disallow methods with the same name as primary constructor parameters. This is confusing and while it could potentially be understandable code if the parameter was never captured, could then have spooky action at a distance if the parameter was accidentally captured when a user forgot to write the `()` of the method name. #### Conclusion These words are accepted. ================================================ FILE: meetings/2021/LDM-2021-10-25.md ================================================ # C# Language Design Meeting for October 25th, 2021 ## Agenda 1. [Required members](#required-members) 2. [Delegate type argument improvements](#delegate-type-argument-improvements) ## Quote of the Day - "It's officially late, not casually late" ## Discussion ### Required members https://github.com/dotnet/csharplang/issues/3630 It's been nearly a year since we last looked at required members, so we started today by recaping the proposal as it currently [exists](https://github.com/dotnet/csharplang/blob/ec01d26c47d8d3e3e10b1dd0ade96dae6f99934a/proposals/required-members.md). We also took a look at the feedback we received when this was last shown to our design review team (mainly internal partners and former language design members who aren't involved the day-to-day design of the language anymore). The feedback we received at that point was that the proposal, as it currently stands, is too complex. We have a lot of syntax for enabling a set of contract modifications: 1. Users can say `init(Prop)` to signal that the constructor requires all things the type does, except the members in the `init` clause. 2. Users can say `init required` to signal that the constructor does not require any of the members the type does, and the compiler will ensure that the user correctly initializes all members in that constructor. 3. Users can say `init required!` to signal the same 2, except that the compiler will not enforce that the user correctly initializes all members in that constructor. This syntax was quite specialized, as we intended it to be extensible enough that future work around factories would be able to take advantage of the same syntax. However, upon coming back to the syntax, we have several people that changed their opinions on the form; some of the people that didn't originally feel like the syntax "clicked" now like it, and others that originally liked it now think that it doesn't work well. We took a look at the concrete cases that will want to use the above modifications: * Copy constructors will want to opt out of the entire contract, as their job is to initialize all the fields to the original instance's values. * Types might have a grow-up story, where they may originally ship with just some number of required properties, but later may want to add a convenience constructor that sets a few common properties. We think that this case, while not unreasonable, is not the common case for this feature. The first use case wants at least contract modification 3, and possibly 2 as well. It does not need 1. The second case needs contract modification 1 to be expressible in C#. Given this, we think that there's a gradual approach we can take to introducing contract modifications to the language. A potential first approach is just introducing modification 3, without any constructor body validation. A later version of the language can, as part of a warning wave, turn on body validation, and then later allow individual member exceptions as in modification 1. We are a bit split on this decision, however. We think a prototype to play around with that implements this initial decision should help inform the initial feature set we want to ship for this feature. One thing we definitely don't want to do is ship without constructor body validation and without feeling like we can make that the default state later via a warning wave, as that would leave us in a bad position where validation is available, but off by default. We also looked at different syntax forms, namely attributes. We'd considered attributes previously in the design, but steered away from them to see what we could do with language-integrated syntax. We think we've gone as far down that road as we can, but our final design still doesn't feel quite right. Given that, we think that having an attribute to express the contract modifications will serve us well. It can be named more verbosely, should still be able to apply to factory methods, and required no new syntax for users to learn. Finally, we also considered a couple of other questions: * Should we have a new accessor, `req`, that is used for a required property instead of `init`? * We think that this use case is too small, cuts out required setters, and cuts out the ability to mark fields as required. * Should we use a warning or error on construction? Traditionally, "suppressions" correspond to warnings, and the contract modifications can be viewed as suppressing the need to initialize things. * They're not really suppressions though. It's changing the semantics of the code, so we think it's fine (and safer) for us to use errors on construction, not warnings. * We haven't considered how required members will interact with `default(StructType)`. Some thought needs to be put into that. #### Conclusion We will start working on an implementation of the simplified version of this proposal that uses attributes for contract modifications, not specific syntax, and only has contract modification 3. Feedback will then inform whether we need 2 or 1 before we ship. ### Delegate type argument improvements https://github.com/dotnet/csharplang/issues/5321 A couple of weeks ago, Microsoft held our annual internal hackathon, and one of the ideas that we worked on for the it this year was relaxing type argument restrictions, specifically for delegate types. This would allow delegate types such as `Action` or `Action>`, where these types are restricted from use in delegate types today. In general, the LDM has a lot of interest in this space, but there is some concern that this proposal doesn't go far enough in relaxing restrictions, and might run into issues if we try to generalize it more fully. A very common pattern in generic code in C# is to further abstract over parameter types: for example, a method might take a `Func` and a `T`, and pass the `T` parameter to the `Func` invocation. This proposal falls apart for such methods, which is a very early cliff to fall over. If we can instead generalize this work, such that these restricted types are allowed in more places, then we'd be in a much better position. Even if we don't ship the whole thing at once, we'd really like to make sure we're not painting ourselves into a hole with a proposal like this. As a part of this, we also want to explore a revamp to the .NET delegate story. Today, `Action` and `Func` are the de-facto delegate types in C#: they're used throughout the BCL and lambdas/method groups use them where possible for their natural type. However, there are limits to this, as custom delegate types still have some advantages: * Ref kinds/restricted type arguments. This is solved by the current proposal as it stands. * Parameter names. We don't think this would be very difficult to solve, as we have prior art with tuple names. * Attributes. This is much more difficult to accomplish, to avoid needing to synthesize custom delegate types we'd need to have some way of putting attributes on a type argument. The runtime does not support this today, and it won't be as simple to add as the type restriction removals were. We've been moving towards structural typing of delegates ever since `Action` and `Func` were introduced, and we think it might be a better approach to the problem to start from fully structural delegate types, and work backwards to see how we could make that work. It's very likely that we'd end up doing some of the same work generalizing on `Action` and `Func`, but there's some additional interesting wrinkles around `Action` and `Func`. We might be able to unify these two, but we then want to be able to do the same for other types. How would we make it work for `Task` and `Task`, for example? Is there some attribute we could add to the runtime that would allow it to treat these types as identical, forwarding from one to the other? And how would that flow through the C# type system? #### Conclusions We don't have any conclusions today. We think that much more exploration of this space is needed before moving forward with any specific proposal. We want to look into generalizing the generic restriction removals, and into generalized structural typing for delegates. ================================================ FILE: meetings/2021/LDM-2021-10-27.md ================================================ # C# Language Design Meeting for October 27th, 2021 ## Agenda 1. [UTF-8 String Literals](#utf-8-string-literals) 2. [Readonly modifiers for primary constructors](#readonly-modifiers-for-primary-constructors) ## Quote of the Day - "I fought for that when I was young and idealistic" ## Discussion ### UTF-8 String Literals https://github.com/dotnet/csharplang/issues/184 UTF-8 strings are an extremely important part of modern day programming, particularly for the web. We've previously talked about including special support for literals based on UTF-8 in C#, as currently all our literals are UTF-16. It is possible to manually obtain byte arrays of UTF-8 bytes, but the process for doing so is either: 1. Error-prone, if you're hand-encoding a byte array. 2. Cumbersome and slightly inefficient, if you're creating static data to be later used. 3. Very inefficient, if you're converting the bytes every invocation. We'd like to address these issues, doing the minimum possible work to make current scenarios more palatable without blocking our future ability to innovate in this space. Currently, the runtime does not have a real `Utf8String` type, and instead uses `byte[]`, `Span`, or `ReadOnlySpan` as the de-facto interchange type for UTF-8 data. Many members of the LDM are concerned that, if we bless these types with conversions in the language, we will limit our future ability to react to the addition of such a type into the runtime itself. In particular, if `var myStr = "Hello world"u8;` meant any of those three types, we lose out on the ability to make it mean `Utf8String` in a future where that is added. This issue is further compounded because we don't know that such a type _will_ be added in the future: ongoing discussions are still being had over whether a dedicated type will be added, or if the runtime could just have a flag to opt into having all `System.String` instances just become UTF-8 under the hood. The u8 suffix had mixed reception with LDM. On the one hand, turning strings into one of the interchange types (either implicitly via target-typing, or only via explicit cast) is convenient from a programming perspective. It also doesn't create a blessed syntax form that runs into the problems of the previous paragraph. On the other hand, there is no direct indication _how_ the strings are being converted to bytes, which the suffix is useful for. Are the bytes just a direct representation of the UTF-16 data, or an encoding the string in UTF-8 bytes? Another question is whether a language-specific conversion is the correct approach here, or if we could create a user-defined conversion from `string` to `byte[]`. This would allow non-constants to be converted as well, and the language/compiler could make the conversion special such that it could be optimized to occur at compile-time when constant data is involved. It does suffer from the same problems of what byte encoding is being used, however, and we have existing solutions for converting non-constant strings to UTF-8. We also discussed a few other questions: * Should we have a u8 character literal? * People cast `char`s to `byte`s in a number of places today, this would need a suffix. * It would also still need to return a sequence of bytes, rather than a single byte, so what advantage would it bring over a single-character string literal? * Should the byte sequence have a null terminator? * Interop scenarios will want a null terminator. However, they can add one by including a `\0` at the end of the string, and an analyzer can catch these cases. * On the other hand, most managed code scenarios would break if a terminator was included. * How specific should we make the language around the compile-time encoding of a given string literal? * We'd like to make it relatively loose, such that we can say "the compiler is free to implement the most efficient and allocation-free form possible." #### Conclusion We don't feel confident enough to make any final calls today, but we'd like to make a prototype with target-typed literals and get some usage feedback. We'll implement that behind a feature flag, and give the compiler to our BCL and ASP.NET partners so they can give us their thoughts from real API usage. ### Readonly modifiers for primary constructors https://github.com/dotnet/csharplang/discussions/5314 https://github.com/dotnet/csharplang/issues/188 We wanted to revisit this question after we received a large amount of feedback from the community after our [last meeting](./LDM-2021-10-20.md#primary-constructors) on primary constructors. The feedback was notable in particular because of the volume, as our discussion issues don't normally generate as much discussion from as many separate community members as this one did. The feedback generally tended in one direction: mutability is an ok default, especially given the history of C#. However, they would like a succinct way to make the generated fields `readonly`, without having to fall back to manual field declaration and assignment. To address this, we took a look at another longstanding C# proposal, allowing `readonly` on parameters and locals. Several LDM members are concerned about this as a general feature, mainly because of the "attractive nuisance" issue. `readonly` is often a sensible default for locals and (in particular) parameters, but in practice LDM members do not find accidental local/parameter mutation to be a real source of bugs. Meanwhile, if we introduce this feature in general, people will start to use it, and require usage where possible in their codebases. It can add clarity of intent for reading later, but many members are not convinced it meets the bar with C# 20 years old. However, scoping the feature down to just primary constructor parameters was slightly less controversial. Most members of the LDM are not opposed to the concept in general, but we remain conflicted as to whether we need to have such a feature immediately. While we did get a good deal of immediate feedback, some LDM members are unsure whether this feedback will persist after users actually get their hands on the feature and give it a try. We could also take primary constructors in a more radically different direction, where we'd adopt a more F#-like approach and consider these parameters captures, not fields. #### Conclusion We did not come to any conclusions today. We will revisit soon to talk more about this area. ================================================ FILE: meetings/2021/LDM-2021-11-01.md ================================================ # C# Language Design Meeting for November 1st, 2021 ## Agenda 1. [Order of evaluation for Index and Range](#order-of-evaluation-for-index-and-range) 2. [Collection literals](#collection-literals) ## Quote of the Day - "So 'The customer wants it therefore it's wrong' is your new motto?" ## Discussion ### Order of evaluation for Index and Range https://github.com/dotnet/roslyn/issues/57349 We looked at an inconsistency in the order of evaluation between `Index` and `Range` operations, when those are applied to a type that does not have native methods that accept `Index` or `Range` types. The `Index` portion of the spec is fairly straightforward and we were easily able to determine the compiler had a bug with the lowering it performed. However, the specification is unclear about the `Range` translation: it does not clearly communicate the order of evaluation for the `receiver`, `Length`, and `expr` expressions that are cached. Our current behavior is inconsistent with the `Index` behavior, and likely confusing to users. After a bit of discussion, we unanimously agreed to standardize the order of evaluation to be consistent with the written original order in the source, followed by compiler-needed components if necessary. For indexers, this is `receiver`, then `expr`, then `Length` if needed. #### Conclusion Codify the expected behavior as `receiver` then `expr` then `Length`. ### Collection literals https://github.com/dotnet/csharplang/issues/5354 We took a first look at a proposal for a "collection literal" syntax in C#, doing a general overview of the proposed syntax form and gathering general feedback on the proposal. We hope to approach this proposal as a "one syntax to rule them all" form, that can achieve correspondence with the list pattern syntax form, support new types that collection initializers can't today (such as `ImmutableArray`), and serve as a zero or even negative cost abstraction around list creation that is as good or better than hand-written user code. There's some unfortunate bad interaction with existing collection initializers, however. They'll still exist, and they will be advantageous in some cases. Since the user is explicitly calling `new Type` on them, they have an obvious natural type, while collection literals will need some form of target-type for cases where there is no obvious natural type or if the user is trying to select between ambiguous overloads. They also probably wouldn't support this new splat operator, which also makes them worse in that case; even if we extended support for it, it's very likely that the new form would lower to a more efficient form with the pre-length calculation that it able to support. We're unsure about some of the various axes of flexibility in this space. Some of them that we talked about are: * Existing collection initializers require the type to implement `IEnumerable` to be considered collection-like. Do we want to keep this restriction for the new form? It would lead to an odd non-correspondence with list patterns, since they are pattern-based and not interface-based. The proposal actually started more restrictive, but loosened to the current form after considering things like `HashSet h = [1, 2, 3];` and deciding that was perfectly fine. * How specific do we want to make the lowering for various forms? Since it's a goal of the feature to be as optimal as possible here, if we prescribe too specific of lowering forms then we potentially make it difficult to optimize the implementation. We know from experience, though, that while we'll have a small window of opportunity just after ship to make changes to the way code is emitted, making changes years on will be dangerous and will likely have impacts on some user's code. * We think that a natural type for this syntax will have a lot of benefits, such as allowing it to be used for interfaces (`IEnumerable ie = [1, 2];`) and it will be in line with the recent work we did around lambda expressions. However, unlike lambdas, we don't have a single list type in .NET that is unambiguously the natural type it should be. We'll need to dig into the pros and cons of the space and decide on how we want to approach this. We also talked briefly about possible extensions of this space. One obvious one is dictionary literals. We think we can move ahead with collection literals for now, as long as we keep both dictionary literals and dictionary patterns, to be sure we're holding ourselves to the correspondence principle. Another area we should investigate is list comprehensions. We're not sure whether we want comprehensions, but we should at least look at the space and make sure we're comfortable that we'd never consider them or leave ourselves the space to be able to consider them in the future. Finally, we want to look at other contemporary languages and see what they do for this space. For example, F# has dedicated syntax forms for each of it's main list/sequence types. Unlike C#, they have largely unified on a very few specific collection types. This allows them to have dedicated syntax for each one, but this approach isn't likely to work well in C# because we have a much wider variety of commonly-used collections, tuned for performance across a variety of situtations. Kotlin takes a different approach of having well-known methods to construct different collections, such as `listOf` or `arrayOf`. It's possible that, with `params Span`, we'd be able to achieve 90% of what we're trying to do without needing to build anything into the language itself. And then there are languages like Swift, Python, Scala, and others that actually have a dedicated literal for lists, with varying degrees of flexibility. #### Conclusions No conclusions today. A smaller group will begin a deep dive into the space to flesh out the various questions and components of this proposal, and come back to LDM with more research in the area. ================================================ FILE: meetings/2021/LDM-2021-11-03.md ================================================ # C# Language Design Meeting for November 3rd, 2021 ## Agenda 1. [Name shadowing in local functions](#name-shadowing-in-local-functions) 2. [`params Span`](#params-spant) ## Quote(s) of the Day - "params Span. This is an easy one, we should be out in 10 minutes." - "For some value of 8" - "Stack goes up. Stack goes down. You can't explain that." - "That killed my monologue." "Did you mean 'evil villain monologue'?" "No one killed me during the monologue therefore I can't be an evil villain." "No one kills the evil villain during the monologue, they just learn the evil plan and then escape to thwart it when the villain leaves the implementation to his minions." - "Solved the mystery: cat sitting on the spacebar" ## Discussion ### Name shadowing in local functions https://github.com/dotnet/csharplang/discussions/5327 In C# 8, we relaxed the name shadowing requirements for lambdas and local functions, but we wanted to revisit the specific implementation strategy and whether we went too far. In particular, we allowed a local function to _both_ capture a variable from an outer scope and shadow that variable in a nested scope inside the local function. This _and_ in the previous sentence is the part we're concerned with: several members of the LDT didn't realize that we were agreeing to this in the original discussions on shadowing. There are two general models of shadowing in C#: * Locals shadowing another local within the same function. This is expressly disallowed, and has been since C# 1. * Locals shadowing a field. This is allowed, but the user can always get to the field version by using `this`. In the same method, it is possible to use the field without the `this` qualifier and shadow it in a nested scope. Shadowing between local functions while also capturing that local from the outer scope has similarities to both of these models: on the one hand, it's a local variable, even if that variable has been potentially lifted to a field in an implicit closure. On the other hand, it's a variable from a scope outside the current function, just like a field. This duality leads to conflicting resolution strategies, but we ultimately think this has more in common with locals shadowing other locals in the same method than it does with locals shadowing fields. The rules we wish we had implemented are: if a lambda or local function (or a nested lambda or local function) captures a variable from an outer method, it is an error to shadow that variable. However, these rules have been out in the wild for nearly 3 years at this point, and we don't feel that the level of concern warrants a language change or a warning wave to cover it. If an analyzer wants to implement such a suggestion (in dotnet/roslyn-analyzers, for example), they are free to, but the compiler itself will not do so. #### Conclusion We regret that simultaneous capture/shadowing was allowed, and now it's too late to really fix it. Future features should keep this regret in mind and not use the way shadowing in local functions/lambdas works as precedent. ### `params Span` https://github.com/dotnet/csharplang/issues/1757 `params Span` is a feature that has been requested since we first implemented `Span` in C# 7, and has a number of benefits. Today, users interested in performance with `params` methods need to maintain overloads for common scenarios, such as passing 1-4 arguments (as we do for `Console.WriteLine`). These APIs are often not straightforward to implement: if there was a simple common method they could all delegate to, that API would have been exposed in the first place. Because of this focus, `params Span` is an interesting first for the language: a `Span`-based API that is explicitly targetted at the average C# developer, rather than at someone who already knows what a `Span` and why they would want to use it. There are also strong conflicting desires in the space that makes it very difficult to design a single lowering strategy. We want to avoid allocations where possible, but we also want to avoid blowing out the stack when large numbers of parameters are passed to a `params` method. This is particularly important in recursive code: Roslyn regularly runs into issues when inlining decisions can affect whether we are able to compile some customer's code, as the compiler is an extremely recursive codebase. At the same time, we also want to avoid being overly specific in the language specification on how this feature works. We would like to view the details as more an implementation concern, much like we do with how lambdas are emitted, and avoid making strong guarantees about what will or won't stackalloc for what scenarios. As this is more targetted to perf than lambdas are, it's possible that we can't be as glib in this space as we can for lambdas, but it's an aspiration for the feature. Despite our desire to be less specific, we do think it's important to the initial design, and therefore the conception of who the feature is for, to have an implementation strategy in mind. There are a number of different strategies that have been brought, with various pros and cons: * Using a custom struct with a specific layout, and then making a span from the start of that struct. * A shadow-stack that exists solely for the purpose of being able to give out and return stackalloc'd chunks of memory (think RAII stack space). * Pushing variables onto the stack, then making a span from the start of those pushes (similar to the first approach, but without a dedicated type to be wrapped). * `stackalloc` space that can be popped after a function is called. * Not doing any kind of optimized IL in C#, and relying on the JIT to perform the escape analysis and translation from array to stackalloc when viable. #### Conclusion We'd like to see a group from the compiler and the runtime work through these proposed strategies and come up with "the way" or combination of ways that this will work, so we can start to see how the user experience for the feature would actually work. ================================================ FILE: meetings/2021/LDM-2021-11-10.md ================================================ # C# Language Design Meeting for November 10th, 2021 ## Agenda 1. [Self types](#self-types) ## Quote of the Day - "Is that a threat [redacted]?" "I think it's a promise" "Well, then someone gets to tell management why it slipped." "Now that was a threat." ## Discussion ### Self types https://github.com/dotnet/csharplang/issues/5413 Today, we took a first look at adding a self type constraint to C#, as part of the work on generic math in C#. We see 3 main problems that the self type constraint could address: 1. Today, generic math using the Curiously Recurring Type Pattern (CRTP) to specify self types, which looks like `interface INumber where T : INumber`. This is quite verbose, requiring stating the name of the interface twice, and even a small reduction to `interface INumber where T : this` helps both with the number of characters typed and the clarity of the intent of the code. 2. CRTP does not actually enforce that the `T` implemented must be the current type. For example, someone could do `struct MyStruct : IAdditiveIdentity`, because `int` implements `IAdditiveIdentity`. CRTP involving operators will naturally enforce that `T` is actually the current type, because of the rules around operator implementation, but this is complex from both a spec perspective and an implementation perspective. 3. Being able to call explicit implementations of an interface. In .NET 6, many of the preview interface definitions are implemented on types _explicitly_, to avoid users accidentally taking a dependency on them without meaning to. However, this leads to confusion with how to call that implementation: you have to create a generic wrapper method, which we've received feedback is quite confusing. While some of these explicit implementations will be transitioned to regular implementations in .NET 7 when the feature ships for real, a few must remain explicit to avoid covering existing functionality that behaves in a slightly different manner. Of these concerns, we think the best motivation for self types is actually the first one. For concern 2, we're unsure what the real harm is. Yes, users can provide an odd implementation of one of these interfaces, but that's going to be true forever in C# regardless of our decision here, unless we take a breaking change to `IEquatable` and similar interfaces that shipped with C# 2. It does simplify some rules for the compiler, but ultimately we're not sure that addressing concern 2 is a motivating factor. Concern 3 is valid, but we're not sure the self type is the appropriate way to fix it. We've had `base` calls for DIMs on the backlog for a few years now, and we see the possibility for a more generalized way to call a specific implementation of a method on a type. The current design for the self type happens to address this concern, but not in a generalized fashion. In addition, several members of the LDM are not convinced by the prevalence of the need to call these implementations once .NET 7 is out: they do exist, but how common will needing those implementations be? We also talked about the possibility of using associated types to represent the self type, rather than a public type parameter. While this is somewhat attractive, we're in general agreement that unless we have real runtime support for an associated self type, we don't want to take this direction. In C#, we generally avoid having metadata significantly differ from the source; if the metadata needs to have a generic type parameter, then we would like the source to indicate such. At some point, associated types need to be translated into the universal type space, which will spread virally throughout the program: that either needs to be done at compile time by Roslyn (the representation change we want to avoid), or it needs to be done by the runtime. The runtime has concerns about avoiding an MxN problem (where M methods will need to be specialized N times) without whole-program analysis, which is not always available. There's also some argument that, of all the things associated types can do, the self type is one of the least interesting ones. Consumers of the generic math feature will almost certainly still need to use a generic type parameter, because unless they're creating a method with one input and no outputs, they will need to constrain each input and output to be the _same_ associated type, not just _some_ associated self type. IE, using a hypothetical `INumber.this` syntax: ```cs // How is the compiler to know that the `INumber.self` of the `left` parameter is the same as the `INumber.self` of the `right` parameter, and how does it make // any assumptions about the return type? public INumber.self Add(INumber.self left, INumber.self right) => left + right; // Users would actually need to resort to generics to relate these parameters: public T Add(T left, T right) where T : INumber.self => left + right; ``` Given that we see the _vast_ majority of the usages of the self type being consumption, not definition, we're unsure the potentially significant cost of the associated->universal type translation actually buys the user anything. #### Conclusions We're going to have a small group explore this space more and make revisions to the proposal before coming back to LDM. ================================================ FILE: meetings/2021/LDM-2021-12-01.md ================================================ # C# Language Design Meeting for December 1st, 2021 ## Agenda 1. [Roles and extensions](#roles-and-extensions) ## Quote(s) of the Day - "A certain section of the community that I suspect are [long pause] uncommonly aligned with Haskell" - "I feel like I might be, as [redacted] said earlier, uncommonly aligned with Haskell." - "This conference room table is too heavy to flip" ## Discussion ## Roles and extensions https://github.com/dotnet/csharplang/issues/5497 The idea of roles and extensions has a long history in C#, under various names, throughout the years. Previous discussions/issues on the topic include (but aren't limited to: these issues have more links themselves): * https://github.com/MattWindsor91/roslyn/blob/master/concepts/docs/csconcepts.md - Concept C# * https://github.com/dotnet/csharplang/issues/110 - Type classes * https://github.com/dotnet/csharplang/discussions/164 - Shapes and extensions * https://github.com/dotnet/csharplang/issues/1711 - Previous roles and extensions exploration post After some more thinking on the topic, we're back with a proposal in the space. It's early days yet for this proposal: extensive prototyping and exploration of implementation details will be needed to make forward progress. But overall, the LDM is excited about the direction this proposal is taking, and we spent all of today's session discussing the proposal, both from a high level and getting into some implementation ideas. Broadly, there are 2 different features here, and the feature we choose to pursue will heavily drive our implementation concerns. There's a smaller, simpler feature where we say that we will never allow implementing interfaces on classes the user doesn't own. (Not to say that it's a small feature, just that it's small_er_). This feature could be implemented entirely through erasure if desired, requiring little to no runtime changes, but that would mean it has the commensurate issues erasure brings. Overloading could be accomplished via `modopt`s, but edges would still bleed through in reflection and generic usage. The larger, much more complex feature is saying that we will, at some point, allow implementing interfaces on classes the user doesn't own. Even if this feature doesn't ship initially, it will impose a much higher burden on the implementation. In such a world, this code (or something similar) should be possible: ```cs // Assume some pre-existing Fraction type from an older library with a constructor that takes an int numerator and an int denominator role FractionAdditiveIdentity : Fraction, IAdditiveIdentity { public static Fraction Zero => new(0, 1); public static Fraction operator+(Fraction left, Fraction right) => left + right; } // With the above adapter, this code should be compilable: public T Sum(IEnumerable elements) where T : IAdditiveIdentity { T result = T.Zero; foreach (var el in elements) { result += el; } return result; } IEnumerable fractions = ...; Console.WriteLine(Sum(fractions)); ``` However, if that code can compile, then it means a few things: * FractionAdditiveIdentity needs to be real in metadata. It can't just be erased: there needs to be a real runtime type that can be used to find the implementation of `T.Zero`. * There needs to be some sort of variance relationship between `IEnumerable` and `IEnumerable`. Is that an identity conversion somehow? Or a role variance conversion? Neither of these can be accomplished via erasure, and moreover would be a big breaking change if we were to add them later. It might be possible, with the right set of restrictions, to ship an erasure-based solution now and add this later, but we'll need to fully explore the space and have a good understanding of how the full solution would be implemented before we ship the initial feature. When it comes to erasure, there are also different stages that types can be erased in. For example, we could have real metadata for roles, but they could be erased in most scenarios by the JIT compiler, as opposed to being erased by Roslyn. This could potentially simplify the language rules, at the expense of making the runtime side more complex. We also think there are opportunities to leverage our existing variance rules here. `List` and `List` are hard to relate to each other in a non-erasure-based system, because those are really different types and need to have different observable results in type tests, reflection, generics, etc. But perhaps we can say that there is a _variance_ relationship between `List` and `IList`: they are not the same object, but the class `List` type is convertible to an interface via a variant conversion, with automatic boxing conversions inserted by some stage of the pipeline where necessary. Overall, the team is interested in trying to go all the way here. An erasure-based system would allow us to ship in the nearer term, but we've been talking about type classes in the language for years. We'd really like to enable the whole deal, rather than cordoning it off forever more. Doing that is going to take both effort and time, but we think it's worth it. Some other miscellaneous thoughts: * Can roles reimplement underlying behavior? IE, can they provide their own `ToString` to override the existing one? While this is potentially an attractive feature, it would have some serious consequences on implementation strategies, and it throws some odd questions in for the goal of having `Identity` conversions from a role to the underlying type and back. * Is there a way we could have automapping of role properties onto an underlying dictionary, enabling an "easy" mode similar to typescript interfaces? This might be a place where source generators would work well, but if that's the approach we would want to take we'd need `partial` roles. * This feature would resolve a number of alias requests we've had over the years, particularly if we disallow sideways role conversion (or make it require an explicit cast). This would allow a user to have a `Mile` role and a `Kilometer` role, both of which extend some numeric type. They could even potentially be a generic role on top of `INumber`, allowing `Mile` to automatically derive all conversion information from a generic implementation. #### Conclusion The LDM is excited to continue exploring this feature, in particular the full interface implementation version. This is a long lead proposal: it's going to take time to explore and implement. But we think we have a promising start to really start prototyping with. ================================================ FILE: meetings/2021/LDM-2021-12-15.md ================================================ # C# Language Design Meeting for December 15th, 2021 ## Agenda 1. [Required parsing](#required-parsing) 2. [Warnings for parameterless struct constructor](#warnings-for-parameterless-struct-constructor) ## Quote of the Day - "Please don't make that the quote of the day. We want to project responsibility to the community." ## Discussion ### Required parsing https://github.com/dotnet/csharplang/issues/5439 The main question here is whether we should allow types named `required` in the language in C# 11, without escaping the name with an `@`. We did a similar thing for `record` in C# 9, but in that scenario `record` had true syntactic ambiguities. For `required`, there aren't true syntactic ambiguities: just like with `async`, it is possible to fully disambiguate types named `required` and `required` used as a modifier with enough lookahead. However, we have started taking a harder stance against types named with all lowercase 'a'-'z' characters: in .NET 7, we will be report a warning on such type names in general. While we still want to make sure that we're choosing names for new keywords that are unlikely to conflict with user-defined types, we think `required` is unlikely to be a type name, even beyond the general C# convention of not naming in lowercase, as `required` is not a noun. Even for `record`, which is a noun and reasonable one to use (for a record in a DB, as an example), we heard little to no complaints about erroring on it. Therefore, we are comfortable making `required` illegal as a type name without using an `@` to escape it. #### Conclusion `required` will be disallowed as a type name in C# 11. In general, we are ok with taking over all-lowercase identifiers that can be confused for type names, so long as we do our due diligence in making sure that we're not breaking existing common usages. ### Warnings for parameterless struct constructor https://github.com/dotnet/csharplang/issues/5546 This issue around `new S()` where `S` is a struct without a parameterless constructor is a particularly thorny area. There are three main paths we could take to resolve it, each with significant downsides: 1. Warn on field initializers when a parameterless constructor will not be created. This is painful for users trying to share initializer logic across multiple other constructors, and is also painful for record structs. If a user wants to change anything about a record struct parameter (ie, turn the property into a field, make one property `init`-only, add a validation check) there is no alternative _but_ making a field initializer. 2. Warn when calling `new S()` if `S` doesn't have a parameterless constructor. This is an extremely broad hammer, as this code has been legal since C# 1. It also has bad interactions with generics, as `where T : struct` implies `where T : new()`. We think this option is like attempting to use a sledgehammer to drive in a finishing nail. 3. A more limited version of 2 where we only warn if the struct in question has field initializers. This heuristic breaks almost immediately, as we have no way to determing whether a struct from metadata has field initializers. This might be a place that an analyzer implementing option 1 would make more sense than a compiler warning: analyzers can be more configurable than analyzer warnings, both in their severity levels, and in accepting additional options to tune the analyzer. An analyzer will also be able to do more heuristics than we feel comfortable doing in the language, such as detecting when initializers are only using the primary constructor parameters in record structs. We also looked at the second issue listed in the bug, but determined that it is already an error, and we did not need to make any further changes. #### Conclusion We'll look at doing an analyzer implementing a warning on structs with field initializers that do not generate a parameterless constructor, with some amount of heuristic suppression for scenarios where it is unavoidable. ================================================ FILE: meetings/2021/README.md ================================================ # C# Language Design Notes for 2021 Overview of meetings and agendas for 2021 ## Dec 15, 2021 [C# Language Design Notes for December 15th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-12-15.md) 1. Required parsing 2. Warnings for parameterless struct constructor ## Dec 1, 2021 [C# Language Design Notes for December 1st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-12-01.md) 1. Roles and extensions ## Nov 10, 2021 [C# Language Design Notes for November 10th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-11-10.md) 1. Self types ## Nov 3, 2021 [C# Language Design Notes for November 3rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-11-03.md) 1. Name shadowing in local functions 2. `params Span` ## Nov 1, 2021 [C# Language Design Notes for November 1st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-11-01.md) 1. Order of evaluation for Index and Range 2. Collection literals ## Oct 27, 2021 [C# Language Design Notes for October 27th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-27.md) 1. UTF-8 String Literals 2. Readonly modifiers for primary constructors ## Oct 25, 2021 [C# Language Design Notes for October 25th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-25.md) 1. Required members 2. Delegate type argument improvements ## Oct 20, 2021 [C# Language Design Notes for October 20th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-20.md) 1. Open questions in list patterns 1. Types that define both Length and Count 2. Slices that return null 2. Primary constructors ## Oct 13, 2021 [C# Language Design Notes for October 13th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-13.md) 1. Revisiting DoesNotReturn 2. Warning on lowercase type names 3. Length pattern backcompat ## Sep 22, 2021 [C# Language Design Notes for September 22nd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-22.md) 1. Open questions in list patterns 1. Breaking change confirmation 2. Positional patterns on ITuple 3. Slicing rules 4. Slice syntax recommendations 5. Other list pattern features 2. Nested members in `with` and object creation 3. CallerIdentityAttribute 4. Attributes on `Main` for top level programs ## Sep 20, 2021 [C# Language Design Notes for September 20th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-20.md) 1. Lambda breaking changes 2. Newlines in non-verbatim interpolated strings 3. Object initializer event hookup 4. Type alias improvements ## Sep 15, 2021 [C# Language Design Notes for September 15th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-15.md) * Feedback from the C# standardization committee * Permit pattern variables under disjunctive patterns ## Sep 13, 2021 [C# Language Design Notes for September 13th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-13.md) 1. Feedback on static abstracts in interfaces ## Sep 1, 2021 [C# Language Design Notes for September 1st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-01.md) 1. Lambda expression conversions to `Delegate` 2. C# 11 Initialization Triage 1. Required properties 2. Primary constructors 3. Immutable collection initializers ## Aug 30, 2021 [C# Language Design Notes for August 30th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-08-30.md) 1. C# 11 Initial Triage 1. Generic attributes 2. List patterns 3. Static abstracts in interfaces 4. Declarations under `or` patterns 5. Records and initialization 6. Discriminated unions 7. Params `Span` 8. Statements as expressions 9. Expression trees 10. Type system extensions ## Aug 25, 2021 [C# Language Design Notes for August 25th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-08-25.md) 1. Interpolated string handler user-defined conversion recommendations 2. Interpolated string handler additive expressions ## Aug 23, 2021 [C# Language Design Notes for August 23rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-08-23.md) 1. Nullability differences in partial type base clauses 2. Top-level statements default type accessibility 3. Lambda expression and method group type inference issues 1. Better function member now ambiguous in some cases 2. Conversions from method group to `object` 4. Interpolated string betterness in older language versions ## Jul 26, 2021 [C# Language Design Notes for July 26th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-07-26.md) 1. Lambda conversion to System.Delegate 2. Direct invocation of lambdas 3. Speakable names for top-level statements ## Jul 19, 2021 [C# Language Design Notes for July 19th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-07-19.md) 1. Global using scoping revisited ## Jul 12, 2021 [C# Language Design Notes for July 12th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-07-12.md) 1. C# 10 Feature Status 2. Speakable names for top-level statements ## Jun 21, 2021 [C# Language Design Notes for June 21st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-06-21.md) 1. Open questions for lambda return types 2. List patterns in recursive patterns 3. Open questions in async method builder 4. Email Decision: Duplicate global using warnings ## Jun 14, 2021 [C# Language Design Notes for June 14th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-06-14.md) 1. Open questions in CallerArgumentExpressionAttribute 2. List pattern syntax ## Jun 7, 2021 [C# Language Design Notes for June 7th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-06-07.md) 1. Runtime checks for parameterless struct constructors 2. List patterns a. Exhaustiveness b. Length pattern feedback ## Jun 2, 2021 [C# Language Design Notes for June 2nd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-06-02.md) 1. Enhanced #line directives 2. Lambda return type parsing 3. Records with circular references ## May 26, 2021 [C# Language Design Notes for May 26th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-26.md) 1. Open questions in list patterns ## May 19, 2021 [C# Language Design Notes for May 19th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-19.md) 1. Triage 1. Checked operators 2. Relaxing shift operator requirements 3. Unsigned right shift operator 4. Opaque parameters 5. Column mapping directive 6. Only allow lexical keywords 7. Allow nullable types in declaration patterns 2. Protected interface methods ## May 17, 2021 [C# Language Design Notes for May 17th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-17.md) 1. Raw string literals ## May 12, 2021 [C# Language Design Notes for May 12th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-12.md) 1. Experimental attribute 2. Simple C# programs ## May 10, 2021 [C# Language Design Notes for May 10th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-10.md) - Lambda improvements ## May 3, 2021 [C# Language Design Notes for May 3rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-05-03.md) 1. Improved interpolated strings 2. Open questions in record structs ## Apr 28, 2021 [C# Language Design Notes for April 28th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-28.md) 1. Open questions in record and parameterless structs 2. Improved interpolated strings ## Apr 21, 2021 [C# Language Design Notes for April 21st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-21.md) 1. Inferred types for lambdas and method groups 2. Improved interpolated strings ## Apr 19, 2021 [C# Language Design Notes for April 19th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-19.md) 1. Improved interpolated strings ## Apr 14, 2021 [C# Language Design Notes for April 14th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-14.md) 1. Shadowing in record types 2. `field` keyword 3. Improved interpolated strings ## Apr 12, 2021 [C# Language Design Notes for April 12th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-12.md) 1. List patterns 2. Lambda improvements ## Apr 7, 2021 [C# Language Design Notes for April 7th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-07.md) - MVP session ## Apr 5, 2021 [C# Language Design Notes for April 5th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-05.md) 1. Interpolated string improvements 2. Abstract statics in interfaces ## Mar 29, 2021 [C# Language Design Notes for March 29th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-29.md) 1. Parameterless struct constructors 2. AsyncMethodBuilder ## Mar 24, 2021 [C# Language Design Notes for March 24th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-24.md) 1. Improved interpolated strings 2. `field` keyword ## Mar 22, 2021 - *Design review* (No notes published) ## Mar 15, 2021 [C# Language Design Notes for March 15th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-15.md) 1. Interpolated string improvements 2. Global usings ## Mar 10, 2021 [C# Language Design Notes for March 10th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-10.md) 1. Property improvements 1. `field` keyword 2. Property scoped fields 2. Parameterless struct constructors ## Mar 3, 2021 [C# Language Design Notes for March 3rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-03.md) 1. Natural type for lambdas 1. Attributes 2. Return types 3. Natural delegate types 2. Required members ## Mar 1, 2021 [C# Language Design Notes for March 1st, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-01.md) 1. Async method builder override 2. Async exception filters 3. Interpolated string improvements ## Feb 24, 2021 [C# Language Design Notes for February 24th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-24.md) 1. Static abstract members in interfaces ## Feb 22, 2021 [C# Language Design Notes for February 22nd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-22.md) 1. Global `using`s 2. `using` alias improvements ## Feb 10, 2021 [C# Language Design Notes for February 10th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-10.md) 1. Follow up on record equality 2. Namespace directives in top-level programs 3. Global usings 4. Triage 1. Nominal And Collection Deconstruction 2. Sealed record ToString 3. `using` aliases for tuple syntax 4. Raw string literals 5. Allow `var` variables to be used in a `nameof` in their initializers 6. First-class native integer support 7. Extended property patterns ## Feb 8, 2021 [C# Language Design Notes for February 8th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-08.md) 1. Virtual statics in interfaces 1. Syntax Clashes 2. Self-applicability as a constraint 3. Relaxed operator operand types 4. Constructors ## Feb 3, 2021 [C# Language Design Notes for February 3rd, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-02-03.md) 1. List patterns on `IEnumerable` 2. Global usings ## Jan 27, 2021 [C# Language Design Notes for January 27th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-27.md) 1. Init-only access on conversion on `this` 2. Record structs 1. Copy constructors and Clone methods 2. `PrintMembers` 3. Implemented equality algorithms 4. Field initializers 5. GetHashcode determinism ## Jan 13, 2021 [C# Language Design Notes for January 13th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-13.md) - Global usings - File-scoped namespaces ## Jan 11, 2021 [C# Language Design Notes for January 11th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-11.md) - Required properties simple form ## Jan 6, 2021 [C# Language Design Notes for January 5th, 2021](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-05.md) - File scoped namespaces ================================================ FILE: meetings/2022/LDM-2022-01-03.md ================================================ # C# Language Design Meeting for January 3rd, 2022 ## Agenda 1. [Slicing assumptions in list patterns, revisited](#slicing-assumptions-in-list-patterns-revisited) 2. [Parameterless struct constructors, revisited](#parameterless-struct-constructors-revisited) ## Quote of the Day - "I just want to point out that slicing with [redacted] sounds like a cooking show, and I'd watch that" "Yeah" "Me too" ## Discussion ### Slicing assumptions in list patterns, revisited https://github.com/dotnet/csharplang/issues/5599 We took another look at this corner case of list patterns, with an eye for trying to settle on a consistent and logical representation that will be both unsurprising to the user, and set the language up for success in the future if we ever want to build on list patterns in other ways. We generally agree that this is an extreme corner case: the overwhelming majority of slice patterns should be variable assignments, and those that aren't are almost certainly not going to be another a list of pattern with a finite number of elements in it. However, we think it's important to codify the language's _expectations_ of how a well-behaved slice method should work, and that future pattern work could possibly suffer if we don't make those assumptions now. Formally, our expectation is: a `Slice` method should _never_ return `null`. It should return an empty slice, or throw if indexes are out of range, but never return `null`. In order to resolve the pattern handling of this, we have 4 options: 1. Keep the status quo, with inconsistencies between value type slices and reference type slices in this corner case. 2. Remove the null test from the DAG. If the `Slice` method violates the above expectation, the code might throw a null reference exception, depending on how the slice is used. 3. Perform just reachability analysis without the null test, and include the null test in codegen. Codegen will produce slightly less efficient code for reference types. 4. Perform reachability analysis and codegen with a "slice and throw if null" node, which will throw a custom exception type if the above expectation is violated. This has slightly worse codegen than 2, but better than 3. However, it cannot be worked around: an explicit `.. null` pattern would never match, as the custom exception would be thrown when a `null` return from `Slice` was encountered. The only workaround would be to fall out of pattern matching entirely. After some discussion, we were split between 2 and 4. We feel that 1, while an ok solution for this niche case, could potentially have bad consequences long term for patterns if we want to evolve anything about slice patterns in the future. 3 is ok, but we're concerned about the worse codegen for the 99% case here. Patterns can and do assume certain behaviors for the things they operate on: properties might be called multiple times, or they might not be, and should return the same result across evaluations, for example. This is just another similar expectation, this time on the `Slice` method. After further discussion, we think that the inability to workaround the checks in 4 are enough of a concern to tilt the balance between the two. #### Conclusion We will adjust our expectations around slice patterns to assume that `Slice` will never return a `null` value, and we will not insert a `null` test into the reachability or codegen DAGs. ### Parameterless struct constructors, revisited https://github.com/dotnet/csharplang/issues/5552 There has been some feedback on parameterless constructors in C# 10 that we want to address now, while C# 10 is still new. Specifically, when a parameterless struct constructor is synthesized has raised concerns about silent breaks for `new StructType()` when a user or library updates their code. In general, it's a bit unfortunate that C# allows `new StructType()` syntax. `default(T)` wasn't in C# 1 (it was introduced with C# 2's generics), so that was the original way to get a default instance of a value type. We think there may be an opportunity to start correcting this with a .NET 7 warning wave, but that will need more design, particularly around object initializers on struct types. In the meantime, we have 3 options for addressing the concerns with the initial release of C# 10: 1. No change from ship. This would mean that we stop synthesizing parameterless constructors when an explicit constructor is introduced. In particular, this has some serious community concerns about binary compat, namely that adding or removing an explicit constructor changes whether field initializers are run (changing the behavior of source and breaking binary compat). We think they're important enough to rule out this option. 2. Synthesize parameterless constructors in more places. We have a good idea how to do this for regular structs: zero init, then run all the field/property initializers. However, we don't know how to do this for record structs. Field and property initializers in record structs can use primary constructor parameters so the synthesized constructor would need to invoke the primary constructor to run field initializers, but using default values for primary constructor arguments may be invalid. Ultimately, we don't think the inconsistency here is any better than option 1: there would be 3 sets of rules for when a parameterless constructor would be generated. One for reference types (always unless there is an explicit constructor), one for regular structs (always if there are field initializers), and one for record structs (always unless there is an explicit constructor, but you can call `new RecordStructType()` anyway). 3. Never synthesize a parameterless constructor. This means that the simple case of just wanting to add a field initializer isn't supported: a full constructor will have to be added. For example: ```cs Console.WriteLine(new S().field1); // This prints 0 struct S { int field1 = 1; // No constructor is synthesized int field2; public S(int field2) => this.field2 = field2; } ``` This will get easier in the future with primary constructors, making it simple to just append `()` on the end of the struct name to add this constructor definition, but it will be unfortunate for now. Of the downsides in all of these options, this is the one we best understand, and more importantly while it is the least ergonomic, it does fully solve the issue around accidentally removing the parameterless constructor (which changes the behavior of source and is a binary break). #### Conclusion We will go with option 3, never synthesizing a parameterless constructor. If a struct has field initializers with no constructors, this is an error. It is a break over C# 10 as initially released, but the fix is both simple and backwards-compatible with the initial version of C# 10, so we think we're still within our ability to make this change. ================================================ FILE: meetings/2022/LDM-2022-01-05.md ================================================ # C# Language Design Meeting for January 5th, 2022 ## Agenda 1. [Required Members](#discussion) ## Quote of the Day - ![OHI Mark!](LDM-2022-01-05-OHIMark.jpg "Image of Johnny from The Room in the roof scene, saying 'OHI Mark'") - "`System.Runtime.CompilerServices.HemlockAttribute`" ## Discussion https://github.com/dotnet/csharplang/blob/main/proposals/required-members.md Today was entirely devoted to going over open questions in required members, validating the recent specification updates and debating the restrictions introduced. ### Required properties accessibilities In our initial version of the proposal, we had a complex meta-language around contracts, allowing individual members to be added or removed on a per-constructor basis. However, our recent design work has sought to pare back the complexity of the feature, which has lead to needing to revisit the topic of members that cannot be set by consumers of a type. Since constructors can only opt out everything or nothing, they don't have the tools to effectively deal with less visible members. Ultimately, we see 4 options for dealing with this: 1. Disallow the scenario. This is the most conservative approach, and the rules in the OHI are currently written with this assumption in mind. The rule is that any member that is required must be at least as visible as its containing type. 2. Require that all constructors are either: 1. No more visible than the least-visible required member. 2. Have the NoRequiredMembersAttribute applied to the constructor. These would ensure that anyone who can see a constructor can either set all the things it exports, or there is nothing to set. This could be useful for types that are only ever created via static Create methods or similar builders, but the utility seems overall limited. 3. Readd a way to remove specific parts of the contract to the proposal, as discussed in LDM previously. 4. Require that the derived constructor must set any properties that are not at least as visible as itself. Of these, we are most interested in 1 or 2. 3 is readding complexity we specifically wanted to remove from this proposal, and while we can add it back at a later date, we're concerned about feature creep for the initial version. 4 has some concerning impacts on implicit changes to the public API of a constructor: by making a member more visible than it is currently, a user could unintentionally expose requirements to their consumers they didn't mean to. For 1 and 2, we think either would be acceptable. 2 is more flexible, but we also don't think the scenario it would cover is important to the feature. We think starting with the most conservative set of rules is acceptable, and keeping 2 or a more complex meta-langauge in our back pocket for future requests. #### Conclusion We will go with option 1: all required members must be at least as visible as their containing type. ### Hiding We verified the section of the specification on hiding, now that we have a decision on the principle for accessibility. Users cannot access hidden members in an object initializer, so we agree that required members cannot be hidden. In the future, if we come up with some syntax for accessing a specific (potentially hidden) member, we should keep this scenario in mind. #### Conclusion Specification upheld. We will disallow hiding required members. ### Overriding #### Adding/removing `required` on override In order to preserve our future design space on contract modifications, we want to disallow removing `required`ness on override. We don't think adding `required` is a problem, as contracts are already additive today, but removing on a per-member basis is not supported in the other aspects of the proposal, so we think we should disallow here too. ##### Conclusion Adding `required` on override is allowed. If the overridden member is `required`, the overridding member must also be `required`. #### Overriding `required` virtuals We have another interesting question on overriding: ```cs abstract class Base { public required abstract int Prop1 { get; set; } public required virtual int Prop2 { get; set; } } class Derived : Base { public required override int Prop1 { get; set; } // This is probably fine? public required override int Prop2 { get; set; } // Is this ok? public void ToString() { _ = base.Prop1; // Already illegal _ = base.Prop2; // What happens, was base.Prop2 initialized? } } ``` There is a general anti-pattern in C# around overriding a virtual property that has storage, and not delegating to that original storage. In particular, the above code overrides Prop2, and then explicitly goes to the base storage. While this is certainly an anti-pattern, we don't think `required` is the place to solve it. There are other logic errors that can result from this case, all of which relate to stale or incorrect data from accessing the wrong storage location. A warning or analyzer would be better suited to addressing that problem in general, and `required` should not be opinionated beyond that. ##### Conclusion No specific restrictions. An analyzer can generally warn about this type of anti-pattern. ### Metadata Representation #### `RequiredMembersAttribute` Finally today, we looked through the proposed metadata representation, verifying the general design. We have two proposals for indicating the required members in a type: 1. Put a single `RequiredMembersAttribute` on the type, listing every member of that type that is required. 2. Put a `RequiredMemberAttribute` on the type, and then put a `RequiredMemberAttribute` on every member in that type that is required. We're happy that both of these preserve our desire for _additive_ contracts: they both require walking up a type chain to get the full list of required members for a type, and additions to a base type do not require downstream changes. We think the version with a single attribute is slightly more attractive, so we'll go with that one, but with a small modification to the lookup rules. The current proposal says that the lookup rules should be standard member lookup: we think this is needlessly complex, and we can simply look at members defined in the current type. Future contract modification attributes might need more complicated lookup rules, but we can address that when we get there. ##### Conclusion Design 1 is upheld, and the member lookup rules are simplified to just looking at members of the current type. #### Constructor protection One of our desires for this feature is that removing required members is _not_ a binary breaking change. This means that our usual protection trick, putting a `modreq` on any constructor that must set required members, isn't going to work for this scenario; if a user removed the last `required` member from a type, that constructor would no longer have a `modreq` on it, breaking binary compat. We therefore need to look at a different method of protecting required members. Our last prior art here is in ref structs, which put an `ObsoleteAttribute` on the type with a specific message, that newer compilers could recognize and specifically ignore. This solution is imperfect, however: `ObsoleteAttribute` can only be applied once, and if the user is in an obsolete context (such as in an obsolete type/member) that obsolete marker is ignored. While some users do this intentionally to work around restrictions, it's also very easy to accidentally hit just by virtue of working in a legitimately actually obsolete member. We think this is unfortunate, and that we'd like to add a new trick to our toolbox for these scenarios. We will look at adding a new attribute to the runtime for the express use of the compiler to "poison" something without affecting binary compat, and have the compiler start recognizing this attribute and specifically disallowing it. This won't help us for required members immediately: we'll need to apply both this new poison attribute and `ObsoleteAttribute`, and may need to continue doing so for some time. But we regret not adding such an attribute for ref structs then, so we should go ahead and do this now. ##### Conclusion We will apply `System.ObsoleteAttribute` to constructors of types with required members, and work with the runtime team and other .NET language teams to add a new attribute specifically for poisoning types/members without affecting binary compat. ================================================ FILE: meetings/2022/LDM-2022-01-12.md ================================================ # C# Language Design Meeting for January 12th, 2022 ## Agenda 1. [Open questions for `field`](#open-questions-for-field) 1. [Initializers for semi-auto properties](#initializers-for-semi-auto-properties) 2. [Definite assignment for struct types](#definite-assignment-for-struct-types) 2. [Generic Math Operator Enhancements](#generic-math-operator-enhancements) ## Quote(s) of the Day - "I should really get a beer, but I'll wait" "Stop [redacted], it's only 10:01 here" ... "What time is it there, snark-o'clock?" "It would be if I grabbed that beer" - "It was stupid syntax, but it was legal" ## Discussion ### Open questions for `field` https://github.com/dotnet/csharplang/blob/4773c1bdb692aba574204a9affa890b0af3d2c05/proposals/semi-auto-properties.md#open-ldm-questions #### Initializers for semi-auto properties For semi-auto properties, there is a question of where initializers are allowed, and what effect they have when written. Our existing rules are pretty simple: manually-implemented properties cannot have initializers. Auto-properties can have initializers, and the effect of the initializer is assigning to the backing field. The `field` keyword walks a fine line here, sitting squarely between both worlds. We think there's some main issues to solve: * If there is a manually-implemented setter, then an initializer cannot call the setter, as initializers run _before_ the constructor and setters can reference type state. Initializers cannot reference type state, so this breaks the rules that rely on that invariant. * On the other hand, for manually-implemented setters, if an initializer assigned directly to the backing field this would cause a very visible but non-obvious behavior difference between putting an initializer on a property and an initializer in the constructor. It is possible to observe a difference here today, but it required a virtual property overridden in a dervied type and is a much more niche case. We think this difference is both confusing and a potential footgun in waiting. To solve this, we have a few options for restrictions on initializers: 1. Treat semi-auto properties as if they are manually-implemented properties. This means they would not support initializers. 2. Treat semi-auto properties with non-existent for auto-implemented setters as if they were auto properties. This means such properties would be allowed to have initializers, and no other types of properties would be allowed to do so. 3. Allow all properties with backing fields to have initializers. These would assign to the backing field, and there would be a meaningful difference between the initializer and assigning in the constructor. After some discussion, we feel that 3 is a step too far. It could potentially be enabled later, if we can find a set of rules that makes sense and unifies the area, but for the moment we don't have those rules. We also think 1 might be a bit too conservative: part of this feature is easing the cliff between full properties and auto properties. Disallowing initializers when the setter is "trivial" seems like it goes against this desire. There's also some potential future overlap with [property-scoped fields](https://github.com/dotnet/csharplang/issues/133). These fields will, presumably, need to be accessible in a limited fashion from constructors or other locations that can access type state, in order to initialize them. `Lazy` backing fields, for example, will almost certainly need to be initialized with a delegate that references the current type, something that cannot be done from an initializer today. If we need more granular control for the automatic backing field, we think we can rely on that proposal to allow this control. ##### Conclusion We have the following formalized rule for when initializers are permitted: * Initializers are permitted when a property has a backing field that will be emitted and the property either does not have a setter, or its setter is auto-implemented. We have the following rule for when properties can be assigned in the constructor of a type: * Properties can be assigned in the constructor of a type if that property has a setter, or is a get-only property whose backing field will be emitted. #### Definite assignment for struct types Semi-auto properties pose a very interesting challenge for definite assignment in struct types. Today, all fields in a struct type must be fully initialized by any constructor of that type. Existing auto properties are fine here: the compiler controls the implementation of the `set` method, and we know that they are not accessing any instance state other than the backing field, and not exposing `this` before assignment of all fields are completed. Semi-auto properties, by contrast, can have a user-implemented `set` or `init` method, which has all the power a regular `set` or `init` method has. It can access instance state, potentially before some other part of the instance state has been assigned. Further, there is no way to mention the backing field from outside the property (this being one of the main selling points of the feature). We see a few potential solutions for this: 1. Require a chain to another constructor or a `this = default` before access to semi-auto properties is permitted. This a bitter pill, as property initializers run before the constructor body. This would mean any property initializers are blown away by the `this = default;` assignment. 2. Have the compiler initialize all automatically-generated backing fields to `default`. This would allow skipping initialization of all auto and semi-auto properties, the former of which is not allowed today. While we think this would work, it introduces inconsistencies between regular fields and backing fields, which is odd, and there could be some clients that suffer from default initializers for all backing fields (mainly around perf). After some discussion, we're pretty conflicted here. We'll have a small group go and explore these ideas and other potential solutions and come back to the LDM with their findings. ##### Conclusion No answers today. ### Generic Math Operator Enhancements https://github.com/dotnet/csharplang/issues/4665 https://github.com/dotnet/csharplang/issues/4666 https://github.com/dotnet/csharplang/issues/4682 Finally, we took a look at 3 different operator-related enhancements for the generic math scenarios we're targeting for .NET 7/C# 11. We're supportive of all of them, but the `checked` operator enhancements are definitely going to be the largest amount of work of the 3. There's new syntax and rules around what things are called when to be hammered out. For relaxing shift operator requirements, we think that this rule has served its purpose. It is technically possible, via external libraries, to emulate `cout` and other shift operator semantics in C# today, but we don't see people doing this. Relaxing the restrictions will make it easier for those libraries to be written, but the general patterns for the language have been established at this point. #### Conclusion These proposals are generally approved of. We'll look forward to reviewing the concrete designs soon, particularly around `checked`. ================================================ FILE: meetings/2022/LDM-2022-01-24.md ================================================ # C# Language Design Meeting for January 24th, 2022 ## Agenda 1. [Required members metadata representation](#required-members-metadata-representation) 2. [Default implementations of abstract statics](#default-implementations-of-abstract-statics) 3. [Triage](#triage) 1. [Nested members in with and object creation](#nested-members-in-with-and-object-creation) 2. [Binary Compat Only](#binary-compat-only) 3. [Attribute for passing caller identity implicitly](#attribute-for-passing-caller-identity-implicitly) 4. [Attributes on Main for top level programs](#attributes-on-main-for-top-level-programs) ## Quote(s) of the Day - "More knowledgeable, but scared is how I like to live my life" - "My new goal is to be able to say C# 11 has a 'spaceship operator'. I don't care what it does." ## Discussion ### Required members metadata representation https://github.com/dotnet/csharplang/discussions/5615#discussioncomment-1940488 https://github.com/dotnet/csharplang/blob/main/proposals/required-members.md [Last time](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-05.md#requiredmembersattribute) we discussed the metadata for required members, we had indicated that we tentatively preferred a single `RequiredMembersAttribute` on a type, which listed strings for each member in the type that was required. We had not considered a couple of potential issues with this, however: 1. IL is allowed to duplicate names for members, so long as the members are of a different kind. IE, a field and a property with the same name is perfectly legal in IL. 2. Non-compiler usages might become more difficult. Doc generation, for example, will want to be able to list the required members for a type, and reflection will likely want to represent whether a member is required in that member reflection info. While still doable with the class-based attribute, it requires more work than just looking on the member for an attribute. We did have some initial concern with the many attribute approach that it would potentially impact load times for assemblies, like nullable was going to before we invested in a compression strategy. After discussion with the runtime folks, however, we came to the conclusion that this is likely minimally concerning, as there won't be nearly the proliferation of these attributes that `NullableAttribute` would have had as originally designed. Thus, to make implementation and consumption simpler, we will go with the `RequiredMemberAttribute` on every member that is required, and on a type that defines required members. #### Conclusion We will adjust the specification to put a `RequiredMemberAttribute` on every member that is marked required, and to put it on any type that contains such members. ### Default implementations of abstract statics As part of determining our C# 11 focuses, we wanted to discuss default implementations for abstract statics, and whether we want to try and get them in during C# 11, or in some future version of C#. Part of this is, of course, runtime dependent; if support isn't there, there's nothing for the language or compiler to implement. From looking at our motivating scenarios, we have some comments on DIMs: * They're an important library design tool. Generic math would like to use them in a few scenarios, but it's not a "critical" feature to have in V1. If they slipped to C# 12, the scenario would be ok. * The ability to call base implementations is not a concern from a library design tool, and likely will not be designed for C# 11. We need to make sure to account for the ability to call base DIMs for instance methods as well, so this is a bigger feature. We'll let prioritization be driven by user request. #### Conclusion If the runtime support lands, we'll try to land the language side as well. ### Triage #### Nested members in with and object creation https://github.com/dotnet/csharplang/issues/4587 There is some confusion around this proposal in `with` expression: what are the semantics going to be? In object initializers, nested member initializers do not create new objects, they just assign to existing ones. This makes the presented `with` syntax confusing, and we think it needs more workshopping. A potential alternative could be something like `a with { Member with { ... } }` to imply the nested withing, but this needs to be explored and fleshed out a bit more before we are ready to commit to accepting the proposal. ##### Conclusion Needs more work. #### Binary Compat Only https://github.com/dotnet/csharplang/issues/4984#issuecomment-891213312 As part of the next issue, we heard about a nascent proposal around the ability to mark a method as for binary compatability only, removing it from overload resolution entirely. We think the idea has merit, but need to see a complete proposal on this before we move forward. ##### Conclusion Needs more work. Whoever looks at this should also look at a version where this is a flag on ObsoleteAttribute. #### Attribute for passing caller identity implicitly https://github.com/dotnet/csharplang/issues/4984 This attribute is useful for the BCL team and expands our caller info attributes. While we do have some reservations on how useful it will be until we get the above binary compat only ability, we think this is fine. Some small details left to work out before we can add it to the working set or backlog though. ##### Conclusion Needs more work. #### Attributes on Main for top level programs https://github.com/dotnet/csharplang/issues/5045 This is important to unblocking partner scenarios, and we will be starting implementation shortly. ##### Conclusion Working set. ================================================ FILE: meetings/2022/LDM-2022-01-26.md ================================================ # C# Language Design Meeting for January 26th, 2022 ## Agenda 1. [Open questions in UTF-8 string literals](#open-questions-in-utf-8-string-literals) ## Quote of the Day - "Doesn't everyone love the native compiler?" ## Discussion ### Open questions in UTF-8 string literals https://github.com/dotnet/csharplang/blob/main/proposals/utf8-string-literals.md#unresolved-questions https://github.com/dotnet/csharplang/issues/184 Today, we looked to answer some initial questions on the UTF-8 prototype implementation. It's important to note that this prototype is intended to be handed off to partners to play with so we can get some feedback on what works and what doesn't: the rules we're deciding today are not intended to be the final LDM rulings on the questions. We can and should revisit some or all of these questions after initial impressions are gathered from users. #### Conversions from null literals We do have precedent in the language for conversions that depend on the constant value being converted: for example, integer constants can be converted to bytes if the constant value would fit in a byte. However, we don't think that it's necessary to special case here. We already allow `(string)null` to be converted to `Span` via a user-defined conversion, so we don't think there's any new ground being tread here. It does marginally widen the breaking change surface, but we think that's ok for the prototype. ##### Conclusion The conversion will apply to null string constants. #### Conversion kinds After examining the existing types of implicit conversions, we think conversion is best specified as a new kind of conversion. It doesn't fit well into any existing conversion: constant conversions, for example, have both constant input and output values. This conversion won't have a constant output value. ##### Conclusion We will introduce a new conversion kind for string constant to UTF-8 bytes. #### Implicit standard conversion The next question after deciding on a separate kind is whether this conversion will be a standard conversion or not. This impacts whether it can be used as an input or output conversion for user-defined conversions, and is also where history complicates our decision a bit. The native C# compiler (used for C# 5 and earlier) implemented the spec incorrectly and considered more conversions to be standard than actually are, leading to an experience that is potentially inconsistent, no matter what we do. For now, though, our initial gut reaction is that there's too much involved in one of these conversions to be considered in the list of standard conversions. We will revisit this question after prototype feedback. ##### Conclusion Not a standard conversion, for now. #### Expression tree representation In theory, this conversion could be represented in an expression tree just as the final bytes wrapping an array creation. However, the conversion itself has semantic meaning, and we think that it's important for expression trees to show this meaning. Therefore, we will block this conversion in expression trees, and future modernization efforts will consider this case well. ##### Conclusion Blocked. #### Natural type of `u8` literals Since this is a prototype, we think that the proposed `byte[]` is fine for now. We will likely want to have a deeper debate about whether string literals should have a natural type of a mutable array, but we don't think that debate is necessary for now. ##### Conclusion The natural type of `u8` literals will be `byte[]`, for the prototype. #### Conversion depth There is some interplay here with the standard conversion rules: if the conversion is a standard conversion, then any place that takes a `byte[]` as a user defined conversion would be able to take a string literal. That leaves things like `IEnumerable`, which standard conversions have no impact on. There's a slippery slope here. If we don't make the conversion a standard conversion, then there's a potentially-infinite number of places to make the conversion work. We'll err on the side of getting feedback for now: the current set of conversion targets (`byte[]`, `Span`, and `ReadOnlySpan`), and we'll see what users think after using the prototype. ##### Conclusion No new conversion targets added for now. #### Breaking changes This one is definitely the trickiest of the issues we're discussing. We just made a big breaking change in C# with lambda natural types. This was successful, but it took a lot of users using the feature and telling us what was broken for us to hammer down the rules. The better function member rule as proposed will address some of the breaking changes, but not all: new instance methods could be called instead of extension methods, for example. Additionally, the justification we used for such changes with lambdas does not apply here. The justification for lambdas was that any extension methods almost certainly delegated to the original method, just providing a target type. This reasoning almost certainly does not hold up for `byte[]` vs `string`. We think the best way to gather feedback is again to be bold and have the prototype make no adjustments at all. This will almost certainly break some people, but based on the lambda feature we have real evidence that people use our previews and report back to us when things break. This feedback will be used to help determine how agressive we need to be around breaking changes, or whether we need to abandon the conversion form entirely and only do a `u8` syntax form. ##### Conclusion The prototype will not adjust any rules here, so we can hopefully see what breaks in practice. #### Suffix case sensitivity We should be consistent with other constant suffixes. ##### Conclusion Consistency means that `u8` or `U8` will be allowed. ================================================ FILE: meetings/2022/LDM-2022-02-07.md ================================================ # C# Language Design Meeting for February 7th, 2022 ## Agenda 1. Detailed review of https://github.com/dotnet/csharplang/blob/main/proposals/checked-user-defined-operators.md (AlekseyTs) ## Checked user-defined operators https://github.com/dotnet/csharplang/blob/main/proposals/checked-user-defined-operators.md ### Syntax We discussed a few syntax options: 1. `checked` after `operator` 2. put `checked` before `operator` 3. use `checked` modifier Conclusion: Stick with proposed syntax (option 1, `checked` after `operator`). PS: it will be nice for implementation to gracefully handle invalid combinations, like `checked +`. ### Checked implicit conversions? We clarified why conversions cannot be `implicit` and `checked`. Generally, implicit conversions should not fail, throw or be lossy. That's already the case with (most) existing conversions and .NET guidelines. There's an exception: integer to floating point conversion in C# does allow loss. Conclusion: Stick with proposed restriction (conversions cannot be `implicit` and `checked`). ### Explicitly `unchecked` operators The proposal is that there are `regular operators` (without a keyword) and `checked operators` (with `checked` keyword), but there is no keyword like `unchecked` to express the "no keyword" default. In some other discussions, we have have allowed a keyword to make the default explicit, but it doesn't feel that useful here. Legacy operators don't exactly have the `unchecked` meaning. Conclusion: No explicitly `unchecked` operator (whether semantically equivalent to regular operators or some other meaning). ### Restricting allowed combinations of declared operators? Should there be some requirements on having both operators when a `checked` operator is declared? What combinations do we allow? 0. provide none 1. provide both 2. provide only regular/unchecked 3. provide only checked The only combination we could consider restricting is (3). But that seems a valid case when you don't know what to do for overflow. There's a question whether should declare a regular operator in such case. Conclusion: No requirement. We may revise this after follow-up discussion on overload resolution rules. ### Is the body of a checked operator in checked context? There's a risk users would assume that `checked` keyword affects context in method body. But we feel users are likely to want to implement their own checks and use unchecked logic in the body, and we don't want such cases to require use of `unchecked`: ``` operator checked ++(...) { unchecked { ... } _ = unchecked(...); } ``` For example, some types like uint128 need some checked and some unchecked logic in their checked operators: ``` result.lower = left.lower + right.lower; // overflow is expected/by-design result.upper = left.upper + right.upper; // overflow is an exception for checked operator ``` We considered some possible rules for producing a warning. For example: a `checked` operator must has an `checked` or `unchecked` block/context. Conclusion: No warning in the language. We encourage analyzer or IDE help. Can be revised based on feedback. ### Names in metadata Proposal API names seem straightforward but should be reviewed through API review. op_UnsignedRightShift already establishes precedent on naming schema. ### Overload resolution We reviewed the alternatives listed in the proposal doc, and explored some variants. It's not obvious that we should favor a checked operator on base type over an unchecked/regular operator on closer type, in a checked context. It's not obvious whether the rules should be symmetric (checked picked first in checked context, unchecked/regular picked first in unchecked context) or asymmetric (checked operator can only be picked in checked context). We discussed how to represent a type with only a checked operator. Should that literally be a `checked` operator, or rather a regular operator? The main alternative proposal we explored: 1. lookup would find the nearest applicable candidate (`checked` operator only applicable in checked context, regular operator applicable in either context) 2. in checked context, if there's any `checked` candidates, discard the unchecked ones Conclusion: proposal will be spelled out and re-discussed in LDM. ================================================ FILE: meetings/2022/LDM-2022-02-09.md ================================================ # C# Language Design Meeting for February 9th, 2022 ## Agenda 1. Continue discussion of checked user-defined operators 2. Review proposal for unsigned right shift operator 3. Review proposal for relaxing shift operator requirements 4. Triage champion features ## Quote(s) of the Day * "Sorry, Jared, gonna have to veto that. Somebody else say something funny in the next 40 minutes" ## Discussion ### Checked user-defined operators (cont'd) https://github.com/dotnet/csharplang/blob/main/proposals/checked-user-defined-operators.md * We discussed the expected behavior for checked operators in Linq expression trees. Checked operators will be supported via UnaryExpression and BinaryExpression expressions in tree. * The `BinaryExpression` already supports `Checked` expression types that determine whether or not the operation is occuring in a checked context. * The `MethodInfo` stored in `expression.Method` will provided information about whether or not the operator used is a checked operator. * We discussed the possibility of supporting invoking `checked operators` in `dynamic` invocations. * Behavior can be adjusted in Core but not in framework * We discussed the possibility of disabling the feature on platforms that do not support dynamic invocation via a runtime flag through `RuntimeFeature`. * This would be the first time that we introduce a runtime feature flag for a C# langauge feature as they are typically intended for runtime limitations. * We discussed the set of possible options for resolving this: * Option 1: Do not add support for this. * Option 2: Only implement for CoreCLR and assume that dynamic evaluation behavior will be different in Framework. * Option 3: Adjust the runtime binder across all platforms (most expensive option). * There is prior art for scenarios where we have added language features but did not make modifications to the runtime binder, such as for default interface methods. * We acknowledge that support has been added for smaller features in the past but that we don't consistently evolve the runtime binder with the language. * **Conclusion:** We will investigate the cost of adding support for `checked operator`s in dynamic invocation in CoreCLR and pursue an implementation if the cost is not too high. ### Unsigned right shift operator https://github.com/dotnet/csharplang/blob/main/proposals/unsigned-right-shift-operator.md - We examined the behavior of support unsigned shift operator as a built-in and user-defined operator. - The unsigned shift operator will generally match the behavior of other shift operators when it comes to aspects around grammar ambiguities, precedence, and more. - The operator will support target the same built-in types as the signed right shift operator. - :warning: The spec currently outlines that the right shift operator supports fewer types than it actually does. For example, the right shift operator does support target `nint`s and `nuint`s but this is not documented. We should follow up on updating the spec to synchronize with the acactualmplementation. - To this end, the unsigned shift operator will support the same set of types that the signed shift operator does in implementation. - We discussed some of the challenges around supporting `>>>` in Linq expression trees. - Proposal 1: For user-defined operators, a `BinaryExpression` node targeting the operator method will be created. For built-in operators, a `BinaryExpression` node will only be created if the first operand is an unsigned type. For scenarios where the first operand is a signed type, a conversion from signed to unsigned will happen before the `BinaryExpression` node is created, then converted back to a signed type. - Proposal 2: Do not add support for `>>>` in Linq expression trees as part of this change. - Proposal 3: Do not add support for `>>>` in Linq expressions as part of this change but place to introduce a new node type for handling unsigned types in `BinaryExpression`s. - **Conclusion:** We'll adopt Proposal 3 and not add support for `>>>` in Linq expressions in the initial phase of this work. ### Relaxing shift operator requirements https://github.com/dotnet/csharplang/blob/main/proposals/relaxing_shift_operator_requirements.md * To support some generic math scenarios, we are relaxing the requirements for shift operators so that the right-hand operator is no longer restricted to an `int` type. * We recognize that the restrictions on the type of the second operand were placed intentionally to avoid unintended behavior (e.g. `cout << "foobar"`) but recognize that the benefits of relaxing the constraint outweight the benefit of stricter requirements on the operator. * **Conclusion:** We support relaxing the constraints to support these new scenarios and recognize that this opens the door to strange behavior. ## Triage ### Collection literals https://github.com/dotnet/csharplang/issues/5354 * This proposal already has general approval from LDM but needs to be fleshed out further in a working group. * Conclusion: Place this issue in the **Working Set** milestone. ### Label statements https://github.com/dotnet/csharplang/issues/5470 * This proposal outlines adding support for label as a statement on its own to avoid scenarios where a `:;` has to follow labels that are defined at the end of a scope, which impacts readability. ```csharp if (!Condition()) goto AfterWork; Work(); AfterWork:; ``` * This issue was discovered in the context of the Regex source generator, but since labeled statements are frequently used in high-performance code it can also appear in non-generated source as well. * Conclusion: The problem is valid and the proposed solution is elegant and non-invasive. Place this issue in the **Any Time** milestone due to lack of urgency. ### Roles and extensions https://github.com/dotnet/csharplang/issues/5497 * This issue has been previously discussed in an LDM and is being followed up on. * Conclusion: Place in **Working Set** milestone. ### "file private" Visibility https://github.com/dotnet/csharplang/issues/5529 * This proposal outlines adding a mechanism to support limiting the visibility of a types to a particular file. * Other lanuages have the ability to hide types from other files in the build including signature implementation files in F# and package level visibility configuration in Go. * There was a variety of discussion about the pros/cons of different levels of visibility for this feature including file-based visibility and namespaced-based visibility to support for local types. * Implementations in other languages have varied levels of scoping from file-based to type-based. * **Conclusion:** We generally agree that there is merit to solving this problem, but some more details need to be fleshed out before we can examine the proposal more fully. Marking this one as a candidate for surfacing back to the committee. ### Adding Index support to existing library types https://github.com/dotnet/csharplang/issues/5596 * This proposal outlines adding support for implicit Index types to existing library types (e.g. `List.Remove`) to allow existing overloads to work with indexors. * We discussed some general concerns around overload resolution with this change and the need to have sensible rules for when an `Index` type is supported in a feature. * The current proposal limits this implicit support to APIs that accept an `int index` parameter but this does not capture: * APIs that might use other types, such as `long`, particularly for those that are in libraries targeting "big data" scenarios (machine learning, GPU-based work, etc.) * APIs that use alternative parameter names (e.g.`position` or `offset`) for arguments that can be treated as indexors. * Scenarios where an API might have multiple arguments (e.g. `(int index, int length)` ) not all of which can be treated as an index. * We discussed the benefits of this work, particularly in reducing the number of new overloads that need to be added and ensuring that users don't have to be on a particular version to use the new Index-based APIs. * We discussed whether it would be sensible to provide an opt-in or an opt-out for some of the functionality to account for scenarios where the overload resolution in a way that doesn't adhere to the users expectations * **Conclusion:** Place in **Working Set** milestone and bring it as an agenda item in an upcoming LDM meeting. ================================================ FILE: meetings/2022/LDM-2022-02-14.md ================================================ # C# Language Design Meeting for February 14th, 2022 ## Agenda 1. [Definite assignment in structs](#definite-assignment-in-structs) 2. [Checked operators](#checked-operators) ## Quote of the Day - "Do we have any historical records on how the early design meetings handled bio breaks?" ## Discussion ### Definite assignment in structs https://github.com/dotnet/csharplang/issues/5737 As part of designing the semantics of the `field` keyword in value types, we have to decide what the impact to definite assignment is. In a [previous meeting](LDM-2022-01-12.md#definite-assignment-for-struct-types), we looked at some possible solutions, and a small group met to discuss a couple of possible solutions: 1. Require `this = default;` in the constructor of the struct. We quickly discarded this as unusable: we just added field initializers, and this would completely blow those initializers away. 2. Automagically initialize all backing fields to default. We like this idea, but are concerned it's very broad, and would lead to double initialization 3. Provide a way to assign the backing field of an auto or semi-auto property and require users to assign it. We thought this was tedious, and could potentially lead to user confusion. Of the 3 options, we liked 2 the best. However, futher thought lead to the idea being debated today, which is to repurpose the existing definite assignment for struct parameters to be used for determining what fields need to be assigned by default. In order to think about this, we wanted to look back at the original design for structs, and examine why C# didn't take this route in the beginning. While there are currently no members of the LDT who were on the design team at that time, we have a decent understanding of the reasoning here: 1. C# originally had "making porting from C really easy" as a top-of-mind issue. In C/C++, value types are often initialized by assigning to all the components of the value directly, rather than by calling any named constructor. In order to make this work with the concept of definite assignment in C#, that meant that the struct must be able to be considered definitely assigned after all components have been initialized (this was the source of the bug in the native compiler that resulted in [CS8882](https://github.com/dotnet/roslyn/issues/30194)). 2. For consistency with local definite assignment for value types, the constructor of a value type has similar restrictions. For `this` to be accessible in the constructor, it must fulfill similar restrictions to `this` as a local. However, this "similar restrictions" reason has a hole in it: regular partial properties already throw a wrench in the mix by creating an anonymous field that can't be seen by anyone. Further, since private fields aren't visible outside the struct, there was already a hole in this logic from the start. We think that, given these points, using definite assignment for this purpose with the option to activate warnings to go back to the original behavior in C# 10 and prior is a fine solution. We will follow up with emeritus members of the LDT who were present when the original restrictions were made, but barring any large revelations we like this approach. #### Conclusion Proposal is accepted, contingent on following up with emeritus LDT members on original reasoning around struct definite assignment. ### Checked operators https://github.com/dotnet/csharplang/pull/5740 https://github.com/dotnet/csharplang/issues/4665 Following the last [2](LDM-2022-02-07.md) [meetings](LDM-2022-02-09.md#checked-user-defined-operators-contd) on checked operators, we reviewed the detailed update on checked operators. In general, this is attempting to resolve the hierarchy of importance for operator resolution, where the elements are: 1. Nearness of the operator - how far up the type hierarchy is the operator? IE, defined on the type being used, or on its base, or that type's base, etc. 2. Betterness - this is standard overload resolution, ie which types are better suited. 3. Checkness - this is a new axis for operator resolution: whether we're in a checked context or not. For existing resolution, the order is nearness, then betterness. Containing types are searched _until_ an applicable operator is found, and then all operators from that type are added to the candidate type list and no further types are searched. At that point, the candidates are narrowed via betterness rules. Importantly, this means that, even if an operator further up the tree is "better" (types are a more exact match), it will never be found because nearness is more important than betterness. In the previous meetings, we decided that, for the new axiom of checkness, checkness is less important than nearness, but more important than betterness, and for the most part the new rules reflect this. However, we are still a bit conflicted on this ranking of axioms. Part of this conflict is in how the operators are defined; we don't have "checked" and "unchecked" operator flavors, we have "checked" and "default" operator flavors. This means that, in derived type that provides a single default operator, that default operator will always be preferred, even if the base type defines a checked operator. This implies that checkness is less important than nearness; in the linked PR, this is example 4. _However_, the rules are also written that, in unchecked contexts, no checked operators are even considered. If nearness was truly more important than checkedness, then if we encounter a type with only `checked` operators, we should stop lookup, and then fail to resolve an operator because that type doesn't define an appropriate default operator. However, that's not the proposal states: instead it only looks for default operators, completely ignoring all checked operators (shown in example 5). This difference has a few members of the LDT uncomfortable, and we think we need more time to mull the rules over. We also acknowledge that, no matter whether checkness or nearness is ultimately more important, the BCL team is going to need to have careful guidance on breaking changes with introducing new checked versions of operators, as a new checked operator will always be considered better for a checked operation than all default operators on a given type. This is also another source of tension around our concern here: if nearness is more important than checkedness, then any subtype that introduces a new checked operator must introduce default operators as well, so that they don't break any users of the operators from a base type. The same is true in reverse for checkness being better than nearness: if checkness is more important, than any subtype that introduces a new default operator must also introduce a checked version of that operator, to ensure that the base type's checked operator is not erroneously preferred over the subtype's. #### Conclusion No conclusion was reached today. We need to think more about this scenario. ================================================ FILE: meetings/2022/LDM-2022-02-16.md ================================================ # C# Language Design Meeting for February 16th, 2022 ## Agenda 1. [Open questions in `field`](#open-questions-in-field) 2. [Triage](#triage) 1. [User-defined positional patterns](#user-defined-positional-patterns) 2. [Delegate type arguments improvements](#delegate-type-arguments-improvements) 3. [Practical existential types for interfaces](#practical-existential-types-for-interfaces) 4. [Static abstract interfaces and static classes](#static-abstract-interfaces-and-static-classes) ## Quote of the Day - "It's not as if we don't have guns lying around that you can point at your foot in the rest of the language" ## Discussion ### Open questions in `field` https://github.com/dotnet/csharplang/issues/5703 https://github.com/dotnet/csharplang/issues/140 Our open question today is whether `field` can be shadowed in a nested scope in a semi-auto property. For prior art, we looked at two scenarios: 1. In C# today, fields can be shadowed by parameters or locals. This is done intentionally, as it is a common pattern to have a local or parameter with the same name, and then assign to the type's field by using `this.` as a prefix. We don't have that same intentionality of purpose here: `field` does represent an anonymous field in the type, but that field isn't actually named `field`, and there's very little logical reason to name a local `field` to intentionally shadow the implicit backing field keyword. 2. `value` is an invalid local name in property setters, as there is an implicit parameter named `value` and parameters cannot be shadowed by locals. However, the reasoning doesn't appear to hold water for `field`, as `field` is not parameter. Ultimately, we think that since `field` represents a field in the type, even if anonymously, the shadowing rules of regular fields should apply. This means that the `field` keyword can be shadowed by parameters or locals in a nested scope. #### Conclusion `field` can be shadowed by parameters or locals in a nested scope. ### Triage #### User-defined positional patterns https://github.com/dotnet/csharplang/issues/4131 User-defined patterns, also known as active patterns in F#, are extremely powerful, but when designing them we need to be sure we're not painting ourselves into a design corner for other future pattern enhancements. There are also some interesting questions we will have to answer around exhaustiveness. Regardless of these questions, we see active patterns as one of the last pattern-related things that need to be added to the pattern feature to make it generally "complete", and are excited to look at them after we land the current pattern work. ##### Conclusion Into the backlog, for after we finish the current pattern features. #### Delegate type arguments improvements https://github.com/dotnet/csharplang/issues/5321 We previously looked at this in LDM [here](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-25.md#delegate-type-argument-improvements), but didn't triage it at this time. Our feelings haven't changed, and we want more exploration of the generalized version before more active work can continue. ##### Conclusion Into the backlog. #### Practical existential types for interfaces https://github.com/dotnet/csharplang/issues/5556 There is still some discomfort on the LDM around munging of generic parameter lists, but we like having a real translation proposal for this. We wonder if there is some way that, with the restrictions imposed on existential types in this proposal, we could have the runtime do the translation without previously-expressed concerns about combinatoric generic explosion, since all usages of these interfaces must already be generic. We think this will be a good thing to explore post roles, as we're actively thinking about them now and don't want to have too many huge type-system changes in flight at the same time. ##### Conclusion Into the backlog. #### Static abstract interfaces and static classes https://github.com/dotnet/csharplang/issues/5783 This would be a nice hole to fill, as long as it comes with either an anti-constraint for allowing static classes as type parameters, or simply removing the restriction entirely. ##### Conclusion Into the working set. ================================================ FILE: meetings/2022/LDM-2022-02-23.md ================================================ # C# Language Design Meeting for February 16th, 2022 ## Agenda 1. [Pattern matching over `Span`](#pattern-matching-over-spanchar) 2. [Checked operators](#checked-operators) ## Quote of the Day - "If Memory serves?" ## Discussion ### Pattern matching over `Span` https://github.com/dotnet/csharplang/issues/1881 https://github.com/dotnet/csharplang/blob/main/proposals/pattern-match-span-of-char-on-string.md We started today by going through the proposal for allowing matching `Span` and `ReadonlySpan` instances against `string` literals. This is an Any Time feature being implemented by a community contributor, and has a few open questions left before it will be ready to be merged. Most places in this section that use `Span` are actually referring to both `Span` and `ReadonlySpan`, unless called out otherwise. #### Non-constant matches Matching a `Span` to a `string` is interesting as a pattern feature because it's the first time that we're matching an input to a value that is non-constant. Matched constants can be subjected to a conversion today: for example, `obj is uint and 1` will convert the `1` integer value in the pattern to a `1` unsigned integer value. The result of this conversion, though, is itself a constant value. For `Span`, this isn't the same. `spanValue is "Hello world"` needs to convert `"Hello world"` to a `ReadonlySpan`, the result of which is _not_ a constant value. It could be argued, therefore, that this should not be permitted, and instead the scenario should wait for [active patterns](https://github.com/dotnet/csharplang/issues/1047). However, we think that this scenario is still morally constant, even if not strictly semantically constant. We can additionally emit better code for the scenario than we currently think active patterns could, as it will benefit from our existing optimizations around string comparisons. ##### Conclusion We are ok with allowing a morally constant pattern, even if it's not semantically constant. #### Specification terms This question is around how we should specify the comparison: should we specifically call out `MemoryExtensions` in the specification and require it to be provided by the framework, or should we have some kind of fallback ability? In supported all distributions, `MemoryExtensions` and `Span`/`ReadonlySpan` have been either been directly in the framework together, or have been shipped as part of the same NuGet package. So we don't think there are any places where `Span` would be present, but the `SequenceEquals` method for comparison won't be. Specifying in terms of `MemoryExtensions` is also much easier for the implementation: if we have some kind of fallback ability, then that code has to be implemented and tested, all in service of a scenario that we think is extremely unlikely. ##### Conclusion We will specify the feature in terms of `System.Memory.MemoryExtensions`. #### `null` constants We considered whether `null` should match against an empty `Span`, since `null` is convertible to `Span`, and the result is an empty `Span`. There are some pros and cons to going either direction. In favor of allowing `null` to match, `Span s = null;` works today, so it will be odd if `s is null` would then fail to compile. On the other hand, the subsumption rules start to differ from string, and in a way that's very hard to explain. `s is null or ""` would fail to compile and `""` would say that it was already handled by a previous case: we think this is more confusing than a clear error message stating that `null` cannot be used to match against a `Span`, please use `""` instead. ##### Conclusion We will not allow `Span`s to be matched against `null` constants. #### Type tests We universally agreed that the input type must be known to be `Span`. This is similar to how other constants work, such as this `uint` example: ```cs using System; uint u = 1; Console.WriteLine(u is 1); // true Console.WriteLine(((object)u) is 1); // false Console.WriteLine(((object)u) is uint and 1); // true Console.WriteLine(M1(u)); // false Console.WriteLine(M2(u)); // true bool M1(T t) => t is 1; bool M2(T t) => t is uint and 1; ``` ##### Conclusion Input type must be `Span` to be matched as a `Span`. No implicit type tests will be emitted. #### Subsumption rules Based on our discussion around `null` constants, we think the subsumption rules for `Span` should match the subsumption rules for `string`s, since they are both being matched with `string` constants. We think that there are a couple of open questions for Utf8 strings and list patterns though: 1. Should Utf8 strings allow pattern matching as well? 2. Should `string` patterns contribute to subsumption in list patterns over that same value? For example, should `stringValue is [] or ""` report an unreachable case? ##### Conclusion Use the same subsumption rules as `string`s. ### Checked operators https://github.com/dotnet/csharplang/issues/4665 [Last time](LDM-2022-02-14.md#checked-operators) we discussed checked operators, we made no solid conclusions on the lookup rules. At the core of our concerns with the lookup rules was concern over what principle was the "most important" part of lookup: nearness (how far up the type hierarchy did the compiler have to go to find the operator) or checkness (whether the current context is checked or unchecked). We've had a few different versions of the lookup rules here: * Version 1 was the original proposal for [unary operators](https://github.com/dotnet/csharplang/blob/7dcc4e6903e2d80b52d21bd8901df964cd16a708/proposals/checked-user-defined-operators.md#unary-operator-overload-resolution) and [binary operators](https://github.com/dotnet/csharplang/blob/7dcc4e6903e2d80b52d21bd8901df964cd16a708/proposals/checked-user-defined-operators.md#binary-operator-overload-resolution). This was originally discussed on [February 7th](LDM-2022-02-07.md#overload-resolution). * Version 2 was updated based on that feedback in [this version](https://github.com/dotnet/csharplang/blob/136d05563a881a451b78034196e4a5becfcc28bc/proposals/checked-user-defined-operators.md#unary-operator-overload-resolution). This was discussed on [February 14th](LDM-2022-02-14.md#checked-operators). * Finally, we have a new version of the rules to consider today: > 1. In an unchecked context, `checked` operators are ignored. > 2. In a checked context, when we find a `checked` operator in `T0` we take that candidate but we ignore its regular counterpart (ie. in `T0`, same signature, but no `checked`) if any. > All the other rules (resolution, betterness) can remain as they are today. This final version is an attempt to codify that, on its own, a single unmarked operator means "all operations", but when paired with a marked `checked` operator, it's actually an `unchecked` operator. This is an idea that had been originally put into the `checked` operator proposal, but we had tried to step away from it and maintain existing operators as applicable to both `checked` and `unchecked` scenarios. As we tried to work through the effects on lookup, however, this seemed increasingly complicated and hard to reason about, so we are revisiting the idea. We further considered whether we should require that, if a type defines a `checked` operator, it should also be required to define an unchecked version. There is the potential that this requirement could be onerous for some types that don't have a well-defined `unchecked` version, but those types have tools to express that: either continue to use a single unmarked operator, meaning it will be used for all `checked` and `unchecked` operations, or explicitly mark the `unchecked` version with `Obsolete` and throw from the body. We think that this nicely sets us up for success here: introducing a `checked` operator at level requires that we go from a default operator world, which just has a single unmarked operator, into a world that knows about `checked` and `unchecked` contexts. This allows us to design the lookup rules coherently to favor nearness, then checkedness, then betterness, as we wanted to. We still need to finalize the exact wording of the rules, but we liked the general principle of option 3. The compiler and the rules will still need to handle cases when a user manually defines a single `checked` operator without an `unchecked` version, either in IL or by making a method with the right names and attributes to appear to be an operator, but this is analogous to other operator scenarios the compiler needs to deal with today. Finally, we discussed whether we should allow and/or require `unchecked` for unchecked counterpart operator. However, we did not have enough time to dig much into this, beyond realizing the room is evenly split among the three camps (required vs allowed vs disallowed), so a future meeting will need to resolve this. #### Conclusion We will require that declaring a `checked` operator means there must be a counterpart unchecked operator, and a future meeting will decide whether this counterpart operator can or must be marked with `unchecked` or not. ================================================ FILE: meetings/2022/LDM-2022-02-28.md ================================================ # C# Language Design Meeting for February 28th, 2022 ## Agenda 1. [Ref fields](#ref-fields) 1. [Encoding strategy](#encoding-strategy) 2. [Keywords vs Attributes](#keywords-vs-attributes) 3. [Breaking existing lifetime rules](#breaking-existing-lifetime-rules) ## Quote of the Day - "I want to see your all reactions to how crazy I sound" ## Discussion ### Ref fields https://github.com/dotnet/csharplang/blob/3d557a45ab03597aca4ccc24ba6eaacbcc930431/proposals/low-level-struct-improvements.md#open-issues Today, we looked at open questions in the `ref field`s proposal, specifically around tradeoffs between enforcement and the approach to the feature. #### Encoding strategy https://github.com/dotnet/csharplang/blob/3d557a45ab03597aca4ccc24ba6eaacbcc930431/proposals/low-level-struct-improvements.md#to-use-modreqs-or-not Our general question here is whether to use `modreq`s as the encoding mechanism for the various semantic elements of the feature, or if we should instead use attributes. Using attributes as the encoding mechanism would allow APIs to evolve their surface areas without a binary breaking change later: for example, if a library forgot to add `DoesNotEscape` (or whatever the final syntax for such a thing ends up being), they could add that attribute later without it being a binary-break to do so. We therefore need to decide whether we consider such elements an integral part of the method signature, and whether this compat scenario is something we feel is likely. After a bit of discussion, we concluded that, except for potentially as part of the initial attribution of existing BCL methods, the idea that something does or does not escape is integral to the design of a method, and we don't find it likely that there will be a general need to change the ref struct capturing of a method after it shipped. Meanwhile, `modreq` gives us much stronger enforcement of the safety rules, and the safety rules are integral to avoiding all sorts of memory safety issues. It is better in this area for a binary break to occur than for an accidental RCE to be opened because memory escaped where it wasn't expected to on binary upgrade. ##### Conclusion We will use modreqs for encoding the safety rules. #### Keywords vs Attributes https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#keywords-vs-attributes Now that we've decided on using `modreq`s as the encoding mechanism, we can look at how they should be applied by a user. This is going to be a complex area for this feature: there are entire languages (ie Rust, M#, and others) that ingrain lifetime management into the language itself, from the very beginning. The initial proposal used attributes as the syntax, and they do have some advantages: they can be sufficiently wordy without seeming unnatural, and we don't have to invent a syntax for a niche of the language. However, given that we have now decided to use `modreq`s as the encoding mechanism for the feature, using attributes seems the wrong approach. Attributes never affect the encoded signature of a method (ie, what is emitted as part of a `call` or `callvirt` instruction), while `modreq`s do. This would set an unfortunate precedent and complicate both the compiler and user understanding of what is actually part of a signature. We think this is something that should be avoided. ##### Conclusion We will explore the dedicated syntax space. We're open to ideas here: anything from specific keywords to more complex lifetime tracking syntax are things we will look at. #### Breaking existing lifetime rules https://github.com/dotnet/csharplang/blob/3d557a45ab03597aca4ccc24ba6eaacbcc930431/proposals/low-level-struct-improvements.md#take-the-breaking-change Our existing lifetime rules are both complex and somewhat inconsistent, which leads to confusion for all levels of users. We'd like to fix it, but it will require taking a breaking change. Our advantage here is that the users who are authoring these APIs are mostly the BCL, or avid followers of the language and improvements. Informal surveys of these populations have indicated receptiveness to taking this breaking change to simplify the surface area. It may also be possible to structure the breaking changes such that the actual C# written gets simpler, but the metadata is more compatible with the existing format today (ie, not changing the meaning of existing metadata, but changing what metadata existing source will compile down to). This would make the compiler implementation more complex, but it might be a good compromise to allow existing code to continue to exist safely. ##### Conclusion We are willing to consider breaks to the area to simplify the existing rules. ================================================ FILE: meetings/2022/LDM-2022-03-02.md ================================================ # C# Language Design Meeting for March 2nd, 2022 ## Agenda 1. [Open questions in `field`](#open-questions-in-field) 1. [Initializers](#initializers) 2. [Property assignment in structs](#property-assignment-in-structs) ## Quote of the Day - "Wouldn't infinite productivity be subject to a Poisson distribution?" "I gave up on my math degree years ago" ## Discussion ### Open questions in `field` https://github.com/dotnet/csharplang/issues/140 https://github.com/dotnet/csharplang/blob/6501263ca17173262ecb4449c4977b28a28cc375/proposals/semi-auto-properties.md#open-ldm-questions We looked at a few open questions in `field` today with fresh eyes, now that we made adjustments to [how definite assignment works in structs](LDM-2022-02-14.md#definite-assignment-in-structs). In particular, we want to make sure that, given the adjustments made to when structs are definitely assigned, our previous decisions make sense. [Previously](LDM-2022-01-12.md#initializers-for-semi-auto-properties) we'd stated: > We have the following formalized rule for when initializers are permitted: > * Initializers are permitted when a property has a backing field that will be emitted and the property either does not have a setter, or its setter is auto-implemented. > > We have the following rule for when properties can be assigned in the constructor of a type: > * Properties can be assigned in the constructor of a type if that property has a setter, or is a get-only property whose backing field will be emitted. Both of these rules are being re-examined today. #### Initializers In addition to re-examining our initializer rule in light of struct definite assignment, we also wanted to consider community feedback, which we received a small amount of. In particular, the feedback was concerned that we were ruling out defaults for INPC or other similar scenarios. After some thought, we agreed with this feedback, and think that with how we've structured the definite assignment rule, we can come up with a simple rule that is just as easy to explain, if not necessarily as obvious to a reader: * Initializers are permitted for any property with a backing field. The initializer always assigns directly to the backing field. We are concerned that this rule will be something learned, rather than something taught. We can potentially improve compiler tooling for error initializer scenarios, but, in records in particular, there is some potential concern that this will be stumbled onto by accident, rather than intentionally. For example, there could be confusion about what this code does: ```cs new Rec(-1); record struct Rec(int Item) { public int Item { get; set => value < 0 ? throw new Exception() : field = value; } = Item; } ``` This will assign `Item` directly to the backing field and not run the initializer, and no exception will be thrown. Despite this, we're hesitantly ok with moving forward with the design. ##### Conclusion The following rule is accepted: * Initializers are permitted for any property with a backing field. The initializer always assigns directly to the backing field. #### Property assignment in structs With the updated rules for definite assignment, the change is relatively straight forward. A semi-auto-property will be treated as a regular auto property for the purposes of calculating `default` backing field initialization if its setter/initer is automatically implemented, or if it does not have a setter or initer. ##### Conclusion As above. ================================================ FILE: meetings/2022/LDM-2022-03-09.md ================================================ # C# Language Design Meeting for March 9th, 2022 ## Agenda 1. [Ambiguity of `..` in collection expressions](#ambiguity-of--in-collection-expressions) 2. [`main` attributes](#main-attributes) 3. [`nameof(param)`](#nameofparam) ## Quote(s) of the Day - "I think the entire language is biased to developers general nature to be introverted. Sure, we can have variables as int j and int p, but to be really inclusive we need ent j and ent p." - "'Enthusiastic Markdown File' is the name of my new punk band" - "Just to clarify, you were talking about heinous rule number 3?" - "The language of 1000 keywords" - "I don't know why I'm passing methane around" "You're passing gas" ## Discussion ### Ambiguity of `..` in collection expressions https://github.com/dotnet/csharplang/issues/5904 https://github.com/dotnet/csharplang/issues/3435 https://github.com/dotnet/csharplang/issues/5354 We got some feedback on our proposal for collection expressions that we wanted to consider in relation to list patterns, before they ship in C# 11 and we can't take back our decisions. Specifically, there is concern that using `..` for slicing in list patterns is setting us up for ambiguities in the future with collection literals, where we want `..` to mean add all elements of a sublist to the list being created. This would make the syntax potentially ambiguous when constructing a list of ranges, as `var x = [1.., .. b, 3..4]` could mean add all the elements of `b` to `x`, or it could mean add the range `.. b` to `x`. Our original motivation for using `..` as the slice pattern was for a different form of consistency: ranges are used to slice collections, so the `..` pattern implicitly slices, where the access form would be `var x = array[1..3];`. So no matter what we do here, there will be some form of inconsistency: either slicing has the inconsistency, or we'll need to parenthesize ranges in collection literals to make a collection of ranges. We have a few different proposals on what do here: 1. Do nothing. 2. Use a different syntax. We looked through a few suggestions and other language's spread/splat operators: 1. `...` - used by JS, TS, and Dart, among others. 2. `*` - used by Ruby and Python 3. `#` - Suggested on csharplang 3. Create a "spread/splat" operator context, as a separable feature from list patterns. We explored the other proposed syntaxes, but nothing immediately stood out as obvious and the right choice. We like the existing consistency of the `..` operator between slicing expressions and slicing patterns, and none of the other syntaxes would keep that. We also did a bit of exploration on proposal 3, and after some discussion we think that there's a potential feature there. We might be able to say that `..` on `Index` and `int` expressions creates a `Range`, and for any other type it performs a spread/splat operation, when in a spread/splat context. We didn't get too far into details around what constitutes such a context, but far enough that we think our future selves will be able to define such contexts. #### Conclusion We will explore option 3 when we get more in-depth on collection literals, and will keep `..` as the syntax for slice patterns. ### `main` attributes https://github.com/dotnet/csharplang/issues/5045 https://github.com/dotnet/csharplang/pull/5817 We looked at open questions in `main` attributes, starting with where to allow `main` and what that specifier should apply to. Every option for allowing `main` in a different file has a set of downsides associated with it: 1. If we allow `main` in other files to target any entry point, that means that we introduce a new project file switch that affects the language, as the project file can set what the entry point is. This means that the attributes applied to a specific `Main` will change based on the project file, which we immediately dislike. 2. If we allow `main` in other files to only target top-level statements, then top-level statements suddenly become not just a stylistic choice, but one that affects what other features of the language can be used. This will make an inconsistent experience for users of generators that want to use the feature. We further examined the motivating use case for this feature, `STAThread` in WinForms. After some review, we're not convinced of this as a motivation. The `Main` method in a WinForms application is basically never touched: in VB, it's even hidden from the user. It seems to us that a source generator could simply generate the entire `Main` method for the scenario, rather than just adding `STAThread` as an attribute to it. While we think that a `main` specifier in the same file might find some use in the future, we don't think we currently have a scenario that supports moving it forward. #### Conclusion Allowing `main` in other files is removed from the proposal, and we will move the proposal to the backlog for consideraton when we have a better motivating scenario for it. ### `nameof(param)` https://github.com/dotnet/csharplang/issues/373 This proposal introduces a potential breaking change that we need to consider: by bringing parameters into scope, we potentially cause a dotted expression to bind to the parameter, instead of whatever it binds to now. This, in turn, could cause existing code to no longer compile. We think it's a good change overall though: we generally wish we had designed `nameof` this way in the beginning, and we think the likelihood of the break is low. The most likely thing for a parameter to collide with is a field of the same name, and this will most often be of the same type, so existing code will continue to compile, even if it refers to a member through a different path now. We do want to get it out in preview soon, so that we can assess the extent of people broken by this change, but we expect it to be very low. #### Conclusion We'll accept the breaking change, and get it into preview soon to ensure that we can understand how many users it might affect. ================================================ FILE: meetings/2022/LDM-2022-03-14.md ================================================ # C# Language Design Meeting for March 14th, 2022 ## Agenda 1. [file private visibility](#file-private-visibility) ## Quote of the Day - "They've removed stuff in my Teams" ## Discussion ### file private visibility https://github.com/dotnet/csharplang/issues/5529 Today we looked an issue that has been requested by the community before, but now has new significance with source generators. In particular, the framework libraries are now starting to ship more functionality in source generators. These generators, such as the regex generator, are thought of as part of the API of the framework, but are defined as part of the user's library. This is concerning from a public API perspective, as internal implementation details of these generators are now much easier for users to access and take a dependency on. There's no existing scoping that can reliably separate generator-written code from user-written code, which then becomes an issue if (when) the framework will want to change internal implementation details of their generators. There is an additional problem that generators need to come up with deterministic-but-unique names, to ensure that they don't collide with other generators or with themselves, running from two different assemblies where assembly A gives B IVT. In general, we agree with the first concern, and would like a mechanism to solve it. The latter concern, duplicated names within the same assembly, is where we spent most our time debating today. Having the compiler do name mangling is slightly concerning as it impacts the user experience in several ways: the IDE experience, the debugger experience, and the expression evaluator (EE) experience. These are all potentially solveable problems, but will take some design work to ensure it works correctly and seemlessly. After some debate, we feel that unless we were to allow duplicated names, this feature would feel incomplete. If a thing is file private, then allowing it to be visible in some other contexts feels like we didn't actually solve the problem. We also thought about generalizing this feature. The proposal today specifically only applies to top-level types, and while they are the primary motivation currently, we don't think they should be the only one. More accessibilities has been a user request for a long time, applying to more things than just top-level types. Member fields or methods, for example, or nested types. It seems unfortunate to design specifically for just one small use case and not consider how it could be extended to other components in the future. Finally, we talked a bit about the syntax. We're not sold on just using `private` here: `private` on nested types already means something, and just using `private` on the top level type would be confusing. It would also interfere with extending a file private accessibility to other scopes. We could simply introduce `file private` as a new accessibility, or we could look at other combinations like `private internal`. #### Conclusion We've settled our first big pivot point for this feature, that file private names should be able to be duplicated in multiple files in the same assembly. We will need to take the feedback from LDM today back to design and come back to a future LDM for the next elements. ================================================ FILE: meetings/2022/LDM-2022-03-21.md ================================================ # C# Language Design Meeting for March 21st, 2022 ## Agenda 1. [file private visibility](#file-private-visibility) 2. [Open question in semi-auto properties](#open-question-in-semi-auto-properties) 3. [Open question in required members](#open-question-in-required-members) ## Quote of the Day - "If we pick one syntax, we can have a quick meeting" *Collective laughter* ## Discussion ### file private visibility https://github.com/dotnet/csharplang/issues/5529 Our discussions today were on both syntax, and the semantics of what differing syntaxes mean. We see problems with both presented options of `file private` and `private`: * For `file private`, what does that mean when applied to member of a type? Is that member private to the member and _also_ private to the file? What does the `private` part of `file private` mean when applied to a top-level type? * For `private`, this is inconsistent with how `private` can already be applied to types in nested contexts: what if we wanted to enable nested file private types in the future? To address these issues, we thought about a variation on `file private`: `file `. This would add `file` as a modifier on all current accessibilities, restricting their scope to be the current file. So `file public` would allow access to everything in the current file, while `file private` would only allow access to things in the containing member (and would not be allowed to apply to a top-level type, as just `private` isn't allowed to do this). However, there are a lot of interesting side-effects from things like `public` vs `private` accessibility: it has impacts on reflection, dynamic, serialization, and more. We're somewhat concerned about these potential effects, and want to be very careful about allowing these things for types and members that ostensibly only accessible inside a single file. We therefore want to take a look at what the minimal solution to address the motivation without blocking off future development work would look like: perhaps just `file`, which would inherit the default accessibility of the location, with no other accessibility specifiers allowed, but this will be looked at in a smaller group and revisited soon. #### Conclusion No decisions today. We'll rethink with a `file` keyword approach see what the downsides are. ### Open question in semi-auto properties https://github.com/dotnet/csharplang/issues/5923 Our model is that `field` reflects an actual field of the type, and rules should flow out from that. Since in this case a `field` is a static field, and static lambdas can access static fields, a static lambda can access `field`, even if it doing so is what causes the field to be brought into existence. #### Conclusion Allowed. ### Open question in required members https://github.com/dotnet/csharplang/issues/3630 Our final question today is on whether we should issue warnings for this scenario, where a required member is being initialized to a value: ```cs class C { public required int Field = 1; public required int Prop { get; set; } = 1; } ``` After some consideration, we think that there could be potential scenarios if a type exposed multiple constructors, and several of them were marked `SetsRequiredMembers`. These constructors could be sharing this initializer, but any remaining constructors will require that consumers set a value. An analyzer seems appropriate if a user wants to forbid redundant assignments here. #### Conclusion No warning. ================================================ FILE: meetings/2022/LDM-2022-03-23.md ================================================ # C# Language Design Meeting for March 23rd, 2022 ## Agenda 1. [Open questions in required members](#open-questions-in-required-members) 1. [Emitting `SetsRequiredMembers` for record copy constructors](#emitting-setsrequiredmembers-for-record-copy-constructors) 2. [Should `SetsRequiredMembers` suppress errors?](#should-setsrequiredmembers-suppress-errors) 3. [Unsettable members](#unsettable-members) 4. [Ref returning properties](#ref-returning-properties) 5. [Obsolete members](#obsolete-members) ## Quote of the Day - "We can and do allow users to shoot their feet off. However, lets not give them something capable of doing nothing else" ## Discussion ### Open questions in required members https://github.com/dotnet/csharplang/issues/3630 https://github.com/dotnet/csharplang/issues/5945 #### Emitting `SetsRequiredMembers` for record copy constructors We're not ok with option 1 here: these constructors can be accessed directly by consumers within the record type or any derived type of the record. We also think that option 2 changes too much metadata to be palatable: every existing record would change on recompile. While incorrect compilation order could result in a `SetsRequiredMembers` being missed from a derived record, we think that this case is sufficiently in the corner that it's not necessary to try and address. ##### Conclusion Option 3, `SetsRequiredMembers` will be added to record copy constructors when the record or any base type has required members. #### Should `SetsRequiredMembers` suppress errors? This is a corner case, as the scenario cannot arise from C# code, but it could potentially occur if another language were to extend a type with required members and either override (without applying the correct attributes) or hide those members. This would cause the required member list to not be understood by the language, and there is danger in allowing something we don't understand to proceed. However, in those cases, the external language user did decide to explicitly add the `SetsRequiredMembers` attribute: this is a signal we can understand without context, so it seems fine to ignore errors around required members if the attribute is found. ##### Conclusion `SetsRequiredMembers` will suppress the error. #### Unsettable members We've previously said constructors that require members must be at least as accessible as the members, to ensure that constructors don't become uncallable. This is just an extension of that scenario. ##### Conclusion We will require that required fields cannot be readonly, and that required properties are settable from every constructor (meaning that they have a setter/initer at least as accessible as every constructor that requires them to be set). #### Ref returning properties There's an interesting debate here similar to the placement of `readonly` in comparison to the ref: does `required` here require setting of the ref itself (not possible with ref properties, might be possible with ref field?), or does it require setting the _value_ pointed at by the ref? If the former, that would suggest `required ref` to be in line with `readonly` members, and would necessitate a syntax for initializing a ref in an object initializer, which doesn't exist today. If the latter, it would suggest `ref required`, to be in line with `ref readonly`, but it's unclear if this is a scenario to be concerned with. ##### Conclusion Given lack of motivation, this is disallowed entirely (on both ref properties and, when added, ref fields). We can revisit in the future if we have motivating use cases. #### Obsolete members Finally, we considered `Obsolete` required members. We're not fans of libraries forcing their users into a warning, either by accident (adding `Obsolete` to a member while forgetting to remove `required`) or intentionally. However, we don't feel this is worth an error: it's technically understandable, we just don't know why someone would intentionally do it. This category of issue is best served by warnings, so we think that's the appropriate strategy. ##### Conclusion Option 3, with a warning. Warnings will be suppressed for `Obsolete` constructors and for types that are obsolete. ================================================ FILE: meetings/2022/LDM-2022-03-28.md ================================================ # C# Language Design Meeting for March 28th, 2022 ## Agenda 1. [Variable declarations under disjunctive patterns](#variable-declarations-under-disjunctive-patterns) 2. [Type hole in static abstracts](#type-hole-in-static-abstracts) 3. [Self types](#self-types) ## Quote of the Day - "That feels like a baby we might throw out with the bathwater" ## Discussion ### Variable declarations under disjunctive patterns https://github.com/dotnet/csharplang/issues/4018 https://github.com/dotnet/csharplang/blob/b6f901132d4ee299157bc9b1745eb578f30d29ef/proposals/pattern-variables.md Last time we discussed this issue, we felt that there were potential dragons in two directions: 1. Should we redeclare existing variables, or have a syntax for assigning into them? 2. Should we allow assignment/declaration into all variables in scope? This update to the proposal attempts to address the latter concern: the former discussion has not yet been had. The updated version of the proposal suggests using definite assignment to determine when a variable can be assigned into: if the variable has a flow state of "not definitely assigned", it can be redeclared in a pattern, and defines definite assignment rules for inside patterns to allow the rules to generalize within patterns and without. There are still some concerns that this is too wide of a scope. Our motivations in this proposal are around allowing sharing blocks of code: allowing `case` clauses to enter a specific body in a `switch` statement with the appropriate information extracted, for example, or allowing multiple conditions to be put on either side of an `||` as part of the condition of an `if` statement. The goal isn't to allow general redeclaration: it's to allow sharing of code. Redeclaring or reusing between unrelated conditions concerns us more: should consecutive `if` statements be able to reuse that name and stomp on that location? Because it refers to a location, a previous `if` body could have passed a `ref` to another area, which could then observe the write to the local. Within a single related construct, however, we think using definite assignment as the mechanism for enforcement is a good solution. While it's not a perfect "definitely not assigned", it limits the redeclaration points and it's much harder to observe state being assigned (not impossible, but much harder, particularly unintentionally). We still need to consider ways to address the first concern, but that exploration will need to be the next meeting. #### Conclusion We would like to scope down the allowed location for redeclaration/assignment to: 1. Within `case` labels for the same switch section. 2. Within a single expression. 3. Within related statements: an `if`/`else if`, for example, but not in two unconnected `if` statements. ### Type hole in static abstracts https://github.com/dotnet/csharplang/issues/5955 A variation of this issue is what stopped the original exploration of static abstracts from proceeding a decade ago: we addressed the wider hole with the existing [restriction](../../proposals/static-abstracts-in-interfaces.md#interface-constraints-with-static-abstract-members), but the hole is still possible to exploit. We therefore are ok with this further restriction, but think that when we add support for static _virtual_ members, we should work with the runtime team to make sure that, if all static virtual members of an interface have a body, it should be usable as a type argument. This would avoid the breaking change from adding a static virtual DIM to an interface, which despite not requiring consumers to update implementing code, would potentially break usage as a type argument. #### Conclusion Restriction is accepted. ### Self types https://github.com/dotnet/csharplang/issues/5413#issuecomment-1079584415 After considering Mads' feedback on self types, we find ourselves agreeing with it. It would be very concerning that we would introduce a version of self types now, marginally improving the usability of generic math but potentially breaking our ability to evolve the type system further. #### Conclusion Self types in this form are rejected. ================================================ FILE: meetings/2022/LDM-2022-03-30.md ================================================ # C# Language Design Meeting for March 30th, 2022 ## Agenda 1. [Definite assignment in struct constructors calling `: this()`](#definite-assignment-in-struct-constructors-calling--this) 2. [`file private` accessibility](#file-private-accessibility) ## Quote of the Day - "Maybe we should renumber that list so we're not confused? I know we're all programmers, but 0-based is confusing here." "We're all programmers, but VB programmers at heart." ## Discussion ### Definite assignment in struct constructors calling `: this()` https://github.com/dotnet/roslyn/issues/58790 https://github.com/dotnet/csharplang/issues/5552 For solving this issue, we generated the following ideas: 1. No change to the behavior, and document it. We don't really think this is tenable, but it is technically an option. 2. Report an error calling `: this()` when field initializers are present and `this()` is a default parameterless constructor. 3. No error; zero fields after running initializers (silent breaking change to existing code). 4. No error, zero fields before running initializers. 5. Option #4 for now (C# 10), then #2 later. 6. Option #4 for now (C# 10), add a warning wave later on uses of `: this()` or `new S()` when there is no user-defined parameterless constructor. Several LDM members immediately felt that 4 was the correct option, and did what they were expecting the code to do. However, it does carry with it the potential for confusion around what calling the parameterless constructor does: for a struct with no parameterless constructor with field initializers, the behavior of `: this()` and of `new S()` will appear to differ. We may specify that field initializers run in a different order, but the perception will likely be different. Option 2 would be a consistent way of correcting this perception issue: either make the behavior between all references to a parameterless constructor identical, or forbid calling a parameterless constructor where behavior would be confusing. However, while this option is most in line with C# of the past, we are already planning on making big changes to struct initialization in C# 11, and this behavior would have the unfortunate behavior of punishing any user that adds a field initializer to existing structs that already use `: this()` in places. We're also concerned about a warning wave here, as there are plenty of uses of `: this()` as a struct initializer that are perfectly fine today that would become warnings if we took a hard line stance here. It might be better to approach such a ruling a code style thing: the IDE can gray out the `: this()` initializer in C# 11, and fixers can remove dead code as they do today. #### Conclusion We will adopt option 4 for C# 10, and look into code style analyzers and IDE experiences to remove potentially confusing and redundant use of `: this()` in C# 11. ### `file private` accessibility https://github.com/dotnet/csharplang/issues/5969 https://github.com/dotnet/csharplang/issues/5529 We looked at another turn of the crank in the proposal around `file private`. The proposal today goes for a very integrated approach, adding a new accessibility modifier and rationalizing its existence with other accessibilities, paring the accessibilities that can be combined with `file` down to a small set. Through discussion, we arrived at 3 proposal options: 1. #5969 as is. 2. `file` or `fileonly` as a new accessibility. It cannot be combined with other accessibilities, and means only "can be accessed in this file". 3. `file` is allowed to be combined with any existing accessibility. Everything works exactly as it does today, but when `file` is applied, the name is not in scope outside the current file. We think that option 1 has the potential to be complicated. Both 2 and 3 are relatively simple to explain, with 2 being the simpler of them. Option 1 limits and changes existing rules when `file` is applied, which starts to need a decoder ring to understand. #### Conclusion We're equally split between option 2 and 3 at this point. We need to explore these a bit more with a smaller group, clarify the motivating scenarios for the feature, and come back soon for another session. ================================================ FILE: meetings/2022/LDM-2022-04-06.md ================================================ # C# Language Design Meeting for April 6th, 2022 ## Agenda 1. [Unresolved questions for static virtual members](#unresolved-questions-for-static-virtual-members) 2. [Parameter null checking](#parameter-null-checking) ## Quote of the Day - "You need a cat to sit on your space bar while you hit a reaction button!" ## Discussion ### Unresolved questions for static virtual members https://github.com/dotnet/csharplang/blob/main/proposals/static-abstracts-in-interfaces.md#unresolved-questions #### Equality operators and conversions Prior to this feature we didn't allow equality operators and conversions (implicit or explicit) in interfaces. The main reason for not allowing equality operators in an interface is that you can't pair them with an overload of `Object.Equals` and `Object.GetHashCode`. For conversions the reason is that there are already language-level conversions to and from interfaces, and we try to prevent user-defined conversions where the language already has them. Why should we allow these as *virtual* static members when we don't allow them as *non-virtual* static members? Arguably the virtual declarations are very different: They do not really say that the interface *itself* has these operators and conversions, but rather that an implementing type would. You cannot call these operators and conversions on the interface itself, but only on a type parameter `T` that is *constrained* by the interface. So they don't really run afoul of the situations that motivate the restrictions on the non-virtual operators and conversions. Static virtual equality operators and conversions are not an esoteric scenario - we eagerly use both in the new generic math interfaces, and usability would be diminished if we didn't allow them. ##### Conclusion Stick to the plan. It's ok to have equality operators and conversions as static virtual members, even while interfaces don't allow them as non-virtual members. #### Confirm placement of qualifier for explicit implementation For explicit implementations of operators we currently put the `IMyInterface.` prefix before the `operator` keyword. Are we certain that this is the right place? It is in fact one of the only places that consistently *exists* across all operator kinds, including conversions. Not all operators have an operator symbol for instance. ##### Conclusion We like it where it is. #### When does a type parameter count as "the type itself" in an operator declaration? Up until now, operator declarations have required the containing type to occur as at least one of the operand types. In static virtual operators we relax that so that a type parameter gets to count as "the type itself", but we have been imprecise on what this means in the past. What should the exact requirement be? In order for a type parameter `T` to count as "the enclosing type" the following are the strictest requirements we can express that still satisfy the scenarios: - `T` is a direct type parameter on the interface in which the operator declaration occurs, and - `T` is *directly* constrained by what the spec calls the "instance type" - i.e. the surrounding interface with its own type parameters used as type arguments. Without self-types in the language we can't express a requirement that `T` is a self-type. On the other end we *could* probably choose to make the rules somewhat *loser* and still make lookup work. ##### Conclusion We keep the strict rules outlined above. The rules on operators today are also quite strict, even though they could probably be loosened without breaking lookup, so this feels in line with the existing language. Furthermore we don't know of scenarios that would benefit from a looser rule, but if we find them we can always relax it later. #### Query expressions over types Since query expressions are spec'ed as a syntactic rewrite, C# actually lets you use a *type* as the query source, as long as it has static members for the query operators you use! In other words, if the *syntax* fits, we allow it! Previously that could never apply to a type parameter, because you couldn't dot off of those, but now that we allow that, should we be true to the syntactic rewrite and allow a type parameter there? ##### Conclusion We think this behavior was not intentional or important in the original LINQ, and we don't want to do the work to support it on type parameters. If there are scenarios out there we will hear about them, and can choose to embrace this later. We don't believe there are other places in the language where this situation can occur, but will check. #### Resolution rules for conversion operators We need to adjust the operator resolution rules to look for operator declarations on type parameters constrained by interfaces that have static virtual operator declarations. In the current implementation there is a stopgap rule that looks for operators in interface constraints essentially *only* when there is not also a class constraint. As soon as there is a class constraint, it hides the interface operators regardless of whether the class itself implements the operators. More consistent with the behavior of member lookup would be that when there are no applicable candidates from the effective base class (essentially the class constraint) we just try again with the effective interfaces (essentially the interface constraints). ##### Conclusion Proposal adopted. ### Parameter null checking https://github.com/dotnet/csharplang/blob/main/proposals/param-nullchecking.md This feature has caused some amount of debate in the community after being released in preview. After having talked to a number of constituencies our read is that there *is* a majority who are happy with the feature but others raise a number of objections at different levels. These are the opinions most commonly expressed: 1. There shouldn't be a feature; NRT is good enough and saving one line of code isn't worth it 2. We should go further and support other preconditions through more of a "contracts" feature 3. We should do it by allowing NRTs to incur runtime checking semantics, under a flag or otherwise 4. We should do it with the proposed semantics but with a different syntax We do feel that this is a small syntactic-sugar feature that not everyone will be using - it is only for when you would have thrown `ArgumentNullException` in your code today. We think some of the fervor of the feedback may be driven by a perception that this is going to be more pervasive in code than it is. Here is a brief summary of the reasons we have had for not taking one of the four paths outlined above; not to say that those aren't up for debate: ##### 1. Do nothing We feel that the feature does have value. In most cases it only removes a line of code, but that could be the only line of code you had (e.g. in a constructor body that otherwise chains to another constructor), or the line that prevents you from using an expression body. In general, validation code can be voluminous enough to obscure the main logic, and the bulk of validation code by far is argument null checks. Additionally the feature gives extra expressiveness in certain situations. In a constructor it will check a parameter *before* it is passed to a chained constructor. In an iterator it will be checked eagerly instead of during the first `MoveNext` call. In an async method it will throw synchronously instead of as part of the `await`. In docs having a shorter syntax means we will include it in more examples (it will not obscure the main point of the sample) which means those examples will be "safer". That matters because many people copy from those samples. So in summary the feature does add value. ##### 2. Do contracts Over the years there have been dialects of C# that embraced generalized contracts to express and check preconditions (and sometimes postconditions) either statically, at runtime or both. One important learning from these experiments is that around 9 out of 10 preconditions are null checking. Deal with that and the rest is in the noise. There would be very little extra value from the significant investment and likely syntax overhead of a generalized contracts feature of any kind. In summary, null checks are the sweet spot for a feature. ##### 3. Use nullability annotations to generate checks It was a core design pillar of nullable reference types (NRT) that they did not alter runtime semantics in any way. If your code compiles, it runs the same way no matter the annotations you have and the warnings you get. This tremendously simplifies the reasoning one has to do about the impact of the annotations. Indiscriminately doing runtime checks based on nullability annotations would obviously be a massive breaking change, but we could of course put the behavior behind a flag - like we did NRT itself. Generating runtime checks for every nonnullable parameter type may be overkill because you get checking in too many places, which is a performance hit, but also may not always be what you want. Of course that could be mitigated by an opt-out syntax on parameters, but now you're back to having just as much syntax, and sort of an arms race between opt-in and opt-out mechanisms. ##### 4. Use a different syntax The `!!` syntax was arrived at after much debate. The feature started out with a single `!`, but it was felt that it was to easily confused with the `x!` null-forgiveness operator in expressions. Moreover we might want to apply null checking elsewhere in the future (e.g. on property setters/initers) and even maybe as an expression or statement. The latter would not be possible if we reused `!` for it. Other proposed syntaxes were longer, diminishing the "savings" compared to just doing the check manually, though possibly improving readability. There is a tradeoff between what's the right "weight" of a feature when you're used to it, versus when you're first learning it. If we optimize for "self-explanatory" that may turn into annoyingly heavy-weight with frequent use. ##### Conclusion We debated between the above overall approaches and concluded that we still have a clear lean towards the currently proposed semantics with *some* syntax. A few people were leaning in the direction of option 2 above, but the partial conclusion is that *if* we are going to do a parameter null checking feature it will be through *some* form of annotation of the parameter declaration, as originally proposed. Running out of time we will save for later the decisions of: - What *should* the syntax be for this feature, and - Once we settle that, do we still feel confident that we want the feature at all. ================================================ FILE: meetings/2022/LDM-2022-04-11.md ================================================ # C# Language Design Meeting for April 11th, 2022 ## Agenda 1. [Relax restrictions on braces on raw interpolated strings](#relax-restrictions-on-braces-on-raw-interpolated-strings) 2. [Self-type stopgap attribute](#self-type-stopgap-attribute) ## Quote of the Day - "Maybe we should introduce the £ symbol for interpolated strings" ## Discussion ### Relax restrictions on braces on raw interpolated strings https://github.com/dotnet/csharplang/issues/5960 After some initial usage of raw string literals, we wanted to revisit the reasoning behind limiting the number of `{`'s just before an interpolation hole, as it came up as confusing to explain to several users. The idea is that, before an interpolation hole, users would be able to specify as many `{` characters as they wanted, and the compiler would only treat the inner `{`'s that make up an interpolation hole as the delimiters. This would allow `$""" {{0}} """` to be a string with the content ` {0} `. There are, however, some potential issues with this: 1. In 99% of cases a `{` in an interpolated raw string literal with a single `$` is not permitted, as such a character denotes the start of an interpolation hole. Relaxing this restriction would be odd, as `{ {0}` would be disallowed, but `{{0}` would be allowed. 2. The rules as is are consistent with the rules for the quotes, and changing the rules would require that users understand multiple sets of rules. 3. `$"{{expr}}"` has meaning today, as a string with the content of `{expr}`. If we permitted `$"""{{expr}}"""` to compile, it might end up confusing users who expect it to have a similar meaning to existing interpolated strings. #### Conclusion We will keep the restriction as is. ### Self-type stopgap attribute https://github.com/dotnet/csharplang/issues/6000 As an attempt to prevent simple user errors with our new numeric interfaces, and preserve the potential to use self types in those interfaces in a future where we add support for them, we considered an attribute that could be applied to type parameters to enforce that the type parameter can only be substituted with the containing type, or a type parameter that is also attributed with the same attribute. We quickly started delving into the nitty gritty of the implementation: should we allow it on abstract classes, for example, or be more opinionated with where the parameter so attributed must occur? However, we came to the conclusion as we were doing this that we were creating `GenericMathSelfTypeAttribute`, not a generalized `SelfTypeAttribute`. We don't have the broader understanding of the usage of self-type type parameters of the entire ecosystem to attempt to rush in a generalized protection feature here, and think the space is more appropriate for an analyzer in the BCL to enforce the rules that matter for the interfaces in question. #### Conclusion Rejected. We will look at a BCL analyzer to enforce the rules they want to enforce for these interfaces. ================================================ FILE: meetings/2022/LDM-2022-04-13.md ================================================ # C# Language Design Meeting for April 13th, 2022 ## Agenda - [Parameter null checking](#parameter-null-checking) - [File-scoped types](#file-scoped-types) ## Quote of the Day - "I think we're just vague about where we become soup" ## Discussion ### Parameter null checking https://github.com/dotnet/csharplang/issues/2145 We're following up to [last week's](LDM-2022-04-06.md#parameter-null-checking) discussion on parameter null checking, deciding on our answers to two questions: 1. Should we change the syntax of the feature, and if so, what syntax should we choose? 2. Do we still feel confident in this feature at all? To think about the first question, we put up some syntax ideas. Some of these have been previously discussed, others arose from community discussion on this feature. We also went through and listed some pros and cons for each syntax form. 1. `void M(string s!!);` - The current form 1. This syntax is perceived as very shouty, which we can understand. It does generalize to an expression form later though, which is nice to think about the future. 2. `void M(string! s);` 1. We dislike putting the modifier on the type, as it implies that `!` can be put on types in other locations, and we don't have any idea on how we'd actually enable such a feature in the future. It also negatively affects usage in lambdas, as users would be required to add a type to their lambda declarations to use the feature. 2. We also considered whether `Type!` could be used on a local, which would force a null check whenever something is assigned to that local. There was not much support this extension after consideration, however. 3. `void M(string s!);` 1. This is much less shouty, as it's only one `!` character. However, we're concerned about confusing all the different meanings of `!`; for example, the `!`'s in this example mean different things: `x! => x!;`. 4. `void M(notnull string s);` 1. A keyword can be more clear, but we're not certain this is. It feels redundant, as the parameter is already declared as not null. It also has a different meaning than the `NotNull` attribute. 5. `void M(string s ?? throw);` 1. On the one hand, this feels pretty intuitive, as `?? throw` is already legal expression syntax (provided there's an exception type on the other side). However, we're concerned with the interaction with default parameters, as `string s ?? throw = ""` looks very confusing, and `string s = "" ?? throw` looks like `?? throw` is part of the default parameter value. 6. `void M(string s is not null);` 1. Our concerns here are the same as 5. 7. `void M(checked string s);` 1. We like that `checked` already deals with exceptions, and it feels like the right word to use: when used with arithmetic, it ensures that users aren't violating the bounds of a type, which is very similar to parameter null checking. However, `checked` can already be applied to expressions, or even turned on project-wide, and we're not sure how we'd apply the null-checking feature in these locations: should all parameters be null-checked if the project-wide setting is on, for example? Or should a `!` in a checked block be checked for null? 8. `void M(string s) where s is not null;` 1. This looks like a promising start for a full contracts feature, but not for a feature only doing null checking of parameters. After the discussion leading to the above points, we felt that only two of the options had enough support to continue discussion: option 1, the current form, and option 3, the less shouty version of the current form. We then turned our focus to question 2: do we still feel confident in this feature at all? As part of this, we also looked at the initial feedback we received on this feature by a number of channels: GitHub, social media, MVPs, conference audiences, internal reviewers, and the LDT itself. We do see a majority of positive reactions to the feature, but it isn't the large majority we normally like to see for C# features, particularly ones that are as broadly applicable as this feature will be, and even the LDT is split on the feature itself. While we think the preview provided us with valuable feedback and was worth pursuing, we ultimately decided that, between options 1, 3, or pulling the feature from C# 11 entirely, pulling the feature is the right move. We may revisit at a later date, if we ever want to pursue more generalized contracts, but for C# 11 this feature will removed. #### Conclusion Parameter null checking is removed from C# 11. ### File-scoped types https://github.com/dotnet/csharplang/issues/5529 https://github.com/dotnet/csharplang/issues/6011 This next change in design of this feature pares it back significantly, basically back to the original design, but with a couple of key differences: 1. There is no `private` keyword here, which was a significant stumbling block in understanding of the feature and unintended feature creep. 2. There are more heavy restrictions on how the feature can be used. In particular, this version solves the immediate source-generator issues that the BCL is running into, while allowing us design room if we ever want to expand the feature in the future. We do need to validate that the restrictions around `partial` types and signatures will solve all the issues the BCL source-generators have, but we think it's a forward direction we can make progress on. #### Conclusion The updated proposal can move forward, and we should validate the restrictions on `partial` types will solve the scenarios in the BCL. ================================================ FILE: meetings/2022/LDM-2022-04-18.md ================================================ # C# Language Design Meeting for Apr 18, 2022 ## Agenda - [Issues with Utf8 string literals](#issues-with-utf8-string-literals) - [Ref and ref struct scoping modifiers](#ref-and-ref-struct-scoping-modifiers) ## Quote of the Day - "Basically, they already know '*this step squeaks.*'" ## Discussion ### Issues with Utf8 string literals We [previously](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#open-questions-in-utf-8-string-literals) made a few decisions deliberately to test them out in a prototype. It's been used internally and externally and we've gathered some feedback and have some recommended changes. #### Target typing a regular string literal to Utf8 types https://github.com/dotnet/roslyn/issues/60612 The preview implementation allows regular string literals `"..."` to be target typed to the Utf8 representation types. This has several problems: First of all it doesn't *tell* you that the implicit conversion causes a Utf8 encoding; the different string encoding is silently adopted. More problematically it is a significant breaking change: There are in fact quite a lot of methods with overloads for both `string` and e.g. `ReadOnlySpan`, and existing calls may now either fail or silently pick a different overload. If we want to mitigate, the choice seems to be between always requiring the `u8` suffix for Utf8 literals or coming up with some tie-breaking shenanigans to reduce the risk of a break. ##### Conclusion Let's require the `u8` suffix in order for string literals to be Utf8 encoded. #### Natural type of Utf8 literals https://github.com/dotnet/roslyn/issues/60644 In the preview implementation the natural type of a `"..."u8` literal is `byte[]`. This has the benefit that `byte[]` is already implicitly convertible to the other types we would like to use to represent Utf8 strings, most notably `ReadOnlySpan`. However, this leads to some very subtle costs, where a `byte[]` is allocated even just to be a receiver or argument of a call that doesn't need an array. Also, we often offer fallback `byte[]` overloads for the use of languages that don't know about spans, and with that being the natural type we end up preferring those more expensive overloads in C# as well. The proposal is to instead make `ReadOnlySpan` the natural type of Utf8 literals. We would then have a conversation about whether there should be target typing to some of the other byte sequence types. Since the literals would be allocated as global constants, the lifetime of the resulting `ReadOnlySpan` would not prevent it from being returned or passed around to elsewhere. However, certain contexts, most notably within async functions, do not allow locals of ref struct types, so there would be a usage penalty in those situations, with a `ToArray()` call or similar being required. ##### Conclusion We are ok changing the natural type to `ReadOnlySpan`. See discussion on target typing next. #### Target type Utf8 literals to `byte[]`? Should we alleviate the async scenario, described above, by allowing Utf8 literals to target type to `byte[]`? That way, in an async method you could still have a local variable: ``` c3 byte[] myU8 = "I am an async resilient Utf8 string"u8; ``` On the other hand, even though `ReadOnlySpan` would win in an overload resolution scenario, there is some risk of unwanted implicit allocation. ##### Conclusion Don't target type. It is ok to have to explicitly use `ToArray()` in these scenarios, especially since it makes it clearer what you are doing. #### Should Utf8 literals be null-terminated Today, for string literals, we put a null terminator beyond the last character in memory (and outside the length of the string) in order to handle some interop scenarios where the string is being `fixed` and the call expects null terminated strings. We do not do the same for other string values, so the solution is at best partial, and at worst a source of a false sense of security. However, it is what it is. In the preview implementation we don't null-terminate the data from a Utf8 literal in the same way. However, an inconsistency with string literals here might lead to further bugs or misunderstandings, especially if interop code is copied and converted over from Unicode to Utf8. ##### Conclusion While the current string literal behavior is debatable, we think the least risky approach is to be consistent and adopt the same behavior for Utf8 literals. ### Ref and ref struct scoping modifiers https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#open-issues We looked at two ways in which you might want to be able to modify the default rules around escaping of refs and ref structs. #### "Scoped" refs and ref structs Up until now the default assumptions around escaping of refs and ref structs have been inconsistent. For every combination of incoming and outgoing refs and ref structs on a given method, we have assumed that incoming might escape through the outgoing. *Except* in one case: When a ref struct was returned, incoming `ref`s were not assumed to be captured. This was because there was no safe way to create a ref struct from a `ref` at the time, but we're looking to change that, e.g. with literally a new constructor overload on `Span` taking a `ref T` to be wrapped by the span! Changing the assumptions to remove the exception would of course be a breaking change, but it would also be a highly desirable simplification. We've therefore done an audit of all the places the break would affect the core libraries, and the result is that only two public methods (and a number of private methods) rely on being able to take refs and return ref structs. Those methods are already inherently unsafe and documented as such. However they are also used extensively! We need a way to modify them to for them to continue to be callable in the same contexts (even if it's unsafe!). Such a modifier would also help other APIs out there which might have relied on the current assumptions to avoid a break for their users. Given the general sophistication of developers trading in combination of refs and ref structs, and the availability of such a modifier, we assess that a break would be quite well contained. In general we want to allow methods to "scope" the use of incoming refs and ref structs to signal that they will not be returned. We could use a keyword or an attribute for this purpose, and our previous leaning has been to use a keyword. Today's proposal is to introduce a keyword `scoped` as a modifier on ref and ref struct parameters. ##### Conclusion Bless the breaking change in light of its small perceived impact, and adopt the `scoped` modifier proposal as the mechanism to block "escaping" of incoming refs and ref structs. #### Escaping `this` from structs Even with the above decision, one assumption that still deviates from the default expectation of escaping is around references to the receiver struct. The escape analysis does assume that ref or ref struct returning members do not share references to `this` or to fields within `this`. The proposal therefore is to introduce a second modifier, `unscoped`, to override the default and allow such escaping when desired. There are several concerns with that: 1. Why is the default for `this` different than for parameters? Is this something we could also change with a similarly well-contained break? 2. While we have a clear and present need for `scoped`, this seems to be pure additional expressiveness. Do we have enough motivation to add it at this point? 3. The `unscoped` keyword seems like a distinct misnomer, since it does not seem like the exact opposite of the `scoped` modifier. The things it "unscopes" (`this` and also `out` parameters) are distinct from things the `scoped` modifier "scopes" (ref and ref struct parameters). 4. If we adopt the feature it is unclear if it is "worthy" of a keyword. Its expected use might be rare enough that an attribute is a better choice. Attributes cannot go on local variables, but whereas that might be a useful future scenario for `scoped` it does not seem as useful for `unscoped`. On the other end we would like to allow ref capture where it's not allowed today. ##### Conclusion Consider "unscoped" as a separate feature, which needs more thinking and fleshing out based on the above feedback. If we do want to change the default assumptions we should try to get it done in this same release, but if not, "unscoped" can potentiall wait for a future release. ================================================ FILE: meetings/2022/LDM-2022-04-25.md ================================================ # C# Language Design Meeting for Apr 25th, 2022 ## Agenda - [`ref readonly` method parameters](#ref-readonly-method-parameters) - [Inconsistencies around accessibility checks for interface implementations](#inconsistencies-around-accessibility-checks-for-interface-implementations) ## Quote of the Day - "The group you're talking about just raised their hand" ## Discussion ### `ref readonly` method parameters https://github.com/dotnet/csharplang/issues/6010 In another lowlevel session, we looked at a feature designed to address pain points in the BCL with `in`. For the most part, `in` works quite well. However, there are a few APIs that have been reluctant to adopt `in` because of two main reasons: * Passing an rvalue, which `in` allows, is an anti-pattern and should be disallowed for a subset of APIs. This part could be addressed in an analyzer. * `in` requires that `in` be specified at the callsite for lvalues, not `ref`. For APIs that would want to move, such as `QueryInterface`, the source-breaking change of moving to `in` is undesirable, and blocks adoption of the feature. This is not addressable by an analyzer. We think that we've left ourselves room in the middle between `ref` and `in` for a `ref readonly` feature, where `in` can be thought of as `ref readonly` plus an additional feature of allowing rvalue parameters. As part of this, we re-examined the motivation behind `in` allowing rvalues, and behind not allowing `ref` to be used for `in` parameters. Rvalues were allowed because the similar feature in Midori originally didn't allow rvalues, and that proved painful for devs in that environment. That environment also had an advantage of being entirely new code: migrating from `ref` to `in` or another keyword as a source-break wasn't nearly the pain point it is for the BCL and C#, which has 20-year-old code still in use today. We therefore believe there is space for two changes to ease the migration path: * Allow `in` and `ref` to both be used at the callsite for `in` parameters. `in` will still be preferred, and a warning will be issued when using `ref` for such cases, but this will allow a migration path that doesn't completely break consumer code, and the warning can be disabled for codebases that don't want to update. * Have a language feature to prevent rvalues being passed where they don't belong. We looked at two possible syntaxes for this: `ref readonly`, or using an attribute like `LvalueOnly`, effectively making it an analyzer. After some discussion, we prefer it being `ref readonly`: it's a language feature, not an analyzer, so it should look like one. We will error when an rvalue is passed to a `ref readonly` parameter. Both `ref` and `in` will be usable at the callsite, but use of `ref` will warn, like it will for `in` parameters. #### Conclusion Feature is accepted. The syntax will be `ref readonly`. `ref` will be usable for both `ref readonly` and `in` parameters at the callsite, but a warning will be issues for such cases. Passing an rvalue to a `ref readonly` parameter is an error. ### Inconsistencies around accessibility checks for interface implementations https://github.com/dotnet/roslyn/issues/60885 [Previously](../2021/LDM-2021-05-19.md#protected-interface-methods), we relaxed restrictions around non-public interface methods. Unfortunately, this now leaves us with inconsistencies around internal interface methods: implementations of the interface in another assembly end up implementing such internal methods implicitly, even when they lack visibility. This is particularly bad for virtual, non-abstract members, where an interface could be perfectly legal to implement in another assembly, and then adding an internal virtual method could cause that other assembly type to end up overriding an interface member it did not intend to. In general, we dislike all of the proposed solutions here. Checking to make sure that methods aren't implicitly implementing interface methods doesn't solve the issue when the dependent assembly doesn't recompile. Relaxing the restriction for explicit implementations seems to fly in the face of accessibility in C#. Trying to emulate the exact behavior of class types has a number of holes in it. We think we need to brainstorm more about what to do here. #### Conclusion No action today. We will go back to the drawing board and revisit. ================================================ FILE: meetings/2022/LDM-2022-04-27.md ================================================ # C# Language Design Meeting for Apr 27th, 2022 ## Agenda - [Default parameter values in lambdas](#default-parameter-values-in-lambdas) - [Null-conditional assignment](#null-conditional-assignment) ## Quote of the Day - "I don't like broccoli, don't make me eat it" ## Discussion ### Default parameter values in lambdas https://github.com/dotnet/csharplang/issues/6051 In another iteration on lambdas, we looked at a gap that lambdas cannot expression, but local functions can: default parameter values. There are two schools of thought on this feature, which differ by whether we generate custom delegate types for these lambdas or whether we just use `Action`/`Func` and require runtime introspection of the delegate instance to determine default parameters. The former feature will allow compile-time usage of the default parameter values, while the latter would mean that the main use case would be introspection features, such as reflection or source-generators. This latter proposal is interesting, but we think it's likely the wrong approach. A feature that can only be observed via reflection or source generation doesn't seem like it integrates well with the rest of C#, and if we ever want to change it in the future the breaking change would likely be quite painful. There is, however, one interesting case that was enabled with C# 10: ```cs var m = M; // Infers Action m(); // Error: no value provided for arg0 void M(int i = 1) {} ``` This case doesn't generate a custom delegate type, and compiles today. We think that this isn't great behavior, and that we should change it in C# 11, before `var` for method groups has time to hugely penetrate large codebases. We also thought about how strict we should be on matching default parameter values. For example, should this code be an error: ```cs var x = (int i = 1) => {}; x = (int i = 2) => {}; // Reassigned with a different underlying default. x(); // What would this pass for i? ``` We again have precedent with method groups, but with a slight difference: method groups converted to delegates are one _possible_ way to call such methods. It is conceivable that there is intentionally a different default value for directly calling a method vs calling it via some delegate type, or the method could come from assembly that the user doesn't control. Lambdas don't have this: they can only be converted to a delegate type, and are defined by the user, not by some other library. Thus, we think it's worth a warning for lambda default parameter value mismatches; it's technically well-defined and the compiler understands what it means, but it's very likely the user is doing something wrong. We could consider a warning wave for method groups mismatches as well, but that will have to depend on whether we thing the signal-noise ratio would be high enough. #### Conclusion Feature is accepted. We will generate custom delegate types for these lambdas, and accept the breaking change to do the same for method groups. We will warn when lambda parameter default values differ from a delegate type they are converted to, if the lambda is not being converted to its natural type. ### Null-conditional assignment https://github.com/dotnet/csharplang/issues/6045 We're looking at old, well-upvoted request: allowing a null-conditional on the left side of an assignment. We generally think it's a good feature, even if `a?.b ??= c` has a very different meaning from `a?.b ?? c`. #### Conclusion Proposal accepted, moved into the working set. ================================================ FILE: meetings/2022/LDM-2022-05-02.md ================================================ # C# Language Design Meeting for May 2nd, 2022 ## Agenda - [Effect of `SetsRequiredMembers` on nullable analysis](#effect-of-setsrequiredmembers-on-nullable-analysis) - [`field` questions](#field-questions) - [Partial overrides of virtual properties](#partial-overrides-of-virtual-properties) - [Definite assignment of manually implemented setters](#definite-assignment-of-manually-implemented-setters) ## Quote of the Day - "I think I've used goto in C# more than I've used a virtual auto property" ## Discussion ### Effect of `SetsRequiredMembers` on nullable analysis https://github.com/dotnet/csharplang/issues/3630 https://github.com/dotnet/csharplang/issues/6081 We have an open question on required properties: what should the effect of `SetsRequiredMembers` be on nullable analysis? Should the property be taken as a tool to silence the compiler, or should it restore the nullable warning to the current constructor? A key insight of required as a feature is that is orthogonal to nullable. It can move where a nullable warning is reported, but it does not suppress the warning: assigning a not-null reference type property to null will still produce a warning. If `SetsRequiredMembers` started actually suppressing the nullable warning, that would mean that this orthogonality is lost, and the two features become more entangled that we would otherwise want. While there could be some users confused why we aren't also warning or erroring on nullable or value type members that aren't set in the constructor, we think the right decision is to have `SetsRequiredMembers` move the nullable warning back to the constructor, rather than suppressing it completely. #### Conclusion Nullable warning will be moved back to the constructor when `SetsRequiredMembers` is applied. ### `field` questions https://github.com/dotnet/csharplang/issues/140 #### Partial overrides of virtual properties https://github.com/dotnet/csharplang/issues/6076 https://github.com/dotnet/csharplang/issues/6077 First up in semi-auto properties, we have a couple of questions about the behavior of overriding a virtual property with a semi-auto property. There existing rules in C# that prevent an auto-property from overriding only one accessor of a virtual property, as it's a potential footgun in waiting. There are two possible viewpoints here: 1. This is a restriction that applies to properties with generated fields. This property has a generated field, so the restriction should apply, as the footgun is still there. 2. This restriction doesn't apply to properties with manually-implemented bodies, so it shouldn't apply here either. After some discussion, we're concerned that the footgun the rule exists to prevent is still here for semi-auto properties. We're also concerned about what the meaning of this code is: ```cs class Base { public virtual int Prop { get; set; } } class Derived : Base { public override int Prop { get => field; } public Derived() { Prop = 1; // Does this call the setter from Base? Or does it assign to the backing field in Derived? } } ``` We don't have a good answer for what this code does: by the specification, it should likely assign to the backing field. But either behavior is surprising. We also had a small side conversation on whether we should have done work with auto properties when they were first introduced to allow them to share storage across virtual hierarchies, but there are issues with this. If this could be done across assembly boundaries, it would be exposing that you have an auto-property as a part of the public contract of a type, and that breaks the encapsulation that properties provide. ##### Conclusion Semi-auto properties must override all accessors of a base property. #### Definite assignment of manually implemented setters https://github.com/dotnet/csharplang/issues/6080 In C# 11, we are changing structs to automatically default initialize fields if the struct is read before all fields are assigned, and we need to decide if this behavior should apply to semi-auto properties as well. Our options are: 1. Do not default initialize the struct if a manually-implemented semi-auto property setter is called. This could either be a blanket statement, or we could try to do some cross-method analysis to determine if we need to do the default initialization of the property. 2. Default initialize as if it was a regular property setter. 3. Default initialize as if it was a regular property setter and suppress the warning, as there's nothing the user can do about it. For option 1, we don't think this is the right time to introduce cross-method analysis into C#. It would bring with it all the complexity of cross-method analysis for very little benefit; if we were going to introduce cross-method analysis, there are areas such as nullable analysis that would have the exact same set of problems, but would be much more beneficial. For option 3, we note that the uninitialized struct warning is not turned on by default, and users have to opt into it. For these users, there is indeed nothing that can be done about the warning except providing an explicit property initializer, or not using the feature. Most C# users will never see the warning, and for those that do care, they should be aware of it in this case too. ##### Conclusion Default initialize when calling a manually implemented semi-auto property setter, and issue a warning when doing so, like a regular property setter. ================================================ FILE: meetings/2022/LDM-2022-05-09.md ================================================ # C# Language Design Meeting for May 9th, 2022 ## Agenda 1. [Numeric IntPtr](#numeric-intptr) 2. [Ref readonly parameters](#ref-readonly-parameters) ## Quote of the Day - "Not all Unsafe is created equal" ## Discussion ### Numeric IntPtr https://github.com/dotnet/csharplang/issues/6065 This C# change is mostly a reaction to a change in the BCL's thinking around `IntPtr`, and how generic math will apply to it. During the design of `nint`, we wanted to keep `nint` and `IntPtr` as distinct as possible, as the intent was not to enable general math on `IntPtr`s. However, as the BCL has rationalized their generic math designs, they've decided that the easiest way forward will be to remove this distinction, and enable generic math on `IntPtr`. We therefore want to update C# to similarly remove the distinction, and regard `nint` as simple alias for `IntPtr` (and `nuint` a simple alias for `UIntPtr`) to keep the language and runtime in sync. #### Conclusion Accepted. ### Ref readonly parameters https://github.com/dotnet/csharplang/issues/6010 We wanted to revisit the decision we made [last time](LDM-2022-04-25.md#ref-readonly-method-parameters), as further discussion after the meeting revealed that we had conflated a few features in the decision, and we wanted to break them out and be sure that we are comfortable with feature. In particular, we did not explore the analyzer route enough, as further investigation revealed that, after the `in`/`ref` mismatch was dealt with, nothing prevented using an analyzer to prevent rvalues being passed to `in` parameters. There are 3 main components, therefore, that this proposal seeks to address: 1. There is no way to move an existing API from `ref` to `in` without a source-breaking change. We addressed this issue by allowing `ref` to be used for `in` parameters, and did not discuss changing this today. 2. At the declaration site, there is no way to indicate that being passed an rvalue from the call site is likely an error. This was our main discussion today. 3. At the call site, lack of required `in` makes it unclear that a ref is being captured. This was not covered today, and we'll try to squeeze a discussion in next meeting. On the surface, points 2 and 3 appear to be a good fit for an analyzer, but we think there might an opportunity to address a lingering annoyance from the original design of `in` parameters: the `in` keyword does not correspond to `ref readonly`, the keyword we use everywhere else throughout the language for a similar concept. We see an opportunity to retcon `in` to be a modifier with extra behavior on top of `ref readonly`, much like `out` is a modifier on top of `ref` with extra behavior on top. There is also the potential for an analyzer here to make the language _more_ complex, not less, as now this spot in the table of declaration site modifiers/permitted call site forms would be taken by an analyzer, not by a native language feature. The history of `in` as a feature stretches back to the time some members of the LDT spent on the Midori project, where they had a custom version of C# that supported a number of features, including a version of `ref readonly`. Initially, they used `ref readonly` as both the declaration site and the call site modifier, but user feedback, particularly on the call site side, was immediate and vocal. To combat this, they brainstormed and settled on `in` (first just at the call site, then eventually as a parameter modifier), as it's the natural counterpart to `out` and has been used as a C# keyword since C# 1.0. This was then brought into C# in the C# 7 timeframe, as we added `ref struct`s based on Midori's experience in the area. Midori, however, didn't have as many of these APIs where passing an rvalue was incorrect, as it had an expanded permissions system for lifetime control. C# does not have this, so we think that `ref readonly` is a good change to make up for this. Next, we will look at how `ref readonly` should affect the call site, and whether it should require that `ref`, `in`, or some other specifier to be explicitly provided. #### Conclusion We upload the decision around `ref readonly` as a parameter modifier. ================================================ FILE: meetings/2022/LDM-2022-05-11.md ================================================ # C# Language Design Meeting for May 11th, 2022 ## Agenda 1. [Inconsistency around accessibility checks for interface implementations](#inconsistency-around-accessibility-checks-for-interface-implementations) 2. [`ref readonly` method parameters](#ref-readonly-method-parameters) 3. [Pattern matching with UTF-8 String Literals](#pattern-matching-with-utf-8-string-literals) ## Quote of the Day - "Did [redacted] turn their camera on? I'm in trouble." ## Discussion ### Inconsistency around accessibility checks for interface implementations https://github.com/dotnet/roslyn/issues/60885 A small group went back and looked at this issue, to come up with a more palatable solution to the problem than the existing proposals. Unfortunately, our hands are somewhat tied here, as the runtime does not do any form of assembly accessibility checks. If the signature of a new interface member (DIM or not, `internal` or not) lines up with an existing member of a type that implements that interface, the runtime will consider that existing member to be an implementation of the interface member. We therefore think the best we can do is to perform a real accessibility check at compile-time. This isn't something we do today, and will prevent the user unintentionally getting into this scenario at compile time. While it can still be observed at run-time due to an upgraded assembly version, this is no different than other existing breaks when introducing new members into interfaces. #### Conclusion Perform a real accessibility check for implementations of internal interface methods, and error if the check fails. ### `ref readonly` method parameters https://github.com/dotnet/csharplang/issues/6010 Following up after the [last meeting](LDM-2022-05-09.md#ref-readonly-parameters) on `ref readonly` parameters, we discussed the call site rules for `ref readonly` parameters. We were dissatisfied with the rules we decided in our [first meeting](LDM-2022-04-25.md#ref-readonly-method-parameters) on the feature, which stated that `ref readonly`'s "natural" calling convention would be `in`, and using `ref` at the call site would be supported, but have a warning. This felt off, as `in` at the declaration site has been redefined to be `ref readonly` + convenience features. Using `in` at the call site, therefore, is a mismatch. We also have precedent in the language for `ref`, as that is what is required for `ref readonly` returns. At the same time, using `ref` at the call site doesn't always sit right either, as `ref` indicates danger: the method being called can see and modify your value. This danger signal is why `ref` is required at the call site in the first place, instead of following the C++ pattern of implicit by-ref parameters. In some ways, we think that there's an argument for allowing `ref readonly` parameters to simply be silent, rather than requiring any specifier; many of the same arguments for `in` apply to `ref readonly` at the call site as well. We have lifetime rules to flag truly unsafe value capturing, and `ref readonly` ensures that a call can't mutate the value from under the caller. That being said, however, the main use cases we are looking at the `ref readonly` feature will benefit from some marker at the call site. A key distinction of most of the APIs we're considering is that they capture or return refs from these parameters, and a marker of some kind is a good thing for such scenarios. We also need to consider that we will have APIs migrating to `ref readonly` from both `ref` and from `in`, so existing code will have potentially any or no existing markers. To resolve these constraints, we've come up with the following set of rules, which cover call site conventions for `ref`, `ref readonly`, and `in` parameters: | Call site annotation | `ref` parameter | `ref readonly` parameter | `in` parameter | |----------------------|-----------------|--------------------------|----------------| | `ref` | Allowed | Allowed | Warning | | `in` | Error | Allowed | Allowed | | No annotation | Error | Warning | Allowed | * `ref` and `ref readonly` parameters require lvalues at the call site. * `in` allows lvalues or rvalues at the callsite. #### Conclusion See the above table and bullets. ### Pattern matching with UTF-8 String Literals https://github.com/dotnet/csharplang/discussions/6036 Finally today, we looked at pattern matching for UTF-8 string literals, particularly against inputs of `ReadOnlySpan`. This is similar to the work we did for `string` for C# 11, where you will be able to pattern match an input of `Span` against string literals. We're generally supportive of this, and we have a broader goal in mind: we'd eventually like `u8` literals to _be_ constants. The runtime would need to be updated to support this (as the CLI spec doesn't indicate that UTF-8 literals are supported as constant values today), but we are in support of steps along the journey of making `u8` literals appear as constants, including pattern matching support. We don't think this is top priority, though, so while we would accept a community contribution to create a specification for how this would work, we won't be actively working on it at this time. #### Conclusion We will review a community-contributed specification for this feature. ================================================ FILE: meetings/2022/LDM-2022-05-23.md ================================================ # C# Language Design Meeting for May 23rd, 2022 ## Agenda 1. [Open issues for `ref` fields](#open-issues-for-ref-fields) ## Quote of the Day No particularly amusing quotes were said during this meeting, sorry. ## Discussion ### Open issues for `ref` fields https://github.com/dotnet/csharplang/issues/1147 https://github.com/dotnet/csharplang/issues/6149 Today we looked at several open issues in `ref` fields. #### Parsing `scoped` There is analogy here to other modifiers we are adding in C# 11, such as `required`. For that modifier, we said that we wanted to error when types names conflict. However, local variables are more of a concern, as the name `scoped` is a perfectly reasonable local variable name. We would like to take the same approach for `scoped` as we do for `required`. ##### Conclusion We will error when types are named `scoped`. We will do the parsing work to make `scoped` as a local name work. #### `ref` auto properties Just as auto-properties are a simplification on the private backing field + property scenario, a ref auto-property could be a simplification on the private backing field + ref-returning property backing scenario for ref structs. While we think this is a scenario that makes sense, we think it would require a setter/initer to actually be useful: otherwise, the user can only assign the backing ref field in their constructor. We don't have a real way to emit setters for ref-returning properties as the runtime doesn't support setters with a `ref` `value` argument. We could potentially do some emit tricks to make it work, but we think that it would be better to wait for first-class runtime support for the scenario, which can then be picked up more generally by other languages. ##### Conclusion We will hold off on `ref` auto properties until we have `ref`-returning property setter support in the runtime. #### Allowing `ref` assignment in object initializers Finally, we looked at whether we should allow `ref` assignment in object initializers. We do have customers that want this: the runtime has been dogfooding a build of the feature, and this was one of their first requests. We do like it in general, but we need to think about the guardrails for requiring ref assignment will work. In particular, there could be multiple potential things that a user would want to do: * `required ref int Field;` - Consumers must ref assign a location to `Field`. * `ref required int Prop { get; }` - Consumers must assign a value to the storage location returned from `Prop`. We punted on this question for `required` members previously, so we'll need to consider more at the next meeting. ##### Conclusion Tentatively accepted, but we need to think about guardrails before confirming. ================================================ FILE: meetings/2022/LDM-2022-06-06.md ================================================ # C# Language Design Meeting for June 6, 2022 ## Agenda 1. [Open issues for `ref` fields](#open-issues-for-ref-fields) 2. [Open issues for static virtual members](#open-issues-for-static-virtual-members) 3. [Concatenation of Utf8 literals](#concatenation-of-utf8-literals) ## Discussion ### Open issues for `ref` fields https://github.com/dotnet/csharplang/issues/6149 We discussed the questions out of order because of the way we perceived them to influence each other. #### Question 5: `scoped` differences in overrides and interface implementations When overrides or implementations differ from their base in terms of `scoped` what should we do? We seem to have the following options: 1. Do nothing: no restrictions for 'scoped' in override/implementation (unsafe) 2. Disallow use of 'scoped' in overrides/interfaces 3. Disallow differences in 'scoped' in override/implementation 4. Allow adding 'scoped', disallow dropping 'scoped' in override/implementation Option 1 and 2 do not seem helpful. Option 3 is simple, allows expressing reasonable things and is safe.Option 4 is a bit more involved, but is more expressive too, and seems more consistent with nullability etc. ##### Conclusion We lean to 4, but do not have important scenarios. We're fine if we only make it to 3 now and potentially do 4 later, which will be compatible. We'd need to spend the time to assure ourselves that 4 is safe. #### Question 6. `scoped` differences in delegate conversions Similar question and similar options, but for conversion of lambdas and method groups to delegate types. ##### Conclusion This should do the same as in the previous question. Even for explicit conversion we should not break `scoped` safety. There are unsafe APIs for breaking the rules if people really want to. Inferred delegate types will preserve `scoped` from their lambda or method group. #### Question 4. Constructor that does not set ref field Should we report a diagnostic on a constructor that doesn't initialize a ref field? It will lead to a null ref exception later. On the one hand we have a set of rules for non-nullable reference types that we could probably adapt to ref fields. On the other hand this is an advanced feature set and this may not be worth it. Also we can't do anything about default values of the ref struct, which are likely to be common. The runtime is planning to do work so that null ref dereference behavior is well defined, as no matter what, safe code can now create ref fields with nulls in them. The runtime will ensure that all locals with references will be zeroed even if `SkipLocalsInit` is specified. ##### Conclusion No diagnostics - it's not worth it, wouldn't save every case anyway, and this is an expert feature. #### Question 3. Ref assignment in object initializers Constructors allow ref parameters to be assigned to ref fields. Should object initializers also allow ref assignment? ##### Conclusion Yes. There's no reason not to, it's just a little bit of syntax and it's obvious what it does. ### Open issues for static virtual members https://github.com/dotnet/csharplang/blob/main/proposals/static-abstracts-in-interfaces.md#unresolved-questions #### Adjusting the type argument rule Should we allow interfaces to be type arguments if they have virtual but not abstract static members - i.e. if all virtual static members have a default implementation? If we don't allow this, adding a static virtual with a default implementation to an existing interface that has no static virtuals will break anyone currently passing that interface as a type argument. This breaks the principle that new interface members with default implementations don't break existing code. ##### Conclusion Let's loosen the rule to preserve the value of default implementations, assuming that we can loosen the corresponding rule in the runtime. #### Virtual equality and conversion operators Should virtual equality and conversion operators be allowed to have default implementations, and to provide implementations in overrides? If so are there any restrictions? Currently interfaces are not allowed to declare *non*-virtual equality and conversion operators. The reason is that such operators too easily lead to confusing or conflicting behavior on the interfaces. However, *virtual* operators do not apply to the interfaces themselves but only to type parameters constrained by them, so they do not seem to have these same problems. Nevertheless, an interface `IThing` with a declaration of the form ``` c# public interface IThing { static virtual bool operator ==(IThing i1, IThing i2) => ... ... } ``` would be quite confusing, especially since it would not apply to instances of the interface itself: ``` c# var result = i1 == i2; // Reference equality ``` On the other hand, a declaration that has "self-type" operands would probably be a lot harder to misunderstand in that way: ``` c# public interface IThing where TSelf : IThing { static virtual bool operator ==(TSelf i1, TSelf i2) => ... ... } ``` Conversions are already not allowed to have interface operands directly. ##### Conclusion Both types of operand declarations should be allowed to have default implementations. We will limit equality operators so that one of the operands cannot be the interface itself and has to be a self-constrained type parameter. The other can be anything (including an interface). ### Concatenation of Utf8 literals https://github.com/dotnet/roslyn/issues/60896#issuecomment-1129362977 The motivating scenario is line breaking source code containing very large Utf8 string literals. Should we wait for a `+` operator for Utf8 strings (`ReadOnlySpan`) in general? Is such a thing even desirable? After all it would be expensive at runtime, and concatenation might not generally be the obvious semantics for `+` on spans - even on spans of bytes. However, on literals its meaning is obvious. #### Conclusion Let's do it. Let's make sure people get good error messages when they try to `+` non-literals. ================================================ FILE: meetings/2022/LDM-2022-06-29.md ================================================ # C# Language Design Meeting for June 29th, 2022 ## Agenda - [UTF-8 literal concatenation operator](#utf-8-literal-concatenation-operator) ## Quote of the Day - "This is an example of our admirable quality that no stone is left unturned" ## Discussion ### UTF-8 literal concatenation operator https://github.com/dotnet/csharplang/issues/184 https://github.com/dotnet/csharplang/pull/6221 Today, we looked at a small proposal update for UTF-8 strings, based on BCL dogfooding feedback: the ability to concatenate UTF-8 string literals together with the `+` operator so that long strings can be split across lines. This is similar to the work with interpolated string handlers, where we similarly enabled the `+` operator for splitting strings across lines. We had two main discussion points: First, should we enable `+` as a general operator on all `ReadOnlySpan`s? For example, should the following code work? ```cs ReadOnlySpan M() => "hello "u8; var helloWorld = M() + "world"u8; ``` We don't think that is a general case we want to support: concatenation of literals can be done easily at compile time, but this would need a general ability to add `ReadOnlySpan`s together, and we don't know how that would work. Where would the resulting byte sequence live, for example, and what would be its lifetime? Second, we thought about whether this operator should have precedence over a user-defined operator `+` on `ReadOnlySpan`? According to the C# spec, all existing predefined operators in C# _do not_ have precedence over user-defined operators. However, this is not how the compiler is actually implemented: the native C# compiler had a bug that always used predefined operators, even when the underlying type defined a `+` operator itself, and Roslyn reimplemented that bug during its creation. When implementing the `+` predefined operator for `ReadOnlySpan`, however, we did not carry that bug forward, so if a user defines their own `ReadOnlySpan` with a `+(ReadOnlySpan left, ReadOnlySpan right)` operator defined on it, that operator will be preferred over this predefined one. We could go and reimplement the bug for this case as well, but we don't think the scenario is important enough to spend the time, and it will simplify the spec if we continue to specify predefined operators exactly as they are today, rather than carving out a special case for this `+` operator. #### Conclusion Proposal accepted. ================================================ FILE: meetings/2022/LDM-2022-07-13.md ================================================ # C# Language Design Meeting for July 13th, 2022 ## Agenda 1. [Lambda default parameters](#lambda-default-parameters) ## Quote of the Day - "We love to point out failures, it's what we do" ## Discussion ### Lambda default parameters https://github.com/dotnet/csharplang/pull/6274 https://github.com/dotnet/csharplang/issues/6051 Today, we reviewed the draft specification for default lambda parameters. There are two main parts of this proposal we need to consider: 1. The actual changes to lambdas themselves. These are relatively straightforward, with only a few nuanced sections we need to consider. 2. Changing the natural type of method groups. This is more nuanced, as it will be a breaking change from C# 10. For the first point, we had relatively few pieces of feedback. We think that we should indeed error when the default parameter value of an argument differs from the target type. For example, this should be an error: ```cs delegate void D(int i = 0); D d = (int i = 1) => {}; // Error: default parameter values differ. ``` The one case that we think should be acceptable is when the lambda does not specify a default parameter value at all. This is an important backcompat case as it as worked since lambdas were introduced, as well as being user convenience. ```cs delegate void D(int i = 0); D d1 = i => {}; // Fine: no value specified D d2 = (int i) => {}; // Fine: no value specified ``` For the second part, we think we need a more detailed understanding of where the breaks can occur before we decide for sure that we will accept the break. We know from C# 10 how subtle and painful breaks here can be. While they would be more limited in scope, and we think that it would only breaks since C# 10, not since lambdas were introduced, we want to understand the space more before committing. Finally, we briefly considered whether we should support `params` parameters as well. We think they're interesting, but didn't make a final decision today. #### Conclusion The lambda rules themselves are accepted, with the error exception for lambdas that don't specify default parameters being converted to a type that does have them. We will get a better understanding on method groups before making a decision. ================================================ FILE: meetings/2022/LDM-2022-07-27.md ================================================ # C# Language Design Meeting for July 27th, 2022 ## Agenda 1. [`scoped` for local declarations](#scoped-for-local-declarations) ## Quote of the Day - *nervous chuckling* "That's actually an interesting question!" "I know, it's just when you say scary things." ## Discussion ### `scoped` for local declarations https://github.com/dotnet/roslyn/issues/62039 https://github.com/dotnet/csharplang/issues/1147 Today, we looked at allowing `scoped` as a local modifier in all local declaration context, such as `using`s, `foreach` iterator variables, and such. We like the idea in principle: users don't think of the various different grammatical ways of creating a local variable differently, they think of them all as locals. There were a couple more in-depth points of discussion about the implementation: * Is `scoped` or not something that `var` should be able to infer? This matters for deconstruction, as in `var (x, y) = MethodCall();`. After some consideration, we don't think scopedness is something we can or should infer. It might also vary between each of the deconstruction variables. Given this, we think that `scoped` needs to explicitly declared, not inferred in any local case. * The specification for `scoped` doesn't particularly line up with how we consider modifiers. We'd like to think of this as a new kind of modifier, a modifier that must go directly on a type, rather than a modifier that can go anywhere in the modifiers list of a method. Unlike method modifiers, `scoped` is specifically a modifier on either the return value or a parameter value, and should be applied in those locations. `scoped unsafe Span M() => ...;`, for example, isn't legal. The `scoped` affects the return, while the `unsafe` generally affects the method body. We therefore think that the specification (and Roslyn implementation) should match this intuition. #### Conclusion We approve the general concept in this issue, but the grammar specification needs to be updated. ================================================ FILE: meetings/2022/LDM-2022-08-03.md ================================================ # C# Language Design Meeting for August 3rd, 2022 ## Agenda 1. [`ref` fields specification updates](#ref-fields-specification-updates) ## Quote of the Day - "If anyone is confused by these rules, you can hit me up anytime. I advise caffeine before the meeting." ## Discussion ### `ref` fields specification updates https://github.com/dotnet/csharplang/issues/6337 Today, we looked at changes that have been made to the `ref` fields feature from implementation feedback. Changes 1 and 2 were approved with very little discussion. Point 3 generated much more discussion. In particular, we discussed the change that `` `this` for `struct` instance methods `` is now implicitly `scoped`. Part of this overlaps with the next point, that `` `ref` parameters that refer to `ref struct` types `` should be `scoped` by default, but it goes further, making it the default for all structs, `ref` or not. The general question is: should we narrow this to just `ref struct` types? This would be a breaking change, and we've accepted a breaking change in this feature already with the updated escape rules under the principle of reducing confusion and having the language do the right thing. This case is less clear-cut though: for the escape rules, the general consensus is that the old defaults were wrong for nearly all methods, and real-world data indicated that very, very few such methods existed. That isn't the case for the `this` parameter on `struct` methods though: `ImmutableArray.ItemRef(int index)`, for example, would be broken by having `this` be implicitly unscoped. If we were designing the system from scratch, we might have narrowed this rule, but given that the compat concerns are larger and the benefit isn't clear cut, we don't think we should narrow this rule. We also took a bit of time to think about whether we were cutting off our ability to make future innovations in the space of `ref struct`s with these rules, such as allowing them in generics, implementing interfaces, and future flow analysis improvements. We don't think that we're introducing any new problems in those spaces, so we'll proceed as is. We did not get to points 4 and 5 today. We will discuss those over email and potentially in a future in-person LDM, if required. #### Conclusion Points 1-3 are approved. ================================================ FILE: meetings/2022/LDM-2022-08-10.md ================================================ # C# Language Design Meeting for August 10th, 2022 ## Agenda 1. [`required` property default nullability](#required-property-default-nullability) ## Quote of the Day - "If I'm going on a long journey I always think I can get all this stuff done on the plane. I always end up watching a movie." ## Discussion ### `required` property default nullability https://github.com/dotnet/csharplang/issues/6359 Today we looked at what the default state of required properties should be at the beginning of a constructor. As implemented and specified currently, properties will be treated as if they have the null state they claim, so `public required string Prop { get; set; }` would have a not-null state at the start of a constructor. We don't think this is a good idea: in C# 10, users could either count on a chained constructor having set all members, or will have the default state of all members set to not-null. We think `required` serves as a good heuristic indicator that the property should be set to `default(T)` at the start of the constructor. The exception to this is when the user chains to a constructor has specified `SetsRequiredMembers` on the constructor. #### Conclusion We accept the following rules: 1. When chaining to a constructor that does not have `SetsRequiredMembersAttribute`, all `required` members have a null state of `default(T)`, including those required members from a base type. 3. At the end of a constructor that is attributed with `SetsRequiredMembersAttribute`, all `required` members must have the null state they are declared with, including those required members from a base type. ================================================ FILE: meetings/2022/LDM-2022-08-24.md ================================================ # C# Language Design Meeting for August 24th, 2022 ## Agenda 1. [C# feature triage](#c-feature-triage) ## Quote of the Day - "There will also be a debate about whether the next one will be 13 or 14" ## Disclaimer Hi all, your friendly C# note taker here. [This time last year](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-08-30.md#discussion), when we did our initial C# 11 triage, I mentioned that readers should not try to use the meeting to "read the tea leaves" for what would be in C# 11. Most of our readers didn't, and for that we are appreciative. However, a few blog posts did crop up on what features were going to be in C# 11, and they contained information about features the did not make C# 11 (and missed some features that did make it). We'd ask, again, that you please not use these notes as a way of saying what will be in the next version of C#. We are looking through our backlogs, user feedback, and other such areas to decide what areas we want to be focusing on, but we are not committing to anything just yet. We are merely trying to get an understanding of our priorities, and any of these features could make C# next, make it in preview form, or not make it at all. Thanks. ## Discussion ### C# feature triage Today, we started our future C# feature triage. This is the first of at least 2-3 meetings where we will be looking at the big and small ticket items and feature areas to determine where our design focus will be for the next year. This year, we started the process by collecting topics from all members of the LDT. The recurring themes of these topics are: 1. Discriminated unions and working with un/less-structured data. Nearly everyone brought up a topic in this broad area, be it looking at closed-type-hierarchy-style tagged unions or unstructured anonymous inline unions of data. We think there's a lot of potential here, and in particular potential for a holistic exploration of the space in the same style as we did for pattern matching, where we used a broad internal vision to deliver 4+ releases of features to build out the space. We would like to spend time in the next cycles coming up with a similar vision for this space, and start delivering on features in it that will fit into this broader vision of making data easier to work with in the language. 2. Roles/extension everything. This is another big feature space that includes things both large and small that users have been asking about for years. Unlike DUs, we have a better understanding of the specific areas this feature is trying to address, and an internal working group on roles has been persistently chipping away at a design for breaking up the larger feature into smaller components that can be shipped without breaking our future ability to innovate in this space. We hope to have results from this group to share soon. 3. Tooling improvements. A rarer suggestion from this group, but there is interest in investing more in the "C#-adjacent" tooling experience, and seeing what can be done to further improve the polyglot notebooks and interactive scenarios and bring them to parity with regular C# projects. We'd also like to look at the experiences that other languages bring in their tooling (specifically via language features) and see what lessons we can learn to make our own experiences better. 4. Expression trees. As last year, discussions on how to modernize this experience have been continuing in the background, and we would like to address them. It's not a small amount of work, but it needs to start somewhere. 5. Community-driven features. We've been successful in the past few releases of having community members design and implement C# features, and we'd like to continue this trend. `field` is an example of this we hope to see soon (there are outstanding design questions that need to be answered after implementation hit issues), but other features in the Any Time bucket that receive community interest should also get some of our attention. 6. Smaller nits, such as block expressions. We've talked about rounding off some smaller corners that have usability impacts, such as expanding our set of expressions in C# (making switch expressions have less of a cutoff, allowing returns in new places, etc). We are generally aligned on the idea, but there are some serious syntax battles that need to be fought. When we look at the sets of features we're interested in, we see large emerging themes, and opportunities to break up the features into smaller things working groups can investigate and bring back to the larger group. We've had good success with this approach in both `required` members and in the ongoing group working on roles, and we think it's a strategy we should adopt more. Next time, we'll discuss these themes further, and see if we can break them up further into more concrete areas for investigation. ================================================ FILE: meetings/2022/LDM-2022-08-31.md ================================================ # C# Language Design Meeting for August 31st, 2022 ## Agenda 1. [Short term work triage](#short-term-work-triage) 1. [Partial properties](#partial-properties) 2. [Using aliases for any type](#using-aliases-for-any-type) 3. [Null-conditional assignment](#null-conditional-assignment) 4. [`await?`](#await) 5. [`params IEnumerable`](#params-ienumerable) 6. [Discriminated Unions](#discriminated-unions) 7. [Roles](#roles) 8. [`pragma warning enable`](#pragma-warning-enable) 9. [Ignored directives support](#ignored-directives-support) 10. [Primary constructors](#primary-constructors) 11. [Switch expression as a statement](#switch-expression-as-a-statement) ## Quote of the Day - "Features ready to eat" ## Discussion ### Short term work triage The LDM is going on break for 3 weeks due to member absences, and the compiler team is starting to wrap up on C# 11 bugs. We therefore took today to triage through smaller features and prototyping work we'd like to start on as we wrap up. The former should be simple features that don't need further design work, and the latter should be features where a prototype would serve to further the design. **Important note**: This list is longer than the compiler team will be able to look at in the next few weeks. Think of it as a shortlist, nothing more. #### Partial properties https://github.com/dotnet/csharplang/issues/6420 This feature has not been looked at much by the full LDM, but the proposal seems straightforward, follows our existing rules for partial methods, and avoids complicated questions on what to do for fully-auto properties. We'll put it on the list. #### Using aliases for any type https://github.com/dotnet/csharplang/issues/4284 We originally wanted to look at this in conjunction with `global using`s, but didn't get to it. It's on the list. #### Null-conditional assignment https://github.com/dotnet/csharplang/issues/6045 Another one we wanted to get to during 11, but ran out of time. It's on the list. #### `await?` https://github.com/dotnet/csharplang/issues/35 This is our second-highest upvoted `Any Time` milestone issue, and continues C#'s operator monad over nulls (along with the previous issue). It's on the list. #### `params IEnumerable` https://github.com/dotnet/csharplang/issues/179 This issue is specifically for `params IEnumerable` parameters, not including `Span` or other possible variations. And we think that's part of the problem with trying to approach this right now: there's too many questions about what exactly to support. We think this has too many questions to be on the shortlist. #### Discriminated Unions https://github.com/dotnet/csharplang/issues/113 Obviously, this is not in the "go implement in the next few weeks" category. However, we think that starting to prototype will help inform our design choices in this space, and this is feature is (by a large margin) the most up-voted issue on csharplang. Protoyping is on the list. #### Roles https://github.com/dotnet/csharplang/issues/5497 This is another area where we think prototypes will help drive the conversation, especially since our current questions are around feasibility of implementation. Prototyping/initial implementation is on the list. #### `pragma warning enable` https://github.com/dotnet/csharplang/issues/3986 This is fairly simple and non-complicated. However, very rarely for a csharplang issue, this issue has no upvotes or activity of any kind, and some members of the LDT are conflicted on whether the feature is worth it. Not on the list. #### Ignored directives support https://github.com/dotnet/csharplang/issues/3507 Would be nice to have, but only in conjunction with the rest of the simple programs feature. Implementation might be one of the simplest ever features in the compiler, but without the tooling to accompany it we don't know that this is the right design. Not on the list. #### Primary constructors https://github.com/dotnet/csharplang/issues/2691 The last time these were brought up, we blocked on what exactly the primary constructor parameters _are_: fields, captures, or something else? We think a prototype to play around with might help us make progress, so this is on the prototyping shortlist. #### Switch expression as a statement https://github.com/dotnet/csharplang/issues/2860 https://github.com/dotnet/csharplang/issues/3038 There are a few different directions we could take this space. We could take this approach, or look at modernizing switch statement syntax, or some other form. This space needs more design, so this is not on the list. ================================================ FILE: meetings/2022/LDM-2022-09-21.md ================================================ # C# Language Design Meeting for September 21st, 2022 ## Agenda - [Unchampioned issue triage](#unchampioned-issue-triage) - [Track subtype exhaustiveness for classes with only private constructors](#track-subtype-exhaustiveness-for-classes-with-only-private-constructors) - [ReadOnlySpan initialization from static data](#readonlyspan-initialization-from-static-data) - [Embedded Language Indicators for raw string literals](#embedded-language-indicators-for-raw-string-literals) - [Implicit Parameters](#implicit-parameters) - [Unsafer Unsafeness](#unsafer-unsafeness) - [Conclusion](#conclusion) ## Quote(s) of the Day - "Yeah, I'm only hearing positive reactions, I've got to shoot it down" - "Unsafer unsafeness" - "It's like a horror movie. It's terrifying, but you want to go see it, because it's also amazing." ## Discussion ### Unchampioned issue triage #### Track subtype exhaustiveness for classes with only private constructors https://github.com/dotnet/csharplang/issues/4032 This comes up often in the context of discriminated unions. It should be thought about when designing that feature. There are some potentially interesting cases in this with ref assemblies as well, since private constructors are normally dropped. Into the working set. #### ReadOnlySpan initialization from static data https://github.com/dotnet/csharplang/issues/5295 We like this idea. Into the working set. #### Embedded Language Indicators for raw string literals https://github.com/dotnet/csharplang/issues/6247 We intentionally left the door open for this proposal, but we'd like to design for more than just raw string literals. We're uncertain whether just multiline strings provides a generalized-enough benefit beyond the existing comment format. Into the working set with this caveat. #### Implicit Parameters https://github.com/dotnet/csharplang/issues/6300 This community proposal is inspired by features from other languages, like implicit parameters in Scala 2. While we think the space is interesting, we think there's several potential issues: first, Scala 2's implicit parameter support was not well received, and reworked as part of Scala 3. The way that binding worked in Scala 2 caused a number of understandability issues, and we don't want to repeat them in C#. Kotlin is also adding a similar feature, context receivers, and we'll be interested to see how it is received by that community. We're also not certain of how widely this would be adopted, especially in the BCL. Finally, there is significant negative community sentiment on the issue. This will go into the backlog for now, pending further community requests. ### Unsafer Unsafeness https://github.com/dotnet/csharplang/issues/6476 While we're not a huge fan of this proposal as a general principle, we think it has merits. `unsafe` in C# is generally concerned with allowing users to express patterns that are _memory_ unsafe, and this proposal doesn't change that principle; it simply extends that memory unsafety to a new area. Importantly, users will have a way to express these semantics, regardless of whether the language changes here, as `Unsafe` will still exist. This proposal ensures that for such code, users will at least be warned, rather than silently getting no feedback whatsoever. #### Conclusion Approved for C# 11. ================================================ FILE: meetings/2022/LDM-2022-09-26.md ================================================ # C# Language Design Meeting for September 26th, 2022 ## Agenda - [Working set triage](#working-set-triage) - [Roles & Extensions](#roles--extensions) - [Discriminated Unions](#discriminated-unions) - [Bridging statements and expressions](#bridging-statements-and-expressions) - [Construction improvements](#construction-improvements) - [`ref struct` improvements](#ref-struct-improvements) - [`params` improvements](#params-improvements) - [Ungrouped](#ungrouped) ## Quote of the day - *Makes a dad joke* "We saw that look on your face." ### Working set triage Today, we went through a significant portion of the current working set of items, and triaged them into a few categories: * Working group themes. We had good success with a smaller working group on `required` members, and we would like to extend the same strategy to other larger items. For each such area, we'll create a working group to investigate all related issues. * Ungrouped features. These are generally smaller features or features with a relatively straightforward design that don't need large amounts of design time to pursue. Importantly, we didn't get through everything in the working set today, so some of these categories are currently light. We'll finish our pass next meeting. #### Roles & Extensions We already have an internal working group for this topic, and they will be continuing to work on it. Related issues: * https://github.com/dotnet/csharplang/issues/110 - Type classes * One of the motivating scenarios for roles * https://github.com/dotnet/csharplang/issues/5497 - Roles & Extensions #### Discriminated Unions This is one of the biggest things that we think will benefit from a smaller group to make design progress. There are a number of areas to investigate, as demonstrated by the open related issues: * https://github.com/dotnet/csharplang/issues/113 - Discriminated unions * https://github.com/dotnet/csharplang/issues/485 - `closed` type hierarchies * https://github.com/dotnet/csharplang/issues/3179 - `closed` enums * https://github.com/dotnet/csharplang/issues/2926 - Target-typed name lookup * We don't want to gate this on DUs, but it will be highly complementary #### Bridging statements and expressions We think there are two main subcategories here: the general feature of block expressions, and more specific improvements and unification of switch statements and expressions. Related issues: * https://github.com/dotnet/csharplang/issues/377 - Sequence expressions * https://github.com/dotnet/csharplang/issues/3037 - Block-bodied switch expression arms * https://github.com/dotnet/csharplang/issues/3038 - Enhanced switch statements * https://github.com/dotnet/csharplang/issues/3086 - Expression blocks #### Construction improvements We have a few issues related to general construction improvements, such as generalized support for factories, support for final initializers, generalized `with` support, and others. Related issues: * https://github.com/dotnet/csharplang/issues/162 - Pattern-based `with` expressions * It is irksome, but do we have customers who really want it? * Not sure we would spend time on this currently * Moved to the backlog. #### `ref struct` improvements There are three interrelated, major improvements that need to be done in parallel: `ref struct`s implementing interfaces, `ref struct`s in generics, and ref fields to ref structs * https://github.com/dotnet/csharplang/issues/1148 - `where T : ref struct` #### `params` improvements This doesn't need a full group, just a single dev to investigate and bring back a list of all the things to support in `params`. The `Span` is blocked on runtime support, but the other parts of the feature can proceed without waiting for it. Related issues: * https://github.com/dotnet/csharplang/issues/1757 - `params Span` * https://github.com/dotnet/csharplang/issues/179 - `params IEnumerable` * https://github.com/dotnet/csharplang/discussions/6489 - Discussion on `params Span` * https://github.com/dotnet/csharplang/discussions/6490 - Discussion on more `params` abilities * https://github.com/dotnet/csharplang/discussions/6491 - Discussion on pattern-based `params` #### Ungrouped Finally, these are the issues that we don't feel need a working group to drive them. * https://github.com/dotnet/csharplang/issues/133 - Property-scoped fields * Still think it will be after `field`. * Into the backlog, as we are currently focused on #140. * https://github.com/dotnet/csharplang/issues/140 - `field` * Will proceed, try to get in for C# 12 after questions are resolved * https://github.com/dotnet/csharplang/issues/1358 - `default` in deconstruction * Small and independent, but needs LDM input to proceed. * https://github.com/dotnet/csharplang/issues/2302 - Efficient `params` and string formatting * Was split up into multiple issues, some of which are already done. Will close and link. * https://github.com/dotnet/csharplang/issues/2180 - Allow omitting unused parameters * We don't think this is currently important. Will move to the backlog. * https://github.com/dotnet/csharplang/issues/2691 - Primary Constructors * Still needs an agreed-upon proposal. We've been meaning to bring one for discussion. * https://github.com/dotnet/csharplang/issues/3507 - Ignored directive support * Leaving as is * https://github.com/dotnet/csharplang/issues/3658 - Repeated Attributes in Partial Members * We're unlikely to make progress on this ourselves, but would accept community contributions here. Moved to Any Time. ================================================ FILE: meetings/2022/LDM-2022-09-28.md ================================================ # C# Language Design Meeting for September 28th, 2022 ## Agenda - [Working set triage](#working-set-triage) - [Discriminated Unions](#discriminated-unions) - [Collection literals](#collection-literals) - [Nullability improvements](#nullability-improvements) - [Ungrouped](#ungrouped) ## Quote(s) of the Day - "Instead of just skipping 13, we'll take a break for the whole year!" - "disjunctive junction, what's your function?" - "That's the sound of never" - "I do weird stuff in C#, and I've never wanted this" ## Discussion ### Working set triage We continue our triage from [last time](LDM-2022-09-26.md). For clarity on what we did today, the only issues listed below were actually covered today. We will update https://github.com/dotnet/csharplang/issues/4144 with the collated results when we are done with the triage. #### Discriminated Unions * https://github.com/dotnet/csharplang/issues/4032 - Track subtype exhaustiveness for classes with only private constructors * Another one for the list #### Collection literals * https://github.com/dotnet/csharplang/issues/5354 - Collection literals * Complex feature area that we think would benefit from a dedicated working group, even though there is only one issue in the group. * It would be good to have some BCL representation as well. * Could also be related to `init` methods as a method of implementation. #### Nullability improvements We have some nullability issues that have been around for a few releases, and it would be good to turn the crank on nullability and make these pain points better. * https://github.com/dotnet/csharplang/issues/3950 - `Task` nullability covariance * One of the common complaints * https://github.com/dotnet/csharplang/issues/3951 - Nullable analysis of LINQ queries * Another super common complaint * Both LINQ as implemented in the BCL, and as done in 3rd-party libraries #### Ungrouped The bulk of our meeting today covered smaller issues that are ungrouped. * https://github.com/dotnet/csharplang/issues/3885 - Support `readonly` modifier for classes and records * We initially thought this might be an Any Time candidate, but discussion revealed it has some potential interactions with with `readonly` on interfaces. * Backlog for now * https://github.com/dotnet/csharplang/issues/3980 - call local static functions in base constructor * We're not going to make progress on this soon, but we think this is a good Any Time candidate. * It still needs an accepted specification, but we think it should be relatively straightforward to define. * https://github.com/dotnet/csharplang/issues/3986 - `#pragma warning *enable*` * This issue has been unchampioned by the current champion, and no one willing to step up and take it over. * We think there could be potential bad performance interactions with the analyzer ecosystem. * Moved to Likely Never. * https://github.com/dotnet/csharplang/issues/1502 - Five ideas for improving working with fixed buffers * Most of these ideas are already implemented or broken out into separate issues. * We will link the appropriate issues from this one and close it out. * https://github.com/dotnet/csharplang/issues/4018 - Permit variable declarations under disjunctive patterns * There are still a few design decisions to make here, and it's been sitting on our docket for a while. * C# 11 priorities have kept pushing it back. * We hope to make progress in the near future. * https://github.com/dotnet/csharplang/issues/4024 - Direct constructor parameters * This was a suggestion that arose as we were thinking about `record` types, but we're no longer interested in this approach. * Moved to Likely Never. * https://github.com/dotnet/csharplang/issues/4121 - Metadata determination of whether a type is a record * If we want to move towards records inheriting from non-records, then we need to think about this. * Cross-inheritance is not currently in the working set, so this moves to backlog. * https://github.com/dotnet/csharplang/issues/4284 - using aliases for any types * We still want this feature, and have a PR implementing the current design. * There are a few design questions to resolve. * We'll pursue this soon. * https://github.com/dotnet/csharplang/issues/4460 - Only Allow Lexical Keywords in the Language * We've started down this path with the warning wave warning on lowercase type names, but we need to discuss this in more depth. * Source generators are forcing our customers to deal with the pain of our choices, not just us, which is a more compelling reason than avoiding work for ourselves. * https://github.com/dotnet/csharplang/issues/4485 - async method exception filter * The pain here is real, but we're not fans of this approach as the solution, as it's a narrow AOP solution. * A smaller group will brainstorm other approaches, including whether a non-language solution would be possible. * https://github.com/dotnet/csharplang/issues/4748 - Directly invoked anonymous functions * There is universal dislike of this feature on the LDM. * Moved to Likely Never. * https://github.com/dotnet/csharplang/issues/5596 - Adding Index support to existing library types (redux) * This needs BCL input on whether adding more overloads for Index/Range for these extra methods does not work. * Backlog for now. ================================================ FILE: meetings/2022/LDM-2022-10-05.md ================================================ # C# Language Design Meeting for October 5th, 2022 ## Agenda - [Review of `ref` fields](#review-of-ref-fields) - [`RefSafetyRulesAttribute`](#refsafetyrulesattribute) - [Return-only scope](#return-only-scope) ## Quote(s) of the Day - "We looked at that in 7.2 and said 'We can't imagine making that work in the compiler'" - All the facial expressions various LDM members made as they wrapped their heads around the rules. ## Discussion We intend to post the slide deck that was shown to LDM at a later date, but it needs a few more revisions before posting. ### Review of `ref` fields https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md The `ref` fields feature has been going under significant and rapid revision as we approach the end of the C# 11 development cycle, based on feedback from dogfooding the feature in (hopefully) its largest consumer, the BCL. RC1, however, had a few more internal and external partners intake the bits, see the breaking changes, and give us feedback on them. Today we wanted to go over the changes we've made in response to this latest round of feedback. The team came with a [presentation](TODO) on the recent changes for us to go over today. #### `RefSafetyRulesAttribute` This attribute will allow us to better recognize whether a module should be treated with the new ref safety rules, or with the pre-C# 11 safety rules. We did have some concerns about the particular format, but these were resolved through discussion: * This could be seen as a stamp of "this module was compiled with language version X", which isn't something we do today. However, we don't expect the safety rules to increase every language version, so we don't expect that this will be a problem. * We also thought about using something other than an integer, in case we have a minor version that we want to rev the rules on. We don't think this is likely, as we've moved away from minor releases at this point. * We thought about an analyzer to inform users about the breaking change on upgrading their assembly, this is tracked by https://github.com/dotnet/roslyn/issues/64344. #### Return-only scope In response to breaking changes seen by customers with `ref` parameters, we added a new scope in between the method scope and the calling function scope: return-only. Something that is return-only is only allowed to escape by ref through a `ref`-return or an out parameter, and not through ref assignment to a `ref` parameter. The LDM members not steeped in the lingo of ref safety found this change particularly hard to understand, and the ref fields group would like to brainstorm a bit on how to better explain the concept. Once the intuition was built, however, it was well-liked. In particular, we are happy that constructors are no longer a special case, and just fall out of the new rules. We would like to see some improvement in the error messages the compiler gives, and that work is planned for VS 17.5. ================================================ FILE: meetings/2022/LDM-2022-10-10.md ================================================ # C# Language Design Meeting for October 10th, 2022 ## Agenda - [Working set triage](#working-set-triage) - [Static classes implementing interfaces](#static-classes-implementing-interfaces) - [Null-conditional assignment](#null-conditional-assignment) - [Lambda default parameters](#lambda-default-parameters) - [Working groups](#working-groups) - [`params` support for lambda default parameters](#params-support-for-lambda-default-parameters) - [Conclusion](#conclusion) ## Quote of the Day - "Can we have multiple spread proposals and name them different things? Mayonnaise, Mustard?" "I can't believe it's not allocating" ## Discussion ### Working set triage Today we wrapped up our triage of the working set. All of the remaining items are not in a working group. #### Static classes implementing interfaces https://github.com/dotnet/csharplang/issues/5783 We feel that there's about one design meeting's worth of open questions in this issue before we could reasonably let it be open for implementation, either from the compiler team or from the community, so we'll try to do that work at some point in the near future. #### Null-conditional assignment https://github.com/dotnet/csharplang/issues/6045 This one is fairly uncontroversial with little outstanding design work. #### Lambda default parameters https://github.com/dotnet/csharplang/issues/6051 Work for this one is in progress, and later on in the meeting we talked about adding `params` support for this. ### Working groups Now that we've gone through our working set, we've decided on the following working groups (listed in no particular order): * Roles and extensions * Collection literals * Nullability improvements * Discriminated unions * Bridging statements and expressions * Construction improvements * `ref struct` improvements * `params` improvements We also decided on a set of norms for working groups to follow with respect to notes and keeping track of their progress: every working group will add a folder under the [`meetings`](/meetings/) directory where they will put meeting notes. These notes will be anonymized, like LDM notes, but are likely to be much rougher than dedicated LDM notes. We will not create dedicated discussions when posting these working group notes. Users that want to comment on them can either comment on the issue for the group or create a discussion, as they choose. Working groups will each have a leader, and that leader will be responsible for coordinating with the broader LDM on when things need to be brought back to the full group. Some statistics on the groups: * Smallest group size: 2 members * Largest group size: 10 members * Mean group size: 5.75 members * At current rate of working group increase (1 in 2021, 8 in 2022) we will have 64 working groups come October 2023. ### `params` support for lambda default parameters https://github.com/dotnet/csharplang/issues/6051 To round out today, we discussed whether we should broaden the lambda default parameter work to include support for `params`, which sticks out a bit for not being supported in the proposal today. While we don't have a direct use case, we think that if we want to make this change, we should make it now as it is a breaking change for inferred delegate type scenarios when inferring from a method that has a `params` parameter. We don't think that waiting for a use case would change the semantic design strategy here, as it needs to be consistent with the work already approved for default lambda parameters. #### Conclusion We will add support for `params`, which will flow through the type system in the same way that default parameters do. ================================================ FILE: meetings/2022/LDM-2022-10-12.md ================================================ # C# Language Design Meeting for October 12th, 2022 ## Agenda - [Keywordness of `field`](#keywordness-of-field) ## Quote of the Day - "You're muted" "Damn, I said it so well!" ## Discussion ### Keywordness of `field` https://github.com/dotnet/csharplang/issues/140 https://github.com/dotnet/csharplang/issues/6530 Today, we considered compiler implementation issues for semi-auto properties. The compiler has run into a few circularity issues during implementation which are proving tricky to work out, and we wanted to revisit the decisions in LDM to see if there was an acceptable, easier option for implementation. After some initial discussion to understand the particular issue found here and brainstorm other approaches to the problem, we came up with the following list of solutions: 1. Make `field` a keyword 1b. Use a different keyword. Maybe something with two components to reduce chance of conflicts. 2. Ignore scopes for determining if there's a `field`. 2b. Could we do a cheap bind of scopes and see if there's a `field` reference without a visible declaration? 3. Add an introducer modifier 4. Do more work in the compiler to implement the existing rules. 5. Make an assumption when we're about to go into a cycle, and error if the assumption is broken later We think 1 and 3 are too big of reimaginings for the feature as of right now: previous LDM work came to the current keyword and feature shape after a lot of investigation, and we don't think that changing them up is warranted at this point, given that we don't think the engineering challenges are insurmountable. We also think option 5 will have unpredictable results: what assumptions the compiler makes may end up feeling like implementation details that leak into the specification. This leaves us with options 2 and 4. Further delving into option 2, we realized that it has many of the same problems as just keeping the original specification as is. All of the following scenarios should work with option 2, but will still require the same engineering effort as 4: ```cs class C { int Prop { get { var x = (field: 1, 2); // Tuple element named field, not a use of the field keyword var y = new { field = 1 }; // Anonymous type property named field var x = new Foo() { field = 1 }; // Regular type member named field if (x is { field: 1 }) {} // Regular type member named field return 1; // No usage of the field keyword, so a backing field should not be generated } } } ``` This realization showed us that what we really need to do is to do the work in the compiler to determine if a `field` keyword is in an expression context, in a purely syntactic fashion. This will require some effort, but unless the effort comes back as being extremely challenging (forcing us to look at options 1 or 3), we'll proceed with option 4. #### Conclusion We will continue pursuing the current work, and revisit if it proves to have a significant cost. ================================================ FILE: meetings/2022/LDM-2022-10-17.md ================================================ # C# Language Design Meeting for October 17th, 2022 ## Agenda - [Primary Constructors](#primary-constructors) - [Permit variable declarations under disjunctive patterns](#permit-variable-declarations-under-disjunctive-patterns) ## Quote of the Day - "I'll do my darndest not to jinx in various ways... In the first hour we'll knock this out" ## Discussion ### Primary Constructors https://github.com/dotnet/csharplang/issues/2691 As indicated [during triage](LDM-2022-09-26.md#ungrouped), the proposal for primary constructors has been updated with the latest thinking on the scenario, and we are now reviewing it. The new version uses capturing semantics for primary constructor parameters, and works for all types, not just records. In going over the proposal, we had a few comments: * [Last week](LDM-2022-10-12.md#keywordness-of-field), we talked about how knowing the backing fields of a type can be a problem for the compiler, and this proposal would introduce a similar concept. However, we think that this is solvable: `field` specifically needs to deal with backwards compatibility, while primary constructors are all new. We can simply say that primary constructor parameters are _always_ treated as captures for these types of purposes, and the removal of the parameters is an optimization strategy the compiler is free to use or not as it chooses. * Interestingly, F# primary constructors will not optimize away the primary constructor parameters if the type is a struct to avoid similar circularity issues when determining whether a type supports default values, and we may end up needing to do that in C#. * We're a little concerned about the potential for confusion when a field shadows a primary constructor parameter. For example: ```cs class C(int i) { protected int i = i; // References parameter public int I => i; // References field } ``` * We definitely feel that there at least needs to be a warning for the double-capture problem. It's fairly easy to accidentally code yourself into a hole here. * We need to think about how a `readonly` modifier on a `struct` will impact the primary constructor. * The `{ statements }` syntax as a primary constructor body is not particularly liked. There are two leading alternatives: * `ClassName { }` - IE, just like a regular constructor, but minus the parentheses. * `init { }` - More visually different from a constructor, which helps it stand out as being part of the primary constructor. Overall, we like the direction this proposal has taken, and would like to start playing with it. We're particularly interested in feedback on the double-capture issue, so a preview of the feature that users can try will help inform the capturing decision here. #### Conclusion We will proceed with a prototype with the proposed semantics, and a warning for the double-capture problem. ### Permit variable declarations under disjunctive patterns https://github.com/dotnet/csharplang/issues/4018 We took another look at updates to this proposal now that we have non-C# 11 time. The main thing we ended up debating was how "exact" should we make variable redeclaration need to be, as some edge cases can be hurt depending on our decisions. Some examples are: ```cs // Example A if (e is C c || e is Wrapper { NullableReferenceTypeProp: var c }) { } // Should there be an error since `var` is `C?` here // Example B if (e is S s || e is Wrapper { NullableValueTypeProp: var s }) { } // Should there be an error since this is S vs Nullable? // Example C if (e is C<(int a, int b)> c || e is Wrapper { Prop: var c }) { c.TupleProperty.a } // Is this ok? Where does the name a come from in the second example? // Example D if (e is Derived d) { d.DerivedMethod(); } else if (e is Wrapper { BaseTypeProp: Base d }) { } // Should d be widened to `Base`? What happens to the above method access? ``` We arrived at 4 possible options for the behavior: 1. Types must exactly match. 1. Variant 1b: types must exactly match _except_ for top-level reference nullability. Nested reference nullability differences are subject to the same nullability warnings as standard conversions. 2. Types must be identity-convertible. This allows for tuple-name differences. 3. We allow a special rule for `var`: `var` doesn't contribute type information when being unified across multiple declarations. 4. We use best common type across all declarations, like ternaries. In discussion, we started with 4. However, we were dissatisfied with the behavior of example D above with that rule, as it either means that a related `case` or `else if` can affect the meaning of the code inside the first `if` block, or it means that we need to introduce variables that change their type based on the current execution flow. We felt that 3 wasn't particularly clear on the behavior, and 2 was potentially confusing: what if there are multiple sets of tuple names? How should we decide which names to use? This was less strong, however, and we are open to allowing differences in the future. Ultimately, we felt that variant 1b is the best option, and we will look for user feedback to see if we should loosen the rules more. #### Conclusion Variant 1b of the rules accepted. ================================================ FILE: meetings/2022/LDM-2022-10-19.md ================================================ # C# Language Design Meeting for October 19th, 2022 ## Agenda - [Open questions in list patterns on `IEnumerable`](#open-questions-in-list-patterns-on-ienumerable) - [Allowing patterns after slices](#allowing-patterns-after-slices) - [Allowing slicing to capture](#allowing-slicing-to-capture) ## Quote of the Day - "You should be able to criticize your own children." "All your children are terrible" ## Discussion ### Open questions in list patterns on `IEnumerable` https://github.com/dotnet/csharplang/issues/6574 https://github.com/dotnet/csharplang/pull/6365 Today, we took a look at the list patterns on `IEnumerable` proposal, and looked through a few open questions on the topic. #### Allowing patterns after slices The first open question is whether we should allow patterns to follow a slice, such as `enumerable is [1, 2, .., 3]`. There's a few main concerns with allowing this: it will significantly complicate the implementation of the emitted code, it could be a potential footgun for infinite `IEnumerable` scenarios, and, depending on implementation strategy, it could actually perform _worse_ than equivalent LINQ code would, as LINQ does typechecks for underlying types and uses indexing when possible. For the first point, we're not overly concerned: the user would have to write equivalent code, and has more opportunity to introduce bugs when doing so, whereas a generalized and tested compiler solution is likely more robust. The second problem we're also somewhat okay with: the footgun has existed since .NET 1.0, with `IEnumerable` and `foreach`. New places things can be enumerated continue to have this footgun, but it's always the same one. The last concern though, that using a list pattern might be less efficient than using LINQ, is a big one. We're okay with the infinite enumerable issue, but we don't want patterns to be avoided because they're seen as the least-efficient way to implement a check. We should be able to optimize these checks for when the underlying `IEnumerable` _doesn't_ need to be fully enumerated to retrieve individual elements. We were planning on implementing a helper for doing these enumerations, and we should work with the BCL team to ensure that it is as efficient as it can be. For example, LINQ has internal interfaces that it uses to back queries depending on whether the original enumerable was list-like, and it would be good to have list patterns be able to take advantage of the same optimizations that Select or IndexOf can. ##### Conclusion We're okay with restricting patterns after slices for now, but we should plan on having them eventually, and work with the BCL team to put a helper (type or set of methods) into the standard libraries that list patterns can use for being as efficient as possible without tying the language to implementation details of specific frameworks. #### Allowing slicing to capture Finally, we looked briefly at whether or not a slice pattern on an `IEnumerable` should be able to capture the sliced section. One concern with this is that it would violate the standard way we do slicing: a `Slice` method should return an instance of the same type it was called on. Slicing an array returns an array, slicing a Span returns a Span, etc. But we couldn't do that here, as the underlying `IEnumerable` could be anything. It would also be weird if we allowed this when we don't allow slicing to capture for any list pattern: we only allow slicing for types that are countable, indexable, _and_ sliceable. We think that, after we work through a helper type to address the first point, we might be in a better position to determine whether we should generally expand slicing support to all list patterns, or whether we don't think that should be generally supported in any non-sliceable input context. ##### Conclusion We'll revisit after the helper type is designed and we have a better idea on efficiency. ================================================ FILE: meetings/2022/LDM-2022-10-26.md ================================================ # C# Language Design Meeting for October 26th, 2022 ## Agenda - [Null-conditional assignment](#null-conditional-assignment) ## Quote of the Day - "Can you see this word I've highlighted?" "3..., 4..., 5..., 6..., 7..., 8..., 9..., 10..., 11..., 12..., 13..., 14..., 15! 15 second delay in the video" ## Discussion ### Null-conditional assignment https://github.com/dotnet/csharplang/issues/6045 Today we took a complete look at the proposal for null-conditional assignment. Overall, there were two points of concern with the feature: 1. Is the intuition around what `a?.b = c();` actually does at runtime good? Most of the LDT was in agreement what the semantics of this would be without discussion, which is a good sign. However, a few members did say that their first read was that `c()` would be executed regardless of whether `a` was null. We think this is very similar to the reaction to `?.` in C#, where many found the behavior intuitive, but a few users had to learn. We're therefore ok with the intuition of the behavior of this feature. 2. We took a look at nested scenarios, such as `a?.b = c?.d = e?.f;`, and thought about whether the semantics would be confusing. There are a few possible results from this nested assignment: 1. If `a` is `null`, nothing else will be evaluated. 2. If `a` is non-`null` and `c` is `null`, `a.b` will be assigned `null` and `e?.f` will not be evaluated. 3. If `a` and `c` are non-`null`, `e?.f` will be evaluated and the result will be assigned to `c.d` and `a.b`. While we think this syntax form is probably not a good one, and we won't encourage users to use it, we think that it's a natural consequence of the way our grammar works, and don't see any good reason to forbid it. #### Conclusion The design is approved as is. ================================================ FILE: meetings/2022/LDM-2022-11-02.md ================================================ # C# Language Design Meeting for November 2nd, 2022 ## Agenda - [Partial Properties](#partial-properties) - [Default parameter values in lambdas](#default-parameter-values-in-lambdas) ## Quote of the Day - "If only there was a way that could be the quote of the day without incriminating someone." "I'm trying to make my jokes unquotable." ## Discussion ### Partial Properties https://github.com/dotnet/csharplang/issues/6420 We started today by going through the partial properties proposal in detail, and trying to address the open questions in the proposal. As part of the motivation, we took a quick look at how popular the field-based approach to property generation is: CommunityToolkit.Mvvm, which added an INPC generator based on fields in version 8.0.0, has over 159,000 downloads as of these notes, 50,000 more than the previous major version of the library. They've also started using diagnostic suppressors to allow property-based attributes to be put on fields, suppressing the warnings about incorrect attribute targets and copying those attributes over to the implementation of the property. We don't think this is great: it's working around the language, rather than with the language, and we'd like to address it. We considered whether we should support implementations being full auto-properties. There are some uses case for this: for example, the regex generator might want to be able to say `public Regex Prop { get; } = ...;` in the generated file. And, language-wise, the issues with allowing this are fairly minimal. However, we think that there's a decent number of tooling and experience issues with it, as the tooling will need to pick a version to consider the declaration, and no matter what we do it will likely feel arbitrary. With semi-auto properties the workaround is not hard either: it's just `public Regex Prop { get => field; } = ...;`. Given this, we'll stick with the restriction that the implementation part cannot be a fully auto-property. We looked at the open question on expanding the feature to cover indexers. We think this is a reasonable expansion: it raises no new questions, has fairly minimal impacts on the implementation, and keeps the language regular. It does mean that we have only one non-field member type that can't be partial now: `event`s. We think extending the feature to events goes a bit farther than we'd like for now. We've had no requests for the expansion, and adding it would bring a number of new questions on field-like events, what to allow/disallow, and others that we're not ready to answer at this time. Finally, we think that while we're doing `partial` properties, there is opportunity to clean up some `partial` papercuts, such as modifier ordering. We will make a list of these papercuts and bring them to a future LDM for consideration. #### Conclusion Feature is approved as specified, indexers will be included. ### Default parameter values in lambdas https://github.com/dotnet/csharplang/issues/6051 https://github.com/dotnet/csharplang/issues/6651 Finally today, we have a question that arose from implementation, around whether lambda default parameters can affect the existence of a conversion. We're a bit concerned by the behavior as specified, as it means that changing a parameter default value will have an impact on overload resolution. We're not OK with this impact, and want to revise the rules here to avoid that. After some discussion, we came to the conclusion that the conversion from lambda to delegate type should always exist, regardless of `params`/default parameter value differences. We then considered whether those differences should cause a warning or an error later in the pipeline, _after_ overload resolution. We're mostly in favor of a warning here, as the code the code is unambiguous in meaning, if not anything we expect to be written outside of compiler unit tests. We also think that, for method group conversions, we should not warn at all. #### Conclusion The existence of conversions from lambdas to delegate types will be unaffected by `params` or default parameter values. Differences in those will cause a warning later in the pipeline. Lambdas that do not have `params`/default parameter values will not warn when converted to delegate types with them. We will not warn for method group conversions that differ by `params` or default parameter values. ================================================ FILE: meetings/2022/LDM-2022-11-30.md ================================================ # C# Language Design Meeting for November 30th, 2022 ## Agenda - [Revise `MemberNotNull` for `required`](#revise-membernotnull-for-required) - [`params Span`](#params-spant) ## Quote of the Day - "I understand I'm broken." ## Discussion ### Revise `MemberNotNull` for `required` https://github.com/dotnet/csharplang/issues/6754 https://github.com/dotnet/csharplang/issues/3630 Today we considered a revision to `required` members, part of the recently-shipped C# 11, before the current behavior gets too broadly used for us to make changes without a language version bump. Specifically, we looked at whether we can use `MemberNotNullAttribute`s on member declarations to inform nullability error suppression for fields initialized by required properties. While we do think this will become less of an issue with future language versions that take up the `field` feature, we think this is a good workaround for the moment. It uses `MemberNotNullAttribute` as a linking mechanism, which was the intended purpose of the attribute, and we're in favor of making the change now while we still can. #### Conclusion Proposed change is accepted, targeted at 7.0.200. ### `params Span` https://github.com/dotnet/csharplang/issues/1757 https://github.com/dotnet/csharplang/blob/6ab4bbf1ec5183c65ad63887b8ec9e73ca0aab67/proposals/params-span.md The params improvements working group has come back from their initial investigations with some initial work. Of the potential work, they looked at `params ReadOnlySpan` as the most interesting thing on the plate, as it makes for real performance improvements, while other ergonomic benefits will potentially be addressed by collection literals. The performance benefits here are mainly targeted at new APIs, as existing APIs (such as `Console.WriteLine`) have already manually optimized these cases by introducing a number of overloads, each taking another argument, until finally leaving a last `params` API. While these APIs would likely still take advantage of them (the runtime is tracking what APIs will want to use the feature in [this issue](https://github.com/dotnet/runtime/issues/77873)), the main improvement will be in new APIs that don't need to write so many overloads in the first place. Some questions we raised during discussion: * Why do we want to support `UnscopedAttribute` on `params`? We don't think this is a real scenario, and it adds significant complexity to the rules. * We need to make sure that our adjustments to Better Function Member aren't introducing any new ambiguities. We don't think they are at first look, but this area often has hidden breaks so we need to make sure. * We spent a lot of time talking about the proposed strategy of always allocating on the stack. There was particular concern with how this interacts with another major part of the feature: introducing `params ReadOnlySpan` parameters and recompiling will prefer the span overload. For callsites with large numbers of params parameters this could end up causing `StackOverflowExceptions`. There are a few possible mitigations: * The runtime can introduce a side-stack for these allocations, so params backing storage is still on a stack, just a different stack. * We could (and probably should) make the wording here much looser, allowing the compiler more latitude in choosing what to do here. * This would then allow the compiler to call a runtime helper to make the decision, as different platforms have different stack sizes and that would influence what they would want to be allocated where. * Should users be able to turn off the feature at the compilation-level such that backing arrays are always heap allocated? This would give a good way for users to test to see if the feature is causing issues for them. * Ultimately we decided that we should lean on the runtime to make allocation location decisions, and we can look at a global switch if we end up seeing problems in preview/release. #### Conclusion Feature is good to proceed to implementation. ================================================ FILE: meetings/2022/LDM-2022-12-14.md ================================================ # C# Language Design Meeting for December 14th, 2022 ## Agenda - [Breaking change for raw string literals](#breaking-change-for-raw-string-literals) - [Program as internal by default](#program-as-internal-by-default) ## Quote of the Day - "Give everyone the ability to quickly filibuster it" ## Discussion ### Breaking change for raw string literals https://github.com/dotnet/roslyn/pull/65973 An email discussion approved this change. No significant concerns were raised in LDM or on email. ### Program as internal by default https://github.com/dotnet/csharplang/issues/6769 Today we revisited the defaults for top-level statements, looking at scenarios that are impacted by our [original decision](../2021/LDM-2021-07-12.md#speakable-names-for-top-level-statements). Some friction has been encountered because of this, particularly in scenarios around testing, and we are generally interested in addressing the scenario in some fashion. There is, however, some debate on the best way to address these scenarios; language changes are big hammers, and we intentionally left knobs in the language (other partial declarations) to address concerns with the default accessibility of the `Program` type. There are visceral reactions to the statement "Make this API public for testing", as program authors generally want to be intentional with their public surface area. This is true for executables too, as executables can be referenced by other programs. Given this, we will take the proposal back for more design, and perhaps find a solution in tooling for the problem, rather than changing the language here. #### Conclusion Proposal will be revisited after more design work (which may decide on a non-language solution to the problem). ================================================ FILE: meetings/2022/README.md ================================================ # C# Language Design Notes for 2022 Overview of meetings and agendas for 2022 ## Wed Dec 14, 2022 [C# Language Design Meeting for December 14th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-12-14.md) - Breaking change for raw string literals - Program as internal by default ## Wed Nov 30, 2022 [C# Language Design Meeting for November 30th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-11-30.md) - Revise `MemberNotNull` for `required` - `params Span` ## Wed Nov 2, 2022 [C# Language Design Meeting for November 2nd, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-11-02.md) - Partial Properties - Default parameter values in lambdas ## Wed Oct 26, 2022 [C# Language Design Meeting for October 26th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-26.md) - Null-conditional assignment ## Wed Oct 19, 2022 [C# Language Design Meeting for October 19th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-19.md) - Open questions in list patterns on `IEnumerable` - Allowing patterns after slices - Allowing slicing to capture ## Mon Oct 17, 2022 [C# Language Design Meeting for October 17th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-17.md) - Primary Constructors - Permit variable declarations under disjunctive patterns ## Wed Oct 12, 2022 [C# Language Design Meeting for October 12th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-12.md) - Keywordness of `field` ## Mon Oct 10, 2022 [C# Language Design Meeting for October 10th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-10.md) - Working set triage - Static classes implementing interfaces - Null-conditional assignment - Lambda default parameters - Working groups - `params` support for lambda default parameters - Conclusion ## Wed Oct 5, 2022 [C# Language Design Meeting for October 5th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-05.md) - Review of `ref` fields - `RefSafetyRulesAttribute` - Return-only scope ## Wed Sep 28, 2022 [C# Language Design Meeting for September 28th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-28.md) - Working set triage - Discriminated Unions - Collection literals - Nullability improvements - Ungrouped ## Mon Sep 26, 2022 [C# Language Design Meeting for September 26th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-26.md) - Working set triage - Roles & Extensions - Discriminated Unions - Bridging statements and expressions - Construction improvements - `ref struct` improvements - `params` improvements - Ungrouped ## Wed Sep 21, 2022 [C# Language Design Meeting for September 21st, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-21.md) - Unchampioned issue triage - Track subtype exhaustiveness for classes with only private constructors - ReadOnlySpan initialization from static data - Embedded Language Indicators for raw string literals - Implicit Parameters - Unsafer Unsafeness - Conclusion ## Wed Aug 31, 2022 [C# Language Design Meeting for August 31st, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-31.md) 1. Short term work triage 1. Partial properties 2. Using aliases for any type 3. Null-conditional assignment 4. `await?` 5. `params IEnumerable` 6. Discriminated Unions 7. Roles 8. `pragma warning enable` 9. Ignored directives support 10. Primary constructors 11. Switch expression as a statement ## Wed Aug 24, 2022 [C# Language Design Meeting for August 24th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-24.md) 1. C# feature triage ## Wed Aug 10, 2022 [C# Language Design Meeting for August 10th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-10.md) 1. `required` property default nullability ## Wed Aug 3, 2022 [C# Language Design Meeting for August 3rd, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-03.md) 1. `ref` fields specification updates ## Wed Jul 27, 2022 [C# Language Design Meeting for July 27th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-07-27.md) 1. `scoped` for local declarations ## Wed Jul 13, 2022 [C# Language Design Meeting for July 13th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-07-13.md) 1. Lambda default parameters ## Jun 29, 2022 [C# Language Design Meeting for June 29th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-06-29.md) - UTF-8 literal concatenation operator ## Jun 6, 2022 [C# Language Design Meeting for June 6, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-06-06.md) 1. Open issues for `ref` fields 2. Open issues for static virtual members 3. Concatenation of Utf8 literals ## May 23, 2022 [C# Language Design Meeting for May 23rd, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-05-23.md) 1. Open issues for `ref` fields ## May 11, 2022 [C# Language Design Meeting for May 11th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-05-11.md) 1. Inconsistency around accessibility checks for interface implementations 2. `ref readonly` method parameters 3. Pattern matching with UTF-8 String Literals ## May 9, 2022 [C# Language Design Meeting for May 9th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-05-09.md) 1. Numeric IntPtr 2. Ref readonly parameters ## May 2, 2022 [C# Language Design Meeting for May 2nd, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-05-02.md) - Effect of `SetsRequiredMembers` on nullable analysis - `field` questions - Partial overrides of virtual properties - Definite assignment of manually implemented setters ## Apr 27, 2022 [C# Language Design Meeting for April 27th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-27.md) - Default parameter values in lambdas - Null-conditional assignment ## Apr 25, 2022 [C# Language Design Meeting for April 25th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-25.md) - `ref readonly` method parameters - Inconsistencies around accessibility checks for interface implementations ## Apr 18, 2022 [C# Language Design Meeting for April 18th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-18.md) 1. Issues with Utf8 string literals 2. Ref and ref struct scoping modifiers ## Apr 13, 2022 [C# Language Design Meeting for April 13th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-13.md) 1. Parameter null checking 2. File-scoped types ## Apr 11, 2022 [C# Language Design Meeting for April 11th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-11.md) 1. Relax restrictions on braces on raw interpolated strings 2. Self-type stopgap attribute ## Apr 6, 2022 [C# Language Design Meeting for April 6th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-06.md) 1. Unresolved questions for static virtual members 2. Parameter null checking ## Mar 30, 2022 [C# Language Design Meeting for March 30th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-30.md) 1. Definite assignment in struct constructors calling `: this()` 2. `file private` accessibility ## Mar 28, 2022 [C# Language Design Meeting for March 28th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md) 1. Variable declarations under disjunctive patterns 2. Type hole in static abstracts 3. Self types ## Mar 23, 2022 [C# Language Design Meeting for March 23rd, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-23.md) 1. Open questions in required members 1. Emitting `SetsRequiredMembers` for record copy constructors 2. Should `SetsRequiredMembers` suppress errors? 3. Unsettable members 4. Ref returning properties 5. Obsolete members ## Mar 21, 2022 [C# Language Design Meeting for March 21st, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-21.md) 1. file private visibility 2. Open question in semi-auto properties 3. Open question in required members ## Mar 14, 2022 [C# Language Design Meeting for March 14th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-14.md) 1. file private visibility ## Mar 9, 2022 [C# Language Design Meeting for March 9th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-09.md) 1. Ambiguity of `..` in collection expressions 2. `main` attributes 3. `nameof(param)` ## Mar 7, 2022 - Design review. No published notes. ## Mar 2, 2022 [C# Language Design Meeting for March 2nd, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-02.md) 1. Open questions in `field` 1. Initializers 2. Property assignment in structs ## Feb 28, 2022 [C# Language Design Meeting for February 28th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-28.md) 1. Ref fields 1. Encoding strategy 2. Keywords vs Attributes 3. Breaking existing lifetime rules ## Feb 23, 2022 [C# Language Design Meeting for February 23rd, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-23.md) 1. Pattern matching over `Span` 2. Checked operators ## Feb 16, 2022 [C# Language Design Meeting for February 16th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md) 1. Open questions in `field` 2. Triage 1. User-defined positional patterns 2. Delegate type arguments improvements 3. Practical existential types for interfaces 4. Static abstract interfaces and static classes ## Feb 14, 2022 [C# Language Design Meeting for February 14th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-14.md) 1. Definite assignment in structs 2. Checked operators ## Feb 9, 2022 [C# Language Design Meeting for February 9th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md) 1. Continue discussion of checked user-defined operators 2. Review proposal for unsigned right shift operator 3. Review proposal for relaxing shift operator requirements 4. Triage champion features ## Feb 7, 2022 [C# Language Design Meeting for February 7th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md) 1. Checked user-defined operators ## Jan 26, 2022 [C# Language Design Notes for January 26th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md) 1. Open questions in UTF-8 string literals ## Jan 24, 2022 [C# Language Design Notes for January 24th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md) 1. Required members metadata representation 2. Default implementations of abstract statics 3. Triage 1. Nested members in with and object creation 2. Binary Compat Only 3. Attribute for passing caller identity implicitly 4. Attributes on Main for top level programs ## Jan 12, 2022 [C# Language Design Notes for January 12th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-12.md) 1. Open questions for `field` 1. Initializers for semi-auto properties 2. Definite assignment for struct types 2. Generic Math Operator Enhancements ## Jan 5, 2022 [C# Language Design Notes for January 5th, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-05.md) 1. Required Members ## Jan 3, 2022 [C# Language Design Notes for January 3rd, 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-03.md) 1. Slicing assumptions in list patterns, revisited 2. Parameterless struct constructors, revisited ================================================ FILE: meetings/2023/LDM-2023-01-09.md ================================================ # C# Language Design Meeting for January 9th, 2023 ## Agenda - [Working group re-evaluation](#working-group-re-evaluation) ## Quote of the Day - "Different variations of crazy come together to make a whole sane person." ## Discussion ### Working group re-evaluation We've had a few months with working groups, and think now is a good time to do a small retrospective on whether the strategy is working well for us. One concern that we have is that team members are feeling overloaded on working groups. Several members felt a good amount of FOMO (fear of missing out) during the sign up process, expecting that all the interesting discussions would occur in the working group, rather than at the LDM. This has lead to the working groups not actually being able to get anything done and taking up more development time that was intended. Some of the working groups have also met a bit too often, when there wasn't enough driving interest. Working groups are creative effort, and trying to force creative effort when there isn't enough enthusiasm doesn't accomplish anything. We've settled on some changes in our process to help the groups be more nimble: 1. Reduce group memberships. While we're not putting constraints on minimum or maximum memberships, LDT members should aim to reduce their workload with the number of groups they join. Group membership should not be a passive topic where you just listen in: it should be an active participant effort. 2. Working groups should bring their progress to the full LDM more often. A significant driver of the over-subscription was FOMO, but the working groups weren't intended to replace the full LDM discussions on topics. Proposals will still be debated in full with the full LDM, and more frequent check-ins will help make sure that the proposals are still a product of the entire group. While the working group can delve into spaces and form opinions, authority is still with the LDM for the final shape of proposals. 3. Reduce meeting frequency. Most groups went out and scheduled recurring meetings every week. This was too often for most groups, and resulted in overburdening the group leaders who needed to establish topics and record notes. Teams should meet as often as makes sense for the team: if there's been no progress on an idea, we shouldn't try to force the creative process. As part of this, some groups may stop meeting altogether, and that's ok. We hope this set of changes will enable the working groups to be more effective, and make sure that LDT members have enough time to contribute to working groups while continuing to work on their regular teams. ================================================ FILE: meetings/2023/LDM-2023-01-11.md ================================================ # C# Language Design Meeting for January 11th, 2023 ## Agenda - [`using` aliases for any types](#using-aliases-for-any-types) - [Pointer types in aliases](#pointer-types-in-aliases) - [Reference nullability in aliases](#reference-nullability-in-aliases) - [Value nullability in aliases](#value-nullability-in-aliases) ## Quote of the Day - "It feels like someone is going to write a blog post about this." ## Discussion ### `using` aliases for any types https://github.com/dotnet/csharplang/issues/4284 Today, we took a look at expanded `using` aliases, and open questions in the area. The LDM is generally in favor of the topic, but there are a few questions to resolve around specific areas of the proposal. #### Pointer types in aliases First, we looked at pointer types in using aliases. We've heard from function pointer users, since before the first preview was publicly released, that they would love to be able to `using` alias long function pointer types. This raises an interesting question: where are unsafety errors reported for such types? We don't have a `using unsafe` today, so would we want to introduce that? Or would we want to check at the use-site, and not give any warning or error in the `using` itself? We enumerated a few options: 1. Don't allow pointer types in `using` aliases, even with #4284 implemented. 2. Allow pointer types in `using` aliases, but introduce `using unsafe` and require it be present. 3. Always allow pointer types in `using` aliases, and check for unsafety at the use site. An initial read was nearly unanimously in favor of 3, but further discussion revealed key concern: An unused `using` alias might never be checked for pointer types, even if the project doesn't have `unsafe` turned on. This felt dangerous and likely to become an odd C# quirk that we would end up regretting. There was also some preference that we ensure that any pointer type syntactic usage is explicitly tied to `unsafe` in some fashion. We also think that `using unsafe` will actually be easier to implement, as it will avoid some circularity issues that we'd otherwise need to be concerned about. Given these concerns, we are planning to go with option 2, and potentially relax during preview or in a future version of C# based on user feedback. ##### Conclusion Option 2, potentially relaxing in the future based on user feedback. #### Reference nullability in aliases Next, we turned our attention to reference type nullability in aliases. Nested nullability is already allowed: `using MyList = List;`, for example. However, should we allow `using NullableString = string?;`? There are some good reasons not to: 1. Complicates implementation and conceptual load by forcing us to guard against `?` on nullable aliases. 2. Usage in pattern matching is broken. These aliases couldn't be used directly, and `{ }` patterns would need to fall to the underlying type. 1. This might further change in `#nullable disable`d contexts. 3. Display in quick info might change with nullable flow states: when an expression has been analyzed to be not null, it could require falling back to the non-aliased underlying type, which we think is unexpected behavior. This behavior would also vary between `#nullable` contexts. ##### Conclusion Top-level nullability cannot be specified in `using` aliases. #### Value nullability in aliases Today, `using NullableInt = Nullable;` is legal. However, given our previous decision, do we want to also forbid `using NullableInt = int?;`, and require users to continue using `Nullable` for such locations? After some discussion, we think this is different than the previous point: `string?` and `string` are the same runtime type, while `int` and `int?` are actually different runtime types; there is no difference in behavior around `#nullable` contexts like there is for reference types. ##### Conclusion Allowed. ================================================ FILE: meetings/2023/LDM-2023-01-18.md ================================================ # C# Language Design Meeting for January 18th, 2023 ## Agenda - [Nullable post-conditions and `async`/`await`](#nullable-post-conditions-and-asyncawait) - [Semi-colon bodies for type declarations](#semi-colon-bodies-for-type-declarations) ## Quote of the Day - "I guess if I want to stay consistent with myself and not be a hypocrite for the third time today." "You're counting the times?" "Yeah... I don't want to go for the hat trick." ## Discussion ### Nullable post-conditions and `async`/`await` https://github.com/dotnet/csharplang/issues/6888 We took a look at a proposal around enhancing nullable support for `async` methods that return `Task`-like types, as existing nullability attributes are often incorrectly applied for this type of method. There are some immediate concerns that come to mind: 1. There are a lot of methods that defer awaiting a `Task` or `Task`-like type until after the returned thing. For example, `ConfigureAwait`, `ValueTask.AsTask`, `Task.WhenAll`, user libraries to add awaiters to `ValueTuple`s of tasks, etc. We could special case the BCL methods in the compiler, but that is a lot of special cases and wouldn't help any user-defined `Task`-like types or methods. 2. Some post-condition attributes, such as `NotNull` on parameters, might be checked up front by the method, before it hits an `await`, and be perfectly fine today (particularly since `async` methods cannot have `ref` parameters). The proposal currently suggests changing this, but that doesn't seem generally correct. 3. We don't have a good way to know whether a method is `async` today, so we can only use a broad heuristic to determine whether to apply `MemberNotNull` and other attributes before or after `await`ing the result. Further, whether or not a method is `async` isn't quite the heuristic we're looking for: what we're actually looking for is whether or not the returned `Task`-like type needs to be `await`ed before the nullability conditions are propagated. For example, a helper method to fill in common parameters and otherwise pass through a call to another member is very possibly not marked as `async`, but should have the same behavior as the `async` method it's proxying. Another spitballed proposal gained a bit of traction as we were discussing the above 3 points: adding a new `RequireAwait` property to our nullability attributes, with some appropriate defaults, and using that to propagate information on nullability. This has some advantages in being very precise, and likely being easy to guide users towards (the warning for `await`ing without fulfilling a `MemberNotNull` could mention setting that property, for example, or if a user sets that property and doesn't need to we could warn), and not requiring significant new amounts of metadata to put on every `async` method to mark it as such. #### Conclusion This will go back to the working group for now to continue iterating on the above idea and address the other contention points. ### Semi-colon bodies for type declarations https://github.com/dotnet/csharplang/discussions/6871 There was some initial confusion on this discussion, as the current implementation actually missed the quoted line in the spec forbidding `;` on non-`record` types. Further, we realized that the spec, as written today, is actually a breaking change; it would make the following code illegal: ```cs record R1; // Legal today ``` We think there are potential cases where the `;` body would be useful, mainly in `partial` types that have generated implementations. There are some potential concerns with future interference on top-level statements, as we may someday allow statements to follow type declarations. That would change `class C; {}` from a guaranteed error to potentially introducing top-level statements in the file, likely causing confusing compilation errors. However, we trust ourselves to handle this when the time comes. We also considered the two other declaration types that currently do not permit `;` bodies: `interface`s, and `enum`s. `interface`s have similar justification to `class`es and `struct`s, where a `partial` interface could add new attributes and generate the rest. `enum`s are not allowed to be `partial` today, so it is unlikely to get much use, but at the same time we don't see a good reason to leave the syntax inconsistent and special case `enum`s here where no other `type` declaration is. #### Conclusion Allow semicolon bodies for all type declarations. ================================================ FILE: meetings/2023/LDM-2023-02-01.md ================================================ # C# Language Design Meeting for February 1st, 2023 ## Agenda - [Position of `unsafe` in aliases](#position-of-unsafe-in-aliases) - [Roles and extensions](#roles-and-extensions) ## Discussion ### Position of `unsafe` in aliases https://github.com/dotnet/csharplang/blob/main/proposals/using-alias-types.md#supporting-aliases-to-types-containing-pointers As part of allowing pointer types in using aliases, we want to enable aliases to carry the `unsafe` keyword. But where should it go - before or after the `using` keyword? Today, one modifier appears *before* the `using` keyword (`global`), and one appears after (`static`). Looking at these, `global` affects *where* the using takes effect, and applies to all forms of using directives. Conversely, `static` applies only to one form of using directive, and only has meaning for that one. It seems that `unsafe` aligns more with the second category, as it only applies to using *aliases*. Additionally, it is useful for all `using`s (and `global using`s respectively) to align visually with the operative keyword(s) in the same horizontal position. #### Conclusion Go with `using unsafe` rather than `unsafe using`. ### Roles and extensions https://github.com/dotnet/csharplang/pull/6880 We reviewed the roles and extension proposal in its current state to provide feedback to the working group. The below sections contains points for clarification and open questions for the group to pursue. #### Terminology We are not necessarily in love with the term "role", and may well change it. It could e.g. be "view". However we'll save that discussion for later. #### Phases The proposal operates with three phases: - "A" provides only static extension members (static methods, properties, fields, etc.) - "B" provides extensions and roles, allowing them to add all static and instance members, except for instance fields, auto-properties or field-like events. Constructors are also an open question. - "C" allows extensions and roles to implement interfaces The phase A vs B split is not just there for potential sequencing over multiple releases: the phase A scenarios have potential for more downlevel friendly code gen, since they do not need to represent an underlying value. Even if we ship phase A and B at the same time, it may be worth ensuring that "static only" scenarios are more easily targeted to older runtimes. #### Syntax The proposed syntax allows for multiple base roles, establishing an inheritance hierarchy among roles. Type members can't be virtual (no virtual, abstract or override modifiers), because the underlying values exist independently of the roles, and therefore cannot have their behavior modified by role declarations. Roles can be nested in roles. Should there be prefix naming conventions for roles and extensions (e.g. `RCustomer`, `EOrder` - or `XOrder`?), like there are for interfaces and type parameters (`ICustomer`, `TOrder`)? #### Underlying type The proposal doesn't use "inheritance" to describe the relationship to the underlying type, even though members are "inherited" in the sense that they are available on the role as well. This is so that we can distinguish where e.g. protected doesn't apply, and to emphasize that roles can augment even sealed types and structs. We need clear differentiating terminology, but this may or may not be the right one. Some of the listed restrictions on underlying types are likely temporary, and imposed by envisioned emit strategy (pointers, ref structs). Others are likely permanent (dynamic, nullable reference type). The doc should clarify that. The rule about extension type parameters all being used by the underlying type is so that we can infer backwards when searching for extensions. We have similar needs for extension methods today. #### Conversions There is an identity conversion between a role and its underlying type, as well as its base roles. The current proposal does not allow sideways identity conversion between roles with the same underlying type, which would mean identity conversion isn't transitive (unlike in the existing language). If this is a problem, we may consider always allowing identity conversion when the underlying type matches, but perhaps give a warning on sideways conversion to preserve some "type safety" across role hierarchies. Does the identity conversion thing clash with the ability to overload methods on role parameter types? Not necessarily. Betterness has an "exact match" clause that would allow you to distinguish. It's desirable to have overload resolution on roles, and we hope it's possible from a metadata perspective, but that remains to be investigated. #### How is an extension "activated" Whoever declares an extension decides that it is an extension. Whenever it is in scope or imported with a `using` directive, the extension applies to its underlying type. This is equivalent to how extension methods work today. Another design would be to have just one kind of declaration, and have the user decide whether to apply the role as an extension. We tentatively agree that whoever declares an extension knows that it is going to be used as an extension, and designs it for that purpose. Also this is less of a deviation from today's behavior. #### Type tests Will you be able to tell roles apart with pattern matching? No, from a runtime perspective they represent the same underlying type, which is the thing we can check for at runtime. #### Why are extensions named types? Why do we think extension types are important? Mostly disambiguation. You can explicitly convert from an underlying type to the extension, and now the extension's members go to the foreground, hiding other extensions. This also underscores the close relationship between extensions and roles. Extensions are essentially roles that get automatically applied. If/when we get to phase "C", this becomes more important, as an extension can implement additional interfaces on behalf of its underlying type, and therefore needs to be able to be passed as a type argument for type parameters constrained by such interfaces. Therefore it needs to be a type. #### Types and roles The type of `this` within a role's instance members is the role type itself. A role satisfies the same constraints as the underlying type. That means value vs reference type (`struct` vs `class` constraint) depends on the underlying type, not the role itself. #### Interaction with extension methods Old-fashioned extension methods will have some restrictions in their interaction with roles and extensions. This is TBD. #### Member lookup Today you cannot call an extension method with a simple name, even if you're inside of the extended type. We're planning to keep that restriction: The lookup would get weirdly two-dimensional and it doesn't seem worth the trouble or potential surprises. Member lookup on a role goes to the role, then base roles, *then* the underlying type. Member lookup (on all types) is also augmented so that if we don't find anything, we'll look in compatible extensions. Extension member lookup is a generalization of today's extension method lookup, except that we can look for other kinds of members, and that we can look at enclosing types. #### Scenarios Previous documents on roles describe several scenarios in more detail. It would be good to gather those (and more) scenarios, and evolve them with the proposal. Examples would also help make it a bit easier to follow the behaviors and restrictions. #### Conclusion With the above feedback in mind, the working group is encouraged to continue fleshing out the proposal. ================================================ FILE: meetings/2023/LDM-2023-02-15.md ================================================ # C# Language Design Meeting for February 15th, 2023 ## Agenda - [Open questions in primary constructors](#open-questions-in-primary-constructors) - [Capturing parameters in lambdas](#capturing-parameters-in-lambdas) - [Assigning to `this` in a `struct`](#assigning-to-this-in-a-struct) ## Quote of the Day - "Where did you think we put that? Magic?" ## Discussions ### Open questions in primary constructors https://github.com/dotnet/csharplang/issues/2691 https://github.com/dotnet/csharplang/blob/2867220fe142dfefd47d8cb20fb88393e4156059/proposals/primary-constructors.md#open-questions #### Capturing parameters in lambdas We first considered the question of what happens when a lambda in a field or property initializer captures a parameter that is also captured by the containing type. For example: ```cs partial class C1 { public System.Func F1 = Execute1(() => p1++); // Capture 1 } partial class C1 (int p1) { public int M1() { return p1++; } // Capture 2 static System.Func Execute1(System.Func f) { _ = f(); return f; } } ``` To follow standard capture rules, both `p1`s will need to refer to the same storage location, and observe the changes from one to the other. There's really two questions here: first, should we allow this at all? And second, how will we implement it (and do any implementation strategies have semantic implications)? For the first question, we started by thinking about whether to disallow the scenario where a primary constructor parameter is captured by two locations at once. But we're concerned about spooky action at a distance here, where adding a field initializer can cause compilation errors in a different location. We also don't think that the issue that the rule was originally created to avoid is an issue in this area: `this` is not being generally exposed before the base constructor has been run, and there are no changes to virtual members that can be invoked before the base constructor has run. We therefore lean towards allowing it. For the second question, we thought about whether to do a double-closure technique, where we create an inner closure type that is then shared between the accesses, or whether to simply close over `this` in the lambda. After some consideration, we think we're ok with just using `this`, particularly as the double-capture could potentially have some bleed-through effects on whether an individual type is considered `unmanaged`, and potentially causes allocations that weren't expected. The main issue will be working with the runtime team to ensure that ILVerify understands the pattern. ##### Conclusion This scenario is allowed, and we will work with the runtime team to make sure that ILVerify is updated for this scenario. #### Assigning to `this` in a `struct` Finally, we looked at whether allowing self-assignment in a struct to silently overwrite primary constructor parameter captures is a good thing, or if we should warn/error/something else for the scenario. We think that this is the least surprising behavior that this could have, and that it would be more surprising if it didn't have this effect. A warning does not seem appropriate: either you didn't even know this syntax was possible, or you do know what this syntax means and you were expecting the state of `this` to be overwritten; primary constructor parameters are part of that state, so they should logically be overwritten. ##### Conclusion Allowed, no warning. ================================================ FILE: meetings/2023/LDM-2023-02-22.md ================================================ # C# Language Design Meeting for February 22nd, 2023 ## Agenda - [Primary Constructors](#primary-constructors) - [Extensions](#extensions) ## Quote of the Day - "I have made a critical error: I forgot to write down quotes of the day today." ## Discussion ### Primary Constructors https://github.com/dotnet/csharplang/issues/2691 https://github.com/dotnet/csharplang/blob/bff4265098ff92551a9cc6aef17b0210f737c42a/proposals/primary-constructors.md We started today by going through the primary constructors specification, and verifying the feature we are going to ship in preview soon. There were few comments on the specification, which is generally unchanged from the initial review. We do think that there is room to relax `ref` and `out` restrictions on `record` primary constructors in the future, to align with primary constructors on non-`record` `class`es and `struct`s, but that is an orthogonal feature that we're not going to do at this time. We also do think that we potential future work on primary constructors to address more scenarios. For example: * Primary constructor bodies * Generating public properties, and naming convention discussions that will inevitably occur when this happens. However, we don't think any of these future features are blocked by the current state of the proposal, and will continue with the plan to merge the feature branch into an upcoming preview of VS and .NET. #### Conclusion We will move forward with the preview. ### Extensions https://github.com/dotnet/csharplang/issues/5497 https://github.com/dotnet/csharplang/pull/6880 Prototype progress on extensions (formerly roles and extensions, formerly shapes) has been proceeding in the working group. We're bringing the recommendations and changes the working group has suggested back to the LDM for debate and approval. In order to help the prototype proceed, today we are focussing on syntax questions. The working group explored a number of syntax options, including the original `role`/`extension`, `view`, `shape`, and `alias`, ultimately deciding on `extension` with a modifier: `explicit` and `implicit`. The LDT agreed that this feels like it unifies the proposal: where previously the distinction between an `extension` and a `role` was hard to remember and non-obvious, we feel that it is now much clearer. There is a single feature, extensions, which is about adding new functionality to an existing type. That extra functionality can be added implicitly (like existing extension methods are today, but with the ability to add more) or explicitly (completely new functionality). We also went over the newly-added `for X` syntax for specifying the underlying type of an `extension`. This is necessary for partial `extension`s to work. An example with the old syntax vs the new syntax: ```cs // Old syntax - Which of these is the interface getting extended, and which is the interface getting added? partial explicit extension Ext1 : Interface1; partial explicit extension Ext2 : Interface2; // New syntax - Unambiguous for both human and computer compilers. Interface1 is the extended type, and Interface2 // is the type being added. partial explicit extension Ext1 for Interface1; partial explicit extension Ext1 : Interface2; ``` Overall, the LDT is very happy with these syntax changes, and the prototype will be moving forward with them. We also looked at another issue the working group has started to consider, `extension` conversions. This covers conversions in all directions: up/down (from extension to underlying type and from underlying type to extension) as well as lateral conversions (from extension to extension with compatible underlying types). There is general sentiment that lateral implicit conversions might not be a great thing: moving from a `JsonViewExtension` to a `XmlViewExtension` is probably not something that should happen unintentionally. At the same time, the initial proposal envisioned the conversion to/from an `extension` as an identity conversion: it would be odd if there were identity conversions from a to b, and b to c, but not a to c. The working group will continue to consider these scenarios, as well as things such as validation on conversion to an `extension`, and come back to the LDM with a more complete view of the various pros and cons. #### Conclusion Syntax are changes, the working group will turn its attention to conversion questions. ================================================ FILE: meetings/2023/LDM-2023-02-27.md ================================================ # C# Language Design Meeting for February 27th, 2023 ## Agenda - [Interceptors](#interceptors) ## Quote of the Day - "Interceptors, that sounds like those auxiliary ships to Star Destroyers" ## Discussion ### Interceptors https://github.com/dotnet/csharplang/issues/7009 Today the LDM took a look at a new proposal in collaboration with the ASP.NET team, interceptors. This feature is intended to help make code AOT-friendly by allowing source generation to replace runtime reflection. By doing static analysis at compile-time and then hooking APIs, we can both improve performance and remove AOT-unfriendly code. This proposal generated significant discussion in the LDM, with roughly two ideological camps forming: 1. Interceptors are intended to be a performance optimization step. Generalized aspect-oriented programming is not a goal of the proposal, and the mechanism they work by is effectively an implementation detail. Users should not need to know that a method has been intercepted, so long as the debugging experience works well. 2. Interceptors are a generalized language feature, of which AOT-optimization is just one possible application. Users should know when a method call has been intercepted: perhaps they need to indicate this by putting a sigil at the callsite, such as `#` (ie, `controller.MapGet#("/", CreateFeed)`). Not everyone fit into these precise camps, but these were the general sentiments. Some other ideas that were raised during the meeting: * Can this be addressed by an IL AOT-rewrite tool instead of a C# language feature? By causing the C# compiler to emit different IL, it needs to be a language feature; if it was instead an additional step after compilation, then it no longer needs to affect the C# specification, and is not restricted to C#. As a C#-only feature, it could negatively affect the F# and VB ecosystems. * These hooked functions are really much more powerful than what can be accomplished today: state from the entire program can influence them. Maybe this calls for a different calling syntax entirely? * Lots of examples in other languages both for and against this: Rust uses the `!` character, while LISP does not have a different syntax. * Should we restrict these to just methods? Could you intercept properties, events, fields? * How general-purpose are we aiming for here, and is there any reasonable way to restrict it to only being a performance optimization step even if we decide that it should only be used for that? As the saying goes, if all you have is a hammer, everything is a nail... We did not come to any conclusions today; indeed, this conversation generated more post-meeting chatter in our Teams chat than any LDM topic in recent memory. It's clear that we need to think more about the problems that were raised today and come back with a revised proposal that takes these questions into account. ================================================ FILE: meetings/2023/LDM-2023-03-01.md ================================================ # C# Language Design Meeting for March 1st, 2023 ## Agenda - [Discriminated Unions Summary](#discriminated-unions-summary) ## Quote of the Day - "We're seconds from lattice theory references" ## Discussion ### Discriminated Unions Summary https://github.com/dotnet/csharplang/discussions/7010 Today, the working group took a summary of their discussions back to LDM and went over them with the group. This took the entire session, and only clarifying questions were asked; the LDM will discuss thoughts on the proposal next week, after members have had some time to think about the topics presented today. ================================================ FILE: meetings/2023/LDM-2023-03-08.md ================================================ # C# Language Design Meeting for March 8th, 2023 ## Agenda - [Discriminated Unions](#discriminated-unions) - [Limited Breaking Changes in C#](#limited-breaking-changes-in-c) ## Quote(s) of the Day - "It sounds like we're not unified on unions" - "Anyone feel like we found the weeds?" - "I shouldn't have used my pizza example" ## Discussion ### Discriminated Unions https://github.com/dotnet/csharplang/discussions/7010 https://github.com/dotnet/csharplang/issues/7016 [Last time](LDM-2023-03-01.md#discriminated-unions-summary), we looked at the summary of the discriminated union working group's investigations. There's a lot there; we also had an additional proposal from a language design member emeritus, who lead the previous investigation into DUs, on a form of efficient tagged unions. One thing that is apparent is that, even with the working group's summary, there is a massive design space here. Another point that becomes obvious quickly is that generics throw many wrenches into any design: you either have a problem where things like `List<(A | B)>` cannot be freely converted to `List<(A | B | C)>`, or there are inefficiencies in representing value types that likely preclude union usage in a number of places. Looking to the major .NET language with union types, F#, they implement tagged unions (though they are considering [anonymous type unions](https://github.com/fsharp/fslang-design/blob/0ff87d5ab3160366a96316f739ba31d9a61d0f4a/RFCs/FS-1092-anonymous-type-tagged-unions.md)). We talked some about how tagged unions implemented via structs is effectively struct inheritance: indeed, it's hard to conceive of a struct inheritance implementation that would not effectively turn into a tagged discriminated union. There are some interesting parts to this implementation strategy though: would a union with simple inline values be able to be more efficiently represented than a union with a nested struct value? For example: ``` union { MyFourInts(int, int, int, int), MyThreeInts(int, int, int) } union { MyFourInts(StructOfFourInts), MyThreeInts(StructOfThreeInts) } ``` If those have different layouts that make the former more compact than the latter, that could be unfortunate, particularly if the layout was guaranteed in such a way that we couldn't improve it in the future. We also thought a bit about how tags will interact with pattern matching. The natural syntax we would want to use likely conflicts with type patterns: can we make that work? And, in particular, will there be a way to declare a variable that encapsulates all the state of a particular tag? The tags aren't types, but maybe we can paper over that for the purposes of pattern matching, using tuples to contain all the state for a particular tag? We ultimately think there is room for multiple types of unions in C#: a tagged, struct-based variant, and a reference type variant. However, we aren't ready to make more progress yet; we'll work on determining the next steps for the working group. ### Limited Breaking Changes in C# https://github.com/dotnet/csharplang/discussions/7033 Continuing in today's LDM theme of light, easy, non-controversial topics, we took a look at a proposal for making limited breaking changes in C#. We posted this to csharplang a few days ago, and the response so far has been mostly positive; plenty of suggestions of how we might tweak it to add more granularity, suggestions to go further (a la Rust editions), and questions on the cadence. There have only been a few negative reactions to the idea, however, which is an encouraging sign overall. GitHub can't be our only source of sentiment on this, though, as it's a self-selecting group; more user research will be needed via other avenues. We brought up that this might change the way the C# compiler approaches certain problems. Today, the compiler tries very hard to not change its semantic behavior while binding. When we see a user taking advantage of a language feature in a newer language version than what they are targeting, we simply produce an error and continue binding. This would change the compiler to introduce new warnings in _lower_ language versions, and cause the compiler to potentially bind in different fashions for higher and lower versions. We need to be cognizant of that, and have an idea of how the compiler change would work before committing to a design that we can't implement. We also thought about the documentation issue. Today, our documentation is updated for new language versions, so if we make breaking changes, users on older versions might slowly get left behind our documentation. Inversely, there are many non-Microsoft sources of documentation (StackOverflow, blogs, tutorial videos, etc.) that never get updated for new content; users trying to copy and paste or learn from these sources might be hurt. This has already happened with top-level statements; our tools and docs updated, but many popular 3rd-party intros and tutorials have not. The changes we're discussing here might not be as much of a concern, however; the cases we've discussed so far are exceptionally narrow cases of changing how an identifier is interpreted. A read of the room indicates that we should continue to pursue this topic, so the working group will continue to flesh out the proposal, gather feedback from user groups, and flesh out the proposal details for future review. ================================================ FILE: meetings/2023/LDM-2023-03-13.md ================================================ # C# Language Design Meeting for March 13th, 2023 ## Agenda - [Unsafe in aliases hole](#unsafe-in-aliases-hole) - [Attributes on primary ctors](#attributes-on-primary-ctors) ## Quote(s) of the Day - "The quote of the day will be 'We have 2 small topics', and the agenda will only have 1 topic." - "We only have 5 minutes left, let's see if we can do it really quickly." *everyone starts talking really fast* ## Discussion ### Unsafe in aliases hole https://github.com/dotnet/roslyn/issues/67281 We started today by looking at an existing hole in the enforcement of `unsafe` for pointer types, nested as the element type of an array in a type parameter to a generic type (such as `List`). This hole has existed for as long as generics have, but now presents us with an inconsistency in the C# 12 `using` enhancements feature, as it introduces `using unsafe`. In brainstorming, we came up with the following options: 1. Keep the status quo. This will mean that most usages of `unsafe` types in `using` will require `unsafe`, _except_ those types contained inside type parameters. 2. Produce an error across all language versions, and require `unsafe`. This will break older consumers that have aliases like this defined. 3. Produce an error conditionally on language version 12. This has 2 suboptions: 1. Give warnings on lower language levels. 2. Do not give warnings on lower language levels. 4. Make this a warning wave in C# 12. This will also result in inconsistent experience like #1, but there will be a diagnostic in all scenarios. 5. Don't add `unsafe` to `using`s in general, and do not require them for `unsafe` types in `using`s. We framed these options of what they do for the future and past of the language: * 1 would result in an inconsistent future for the language, and we have no reason to believe that code like this exists nearly anywhere at all. No search results for it appear in any of our usual sources, when other breaking changes we've made _do_ sometimes appear in searches of GitHub or internal MS code. * 2 would be consistent for the future, but leave the past with no escape hatch. * 3 would be consistent for the future, and leave the past as it is; but the new warnings from a could potentially be an issue for users who are just upgrading their toolsets. * 4, while not as inconsistent as 1, would still leave the future inconsistent, which we do not like. * 5 would be consistent with the past and the future, but the reasons we chose to have `using unsafe` in the first place are still relevant. We don't want to adjust the future of the language based on a bug. Based on this discussion, we think 3b is the best option that maximizes forward direction while letting past code continue existing. #### Conclusion We chose option 3b, producing an error on this code in C# 12 and up, and not giving warnings in lower language versions. ### Attributes on primary ctors https://github.com/dotnet/csharplang/issues/7047 In our speedrun session for this issue, we briefly considered whether `method` in this location is obviously on the primary ctor: could it conceivably be confused with the copy ctor or deconstructor. However, we already have documentation comments on the parameters, and those don't get applied in either of the aforementioned locations. Attributes on the parameters are also only applied to the primary ctor, not to the deconstructor. Given those, we're ok with this proposal. We then determined what bucket to put it in. For now, we'll bucket it with the primary constructor work we're doing in C# 12, which would put it in the working set. We'll rebucket to Any time if it doesn't end up fitting in that bucket. #### Conclusion Proposal is accepted, will be part of the primary ctor work in C# 12. ================================================ FILE: meetings/2023/LDM-2023-04-03.md ================================================ # C# Language Design Meeting for April 3rd, 2023 ## Agenda - [Collection Literals](#collection-literals) - [Fixed-size buffers](#fixed-size-buffers) ## Quote of the Day - "These are now variadic tuple literals" ## Discussion ### Collection Literals https://github.com/dotnet/csharplang/issues/7085 https://github.com/dotnet/csharplang/issues/5354 We started today by looking at open questions in collection literals, spending all of our time talking about the spread operator. We had a few questions we wanted answered about it: 1. Is spread even an important thing to address in the initial collection literals proposal? 2. How much is the compiler free to optimize the creation of these collection literals? Can intermediate literals be elided if a reasonable program wouldn't observe them? For the first question, we universally agree that there is a concept to be explored there. There will be some syntax gremlins to iron out; some members believe that `..` is perfectly natural, as it is the complement of slicing in list patterns and in accessing sub-ranges of existing collections, while other members believe that `..` is bad here because it introduces confusion for collections of ranges. Despite that, we do think the space is worth exploring, and will do so. Next, we thought about compiler optimizations of spreads. There's a lot of interesting side-points on optimizations here that can have a big impact on the collection literal, such as: * If the collection literal is not going to be added to later, then presizing makes sense. If it is going to be added to later, though, presetting the size can have negative impacts and force resizes where they might not otherwise be. * What is the order of evaluation of the creation of the list and the evaluation of the elements? For example, if the elements of the list are pre-evaluated, we could pre-size the collection more accurately; on the other hand, if we have to pre-evaluate the entire element set before calling the constructor of the containing type, could we end up having more stack/local usage and negatively impacting perf there? * What about intermediate collection literals? The proposal suggests using conditional expression spreads with empty collection literals for conditionally adding elements to the containing collection, but do those nested collection literals need to actually be created? Can they be inlined into the containing creation? And if we do that, does that mean that things like extract local can introduce silent perf impacts because intermediate collections are now considered real collections? This last bullet brought up a new question that we then started delving into: 3. Should list literals be thought of as more ephemeral until they're used? For example, if it's just being `foreach`'d over, we could just make it a `stackalloc`. Or, if it's assigned to a `var` variable that is eventually passed to a `List` location, we could say that it's not really a list until that point, but is instead a lightweight list-like thing. This idea is both interesting (gives us lots of room to optimize the implementation for specific scenarios) and terrifying (what is the type of this variable? What does generic type inference do? Is it a quantum variable that needs to have the waveform collapse eventually?), and has lots of room to explore. We could say, for example, that `var` causes the type to collapse, and it's only when these are used as nested expressions in other expressions that they have this weird quantum state. We think this needs to be explored more, and the consequences and impacts on other systems thought through, before we go further down this road. Finally, we also looked at the conditional expression used here. There are some [older discussions on csharplang](https://github.com/dotnet/csharplang/discussions/5588) about making a conditional assignment as part of object initializers, we might be able to general purpose that to work in either of these cases. While we do think that the syntax originally proposed in the spec for collection literals should work and should ideally not produce excess allocations, it does feel ugly, and there's likely a feature to improve that for both collection literals and other types of initializers. #### Conclusions 1. We will continue trying to answer the spread questions and make an effort to include it in the initial feature. 2. Leaning towards allowing optimization, but we need a better understanding of what optimizations are on the table and the consequences of them. 3. We need more info about what the impacts of this change would be. We'll come back after some more thinking on those topics has been done. ### Fixed-size buffers https://github.com/dotnet/csharplang/pull/7064 https://github.com/dotnet/csharplang/issues/1314 Finally today, we took an initial look through the first option proposed in 7064 for implementing safe fixed-size buffers. This is a more complicated proposal that builds off new runtime support for `InlineArrayAttribute`, allowing types defined with a specific pattern to be treated as if they are fixed-size buffers of an element type. Today was mostly an overview; next time we will go over the simpler version of the proposal (option 2) and discuss more about which option we want to go with. ================================================ FILE: meetings/2023/LDM-2023-04-10.md ================================================ # C# Language Design Meeting for April 10th, 2023 ## Agenda * [Fixed Size Buffers](#fixed-size-buffers) ## Quote of the Day - "As a Tanner of the world" ## Discussion ### Fixed Size Buffers https://github.com/dotnet/csharplang/pull/7064 https://github.com/dotnet/csharplang/issues/1314 Continuing from [last time](LDM-2023-04-03.md#fixed-size-buffers), we took a look at the second of the possible fixed size buffer approaches today. This one is much simpler and is based on only exposing `Span` and `ReadOnlySpan` types to the user, instead of `BufferX` types. This ensures that users don't have new types to think about and can keep operating in terms of the types they already know. It also allows changing the size of a fixed-size buffer to not be a binary-breaking change, as the only publicly-exposed types are `Span` and `ReadOnlySpan`, neither of which has an associated fixed length. This simplicity does bring some of its own downsides too, though: * That binary break could be a desired outcome: if a public fixed size buffer shrinks, it's important for that to break the API of anyone who was accessing memory that no longer exists. * On this note, we don't currently have anywhere that we plan to expose public fixed size buffers, outside native interop scenarios. * How do other languages consume these types? Do they have to understand an additional C# concept layered on top of the general runtime pattern? * Does this stifle our future language design space? We could want to reason about fixed-size arrays in the future, for example a `List`, or even going further and allowing const generics as in `List`. * Users might also simply want to pass these buffers around. If the buffer is typed as `Span` or `ReadOnlySpan`, then size information is elided and compile-time bounds checking can be lost. Additionally, using `Span` and `ReadOnlySpan` as the user-facing types doesn't prevent users from defining their own fixed size buffer types, as `InlineArrayAttribute` is a runtime feature. Should C# add support for consuming such types as if they are fixed size buffers, even if we don't `int[4]` as one? This has its own set of complications. We're a bit concerned about whether it's worth the effort: how many users are actually going to define these types? And if they do, will these types conflict with each other? Unlike tuples, we somewhat expect that fixed size buffers could be hundreds of elements long; even if the BCL were to define the first 32 sizes, there's still a good chance that two fixed size buffers from different assemblies would be conflicting definitions, even though they wrap the same type and have the same length. Despite these concerns, though, we think that there's enough benefit for the BCL in supporting these types that we should do so in C#. Next time, we will pick this up again and try to determine whether we will have specific syntax for easily defining these types of buffers in the language itself (such as `int[4]`) or not. #### Conclusion C# will support using fixed size buffer types as if they are buffers. We will think more about whether to have syntactic sugar for defining them next time. ================================================ FILE: meetings/2023/LDM-2023-04-26.md ================================================ # C# Language Design Meeting for April 26th, 2023 ## Agenda - [Collection literals](#collection-literals) ## Quote of the Day - "Do we need dragon warnings?" ## Discussion ### Collection literals https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/blob/760e4a772a06723ea6ab3edabe14029623302ea2/proposals/collection-literals.md Today, we took a look at the current proposal for collection literals. The proposal is looking very good overall, but there are some open questions around how it should behave in some scenarios. First, when the target-type of a collection literal is an `IEnumerable<>` of some kind, what type is emitted by the compiler? As written today, it's always `List<>`, but this has the potential to cause allocations when they aren't needed. It would be great, for example, if `IEnumerable e = [];` could just be emitted as a call to `Enumerable.Empty()`, avoiding the need for an allocation entirely. That can't happen today, though, as it's specified to be `List`. This would also mean that we couldn't do optimizations for scenarios that pass a fixed list of constants to an `IEnumerable<>` function: the consumer could downcast to `List<>` and add to it. We think that, given that we are talking about `IEnumerable<>` and not some more specific type, it should be fine to not guarantee what underlying type we end up using; indeed, that's the entire point of taking an interface, rather than a concrete type. Next, we looked at KVPs turning the collection literal into a `Dictionary<>` automatically. We have some immediate concerns with this provision: the user could just as easily want a `List>` instead of `Dictionary`. There are also concerning differences in behavior with how such a dictionary would be constructed: a `Dictionary` would be union semantics, where duplicate later keys can overwrite earlier keys (or, if specified slightly differently, it could throw instead of overwriting). A list would contain both duplicate keys, which is a large change in behavior with very little to indicate that the behavior has changed. A bit more digging pulled up the important scenario that this rule is trying to cover: `[.. dict1, .. dict2]` should result in a dictionary union of these two input dictionaries, or `[.. dict1, key: value]` should result in a copied dictionary, with `key` set to `value`. We therefore brainstormed an alternate rule, that would be useful not just for `Dictionary<>`, but also for any collection being splatted into another collection: the type of a splat should impact the natural type of a collection literal. This is a rough idea that the working group will need to further explore, but at first glance it seems both powerful and like it addresses the concerns that created this rule in the first place. No conclusions were reached today, but the working group has more feedback to go iterate on and come back to LDM with. ================================================ FILE: meetings/2023/LDM-2023-05-01.md ================================================ # C# Language Design Meeting for May 1st, 2023 ## Agenda - [Fixed Size Buffers](#fixed-size-buffers) - [`lock` statement improvements](#lock-statement-improvements) ## Quote of the Day - "Oh my head hurts" "I'm glad I achieved my goal" ## Discussion ### Fixed Size Buffers https://github.com/dotnet/csharplang/pull/7130 https://github.com/dotnet/csharplang/issues/1314 [Last time](LDM-2023-04-10.md#fixed-size-buffers) we discussed fixed size buffers, we concluded that we wanted to support manually defined inline array types in the language natively. This means, for example, being able to index them. Our next question is, should we support anonymous inline array types? The proposed syntax is `int[4]` for an anonymous inline array of 4 integers; this aligns well with existing array types in the language and is a natural extension. However, the ease with which it slots into the language is almost an issue in itself; inline arrays are not regular arrays and cannot be used where arrays can be. They do not unify across assemblies, and indeed the proposal forbids making them public at all. There are also concerns on what the syntax will end up looking like with nested arrays: a regular array of 4 element int arrays would end up looking like `int[][4]`. This is then complicated further by nullability: a non-null regular array of 4 element int nullable arrays would be `int[]?[4]`. There are even potential issues with `foreach`: an `int[4]` is only indexable via `Span`, which would potentially break usage in async methods. Given all of these hidden gotchas and the lack of demand for anonymous inline arrays, we think the best approach for now is to avoid having them at all. We also looked at initialization scenarios; the main questions here are whether we allow array initializers for these values, and whether we allow C# 12 collection literals for them. For array initializers, we think the answer should be no, as we have now said there are no anonymous inline arrays. We do think that collection literals are the future here, and they should work for inline arrays as well as other types of collections. #### Conclusion Anonymous inline arrays are rejected. Array initializers are not supported for inline arrays. Collection literals are supported for inline arrays. ### `lock` statement improvements https://github.com/dotnet/csharplang/issues/7104 The runtime will be adding a new `System.Lock` type, as an alternative to monitor-based locks. The API has a shape that works well with `using`, but as it currently stands it will misbehave with a standard `lock` statement. This isn't the first type of lock this is true for; for example, `ReaderWriterLock`/`ReaderWriterLockSlim`/`SpinLock`. However, this is different in that all of those have caveats to their using with a regular `lock` statement: rwlocks need to enter in a specific mode that can't be inferred from just a `lock` statement, and `SpinLock` is very rarely used. This new type, on the other hand, is part of an experiment to entirely replace monitor-based locks in .NET, and the hope is that new development will use these instead of an `object` to perform mutual exclusion. Given that, having it do the wrong thing in a `lock` seems like an easy footgun for the language to solve. An open question is whether we want to convince people to avoid monitor-based locks entirely; that seems like it would be best driven by the runtime, rather than the language design team. #### Conclusion The language will support using the new locking pattern for locking on `System.Lock`. ================================================ FILE: meetings/2023/LDM-2023-05-03.md ================================================ # C# Language Design Meeting for May 3rd, 2023 ## Agenda - [Inline Arrays](#inline-arrays) - [Primary constructors](#primary-constructors) - [Attributes on captured parameters](#attributes-on-captured-parameters) - [Warning for shadowing base members](#warning-for-shadowing-base-members) - [Collection literal natural type](#collection-literal-natural-type) ## Quote of the Day - "It's our best chance of having a stem cell" ## Discussion ### Inline Arrays https://github.com/dotnet/csharplang/issues/1314 https://github.com/dotnet/csharplang/pull/7130 Continuing to [follow up](LDM-2023-05-01.md#fixed-size-buffers) on open questions, we turned our attention to validating `InlineArrayAttribute` applications and whether nested object initializers can use indexers on inline arrays. For the former, we're unanimously in favor of the compiler validating that the attribute was applied correctly. The latter proved to be a more interesting conversation; we brought up that arrays are supported, but this actually appears to be a spec violation of the C# compiler, as the syntax is supposed to require an indexer member to be defined on the type being initialized while arrays do not define any such member. We also don't think there's any real demand for this to be natively supported, and by leaving it out users can opt into the feature by defining their own indexer on their inline array types. This allows us to have our cake and eat it too: no implementation complexity for us, and users can get the syntax they choose if they want it. #### Conclusion We will validate `InlineArrayAttribute` applications. We will not natively support inline arrays for object initializer index initialization, but we won't block it if the user defines their own indexer either. ### Primary constructors https://github.com/dotnet/csharplang/issues/2691 We considered a couple of open questions in primary ctors. #### Attributes on captured parameters https://github.com/dotnet/csharplang/blob/fe9915c0708cf95c308cce33983373641fc1b60d/proposals/primary-constructors.md#field-targeting-attributes-for-captured-primary-constructor-parameters We're concerned about allowing attributes on captured parameters. From a language perspective, these are not fields, and don't have to be captured in fields. This would not be the only thing that a user might want to put on their captures; for example, there has been significant feedback that users would like their captures to be `readonly`. This doesn't seem more important than that scenario, so why would this work while that does not? It seems like, if we ever get syntax for specifying more about how the parameter is captured, it would be appropriate to have this feature at that point, but until then we would like to hold off. ##### Conclusion Not allowed. #### Warning for shadowing base members https://github.com/dotnet/csharplang/discussions/7109#discussioncomment-5666621 One piece of feedback that we've seen is that users are concerned about shadowing members from base types accidentally. While this is concerning, we're also concerned that a broad warning might impact a common scenario where the parameter is passed to the base to initialize the shadowing member. We need to think more about this conflict, so we'll do so and come back to the problem at a later meeting. ##### Conclusion No conclusion today. ### Collection literal natural type https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/meetings/working-groups/collection-literals/CL-2023-04-28.md#should-a-collection-literal-have-a-natural-type-if-so-what-type Finally today, we considered whether collection literals should have a natural type. There are a couple of slightly different versions of this scenario: `var`, and `IEnumerable<>`, but `var` is what we spent most of the time discussing. In many ways, a natural type for a collection is going to be surprising, no matter what we choose. Some users will expect such collections to behave like other C# literals: immutable and cacheable. Another segment might expect the literal to be typed as an array: not immutable, but not growable either. A third would expect such literals to be a type that can be added to later, such as `List<>`. No matter what we choose, if we choose a natural type, two of the segments are going to be surprised by the decision. So what if we didn't have a natural type at all? Today, users always need to think about the type of a collection when it is created. Either it needs to be assigned to a local or parameter that has a type, or it's being created by some factory method or constructor that has to be typed. One of the goals of the proposal to simplify this, and that requires having a natural type. But it does feel odd that, for a proposal that goes so far to be optimal when we know the target type, it cannot be optimal in the natural type case. For the cohort of users that want these to be immutable, any mutability in the natural type means that the literals cannot be cached, resulting in lots of extra allocations. For those that expect an array, using `List<>` or some other similar type means that extra overhead is incurred, and trivial cases like empty literals cannot be shared. For users who expect `List<>`, either of the other two means that they will have to specify the collection type at creation, making it no longer a simple scenario. Unfortunately, not having a target type at the start and then adding one later is potentially a breaking change. We did it for lambdas, but it caused significant pain for implementation and has a number of caveats, so if we can avoid the same gymnastics that would be helpful. We also thought a bit about a middle ground, where a collection literal only has a natural type when it is observably stored in a location. For example, when iterated over in a `foreach`, a collection literal could be emitted however is most efficient for the compiler. This does then introduce another risk of deoptimizing by extracting variables. This can already happen with interpolated string handlers, but that is fairly rare. This might be similarly rare, but more such cases always need to be approached with caution. We have no conclusions here this week. We'll come back and think about it more in a future LDM. ================================================ FILE: meetings/2023/LDM-2023-05-08.md ================================================ # C# Language Design Meeting for May 8th, 2023 ## Agenda - [Primary Constructors](#primary-constructors) ## Quote of the Day - "Depending on how you interpret it, the results can be whatever you want" ## Discussion ### Primary Constructors https://github.com/dotnet/csharplang/issues/2691 https://github.com/dotnet/csharplang/discussions/7109#discussioncomment-5666621 Today we looked at one of the pieces of community feedback for primary constructors, around name shadowing from base types. A user might not expect the output that this program will give: ```cs public class Base { protected readonly string a = "base"; } public class Derived(string a = "derived") : Base { public void M() { Console.WriteLine(a); } } ``` This code will print `base` when `Derived.M()` is called, because the base field shadows the primary constructor parameter. While this could be confusing for this particular case, we don't think this is common case for this type of code; instead, the common case is something more like the following: ```cs public class Base(string a) { protected readonly string a = a; } public class Derived(string a = "derived") : Base(a) { public void M() { Console.WriteLine(a); } } ``` In this example, `a` is passed through to `Base` via its constructor, and the protected field is referenced by the derived type ensuring that the value is not double-stored. There are a few possible options we can look for improving the first scenario: 1. Warn whenever a base member shadows a primary constructor parameter at the use site. This would produce a warning in both of the above code samples. 2. A variation on 1, produce a warning whenever a base member shadows a primary constructor parameter and that parameter wasn't passed to the base type. There are 2 subvariants of this: 1. Ensure that names match when doing this. IE, passing `a` to a parameter named `b` would not count for the purposes of suppressing the warning. 2. Do no validation on the name of the parameter. 3. Change the shadowing order: make the primary ctor parameter shadow the base member, so accessing the base member would require `base.` qualification. For 1, we're concerned about the impact to the second code example: that seems the far more common case in the wild, and impacting it negatively isn't great. For 3, we're unsure whether the effective behavior being different than records will be confusing: `Derived` would not create a new member named `a`, it would use the existing one from the base. This leaves us with option 2. For 2, we thought a bit about the subvariants. We're a bit concerned by field naming conventions differing from parameters: what guarantees are there that a constructor parameter named `a` will actually assign to a field named `a`? There are none, of course, only long-standing conventions. Some users prefer to name their fields with `_` prefixes (following the C# code-style guidelines), but many keep them exactly the same. We ultimately think that there's enough signal of intent in passing the primary ctor parameter, no matter what the name of the base ctor parameter is, and we can suppress the warning for this case. #### Conclusion Option 2.2: Produce a warning on usage when a base member shadows a primary constructor parameter if that primary constructor parameter was not passed to the base type via its constructor. ================================================ FILE: meetings/2023/LDM-2023-05-15.md ================================================ # C# Language Design Meeting for May 15th, 2023 ## Agenda - [Breaking Change Warnings](#breaking-change-warnings) - [Primary Constructors](#primary-constructors) ## Quote of the Day - "The main point of closing the door is so that people who are slightly late feel that bit of embarrassment opening the door" ## Discussion ### Breaking Change Warnings https://github.com/dotnet/csharplang/discussions/7033 https://github.com/dotnet/csharplang/blob/ab5afc419eed3d67bf064ec417f69a65aecbef5c/proposals/breaking-change-warnings.md Our previous issue on this feature had a large number of moving parts and generated a lot of interesting discussion. This discussion was good, but it left us wondering what a pared back and small-scale version of this proposal would look like: one where we simply did the break and very little infrastructure around it, to avoid snowballing the proposal and adding many moving parts; in response, a small group sat down and made a minimal version of this feature to see what that would actually look like. The minimal version of this is very simple; when using a newer .NET SDK with a newer C# compiler targeting an older version of the language, warnings will be reported for any potential breaks detected. That's it, no flow around upgrading. LDM members had a few immediate concerns with this: * Is there any guarantee that users will actually enter this state? Particularly on systems where a new warning would fail the build, as opposed to on a machine where a new warning in the list might be missed? * We have plenty of examples via MSBuild and NuGet where new warnings on toolset upgrade breaks our users. Is that something we're prepared to deal with? * All our new warnings on existing code (Warning Waves) are activated by an explicit user action; upgrading to a new version of .NET. This would be the opposite. * If warnings are easily missed, should these be errors instead? * There's some concern that this is too harsh: is it really necessary to break the build with no recourse other than downgrading the toolset? There could be an opportunity to create a level between warning and error; diagnostics that appear as errors but are user-suppressible. * In general, catching the moment of upgrade in this system is hard. One additional consideration that was raised is that we might be trying to force this in too quickly, for the purposes of getting `field` out in C# 12 in its idealized form. If we instead made it so that C# 12 reported warnings, and the version after breaking the scenario. Unfortunately, this has some of the same issues; there's no guarantee that C# 12 will actually be the migration target. It could be that user goes straight from 11, or from .NET Framework, or any other non-12 version. Even if we do LTS->LTS, there could still be missed warnings, and we'd also significantly delay new features. #### Conclusions No conclusions today. We've identified some significant problems with the minimal approach and need to keep thinking. We'll revisit again soon. ### Primary Constructors https://github.com/dotnet/csharplang/issues/2691 https://github.com/dotnet/csharplang/blob/7bd6a9ff7f19df1e5fdbe18f593e5b18264b50da/proposals/primary-constructors.md#double-storage-warning-for-initialization-plus-capture Finally today, we looked at a small proposed potential double storage warning for primary constructors. We think this is completely inline with our previous decisions and approved it unanimously. Several members were surprised we hadn't already made this a warning. #### Conclusion Approved as proposed. ================================================ FILE: meetings/2023/LDM-2023-05-17.md ================================================ # C# Language Design Meeting for May 17th, 2023 ## Agenda - [Inline arrays](#inline-arrays) ## Quote of the Day - "A strategy to hold ourselves hostage" ## Discussion ### Inline arrays https://github.com/dotnet/csharplang/issues/1314 https://github.com/dotnet/csharplang/blob/7b5d0bbcd552e0541db4a5afdad3b75210037120/proposals/inline-arrays.md#the-foreach-statement Following up from a [previous discussion](LDM-2023-05-01.md#fixed-size-buffers) on inline arrays, we looked at `foreach` support for inline array types. The current implementation plan for `foreach` on these types is to do it via `Span` or `ReadOnlySpan`; as these are `ref struct` types, they cannot be `foreach`ed in an `async` method. This restriction makes sense for inline arrays sometimes, as they are more likely to be passed around via `ref` than standard types. Refs can't live past an `await` boundary, so this makes sense for such variables. However, it doesn't make as much sense for inline arrays that are local by value: for example, a local of an inline array type would not be foreachable in an `async` method as proposed today. There are two possible improvements we could make here: 1. Be more granular with `ref struct` usage in `async` methods. As long as the `ref struct` does not cross an `async` boundary, it should be safe to reference. If we were smarter about `ref struct` usage in `async` methods, then inline arrays would simply come along for the ride. It's not a perfect solution though, as there would still be errors for inline array values that can be safely lifted to a display class when a `foreach` loop `await`ed in its body. 2. Have special lowering for inline arrays for async methods. `ref`s to inline arrays would continue to be blocked in `async` methods, as today, inline array values (mainly locals and parameters) can be safely lifted to a display class and iterated over across `await` boundaries without any issues around observability of changes. These solutions are not mutually exclusive; we can do either one, both, or none. What we are fairly certain of at this point, though, is that it is unlikely that we can fit either solution into C# 12. After some discussion, we decided that we are fine with shipping an initial version of inline arrays that restricts `foreach` in `async` methods, and later loosen restrictions on it as we can. #### Conclusion We will support `foreach` over inline arrays, even if it starts as restricted in `async` methods. We will encourage alternate lowering strategies and smarter `ref` rules in async methods where we can, but that will be a longer-term effort. ================================================ FILE: meetings/2023/LDM-2023-05-31.md ================================================ # C# Language Design Meeting for May 31st, 2023 ## Agenda - [Collection literals](#collection-literals) ## Quote of the Day - "You did not just say that" ## Discussion ### Collection literals https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-LDM-2023-05-31.md Today we took a look at what the collection literals working group has been iterating on, and tried to answer a few of their open questions. We started by looking over the list of preferable target-type construction patterns. The idea is to choose the most efficient construction pattern for a given collection literal, given that it has a specific type. Importantly, while this list talks about preferable construction patterns, it does not talk about overload resolution and what a most-specific type would be. Rather, the collection has already been typed by this point, and we are only trying to figure out how the collection will be constructed. There were a couple of concerns brought up during this that the working group will take back to consider: 1. Will there be potential breaking change issues here if we ever want to add a new type in the middle of this list, much like can happen in `foreach`? 2. Should other non-BCL interfaces be able to participate in this mechanism? For example, could Rx.NET allow creating an `IObservable` sequence via a collection literal? We also discussed what the behavior for `IEnumerable` would be: what type should we emit here? Should it be a guaranteed concrete type like `List`, or something else? Or something entirely hidden? The main concern here is around downcasting and observability of that. It's important to note that while some downcasting is not an important scenario, some is entirely innocuous or even an important perf scenario: for example, `LINQ` will try to optimize known-length scenarios, which does involve type testing and downcasts. Given that one of our focuses here is performance, we're not eager to have using collection literals introduce performance gotchas. However, if we said `List` is guaranteed, that also removes some ability to do performance work; for example, representing `[]` with `Enumerable.Empty()`, or caching for `IEnumerable`s composed of entirely `const` data. Given that, we don't think we want to commit to a single specific type. Instead, we want to work with the runtime to make sure that we can eke out every scrap of performance. The working group will explore the space of using unspeakable and/or immutable collection types here. Finally, we also looked at the `Create` method pattern proposed by the working group. We generally like the patterns, but think there potentially needs to be more. For example, unknown-length collection construction does not work with the proposed patterns for `ImmutableArray`. We also considered whether these could just be static methods on the type itself. The runtime is very against this, because these method patterns are somewhat unsafe for manual usage; for example, the `ImmutableArray` creation method returns a mutable `Span` to the backing storage, which is super dangerous. We keep these types of APIs on specific Marshal types today (`CollectionsMarshal`, for example), specifically to avoid them appearing in the standard surface area of the type, and we want to continue this pattern. No conclusions for today, but plenty for the working group to take back and continue iterating on. ================================================ FILE: meetings/2023/LDM-2023-06-05.md ================================================ # C# Language Design Meeting for June 5th, 2023 ## Agenda - [Collection literals](#collection-literals) ## Quote of the Day Due to an unfortunate notekeeping snafu, there are no records of funny things said today. They did exist, but they are lost to the annals of time. ## Discussion ### Collection literals https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-LDM-2023-05-31.md Today we [continued](LDM-2023-05-31.md#collection-literals) our review of the work the collection literals working group has been doing, this time looking at dictionary creation patterns. We first thought about syntax, and the potential for ambiguous syntax. For example, this expresson would be ambiguous: ```cs Dictionary d = [a ? [b] : c]; // [a ? ([b]) : c)] or [(a ? [b]) : c]? ``` There was also a question raised whether it might be useful to state from the literal itself whether it would be a dictionary or a sequence. For example, `[..dictionary1, ..dictionary2]` might want to prefer combining into a new dictionary, not a sequence of elements. To try and address these cases, we explored whether there was some alternative syntaxes that would mark the literal as a dictionary literal and force dictionary semantics. * `[: a ? [b] : c]` - Leading `:` * `[: a ? [b] : c :]` - Leading and trailing `:` * `{ a ? [b] : c }` - Use `{}` for dictionary literals None of these particularly resonated with the LDM. Further, no language that we can find that has dictionary literals differentiates them from collection literals with an extra or different sigil, so there's no prior art to draw on. Given this, we don't think there needs to be a separate syntax for dictionary literals, and `[..dictionary1, ..dictionary2]` will have sequence semantics unless target-typed to a dictionary type (including dictionary interfaces such as `IDictionary`), or unless the literal contains a `key : value` element. Next, we thought about what semantics spreading dictionaries should have, when the literal will have dictionary semantics. For example, what would the following code print? ```cs using System; using System.Collections.Generic; Dictionary dictionary1 = [1 : 2]; Dictionary dictionary2 = [1 : 3]; M([..dictionary1, ..dictionary2]); void M(Dictionary d) { foreach (var (k, v) in d) Console.WriteLine($"{k} {v}"); } ``` There are two possible behaviors: add behavior, where duplicate keys are error, or overwrite behavior, where the last instance of a key wins. There's two competing schools of thought here. In favor of add behavior, most of our existing methods have this behavior. `CreateRange`, the `Dictionary` constructor, collection initializers as they exist today, etc. However, `[a : b]` looks more like an indexer, which does have overwrite semantics today. There also some concern that, as we borrow this syntax from other languages, deviating too much from their behavior will introduce a quirk that is more gotcha than it is helpful. Ultimately, we think that we should prefer overwrite semantics for this scenario. #### Conclusion No special syntax for differentiating a dictionary literal beyond the `key : value` syntax for an individual element. Dictionary literals will use overwrite semantics, not add semantics. ================================================ FILE: meetings/2023/LDM-2023-06-19.md ================================================ # C# Language Design Meeting for June 19th, 2023 ## Agenda - [Prefer spans over interfaces in overload resolution](#prefer-spans-over-interfaces-in-overload-resolution) - [Collection literals](#collection-literals) ## Quote(s) of the Day - "What's the second question?" "Type inference for collection literals." "Oh that's easy" - "Second2, also known as third" - "Oh, I thought [redacted] was making a 'type inference walks into a bar' joke" ## Discussion ### Prefer spans over interfaces in overload resolution https://github.com/dotnet/csharplang/issues/7276 Today we looked at whether overload resolution should have special tie-breaking rules for the `Span` vs `IEnumerable` case. There are a few cases that can this affect, and potentially blocks the runtime from adding overloads that take `ReadOnlySpan` for fear of breaking users. ```cs var ia1 = ImmutableArray.CreateRange(new[] { 1, 2, 3 }); // error: CreateRange() is ambiguous var ia2 = ImmutableArray.CreateRange("Test"); // error: CreateRange() is ambiguous public static class ImmutableArray { public static ImmutableArray CreateRange(IEnumerable items) { ... } public static ImmutableArray CreateRange(ReadOnlySpan items) { ... } } ``` For both examples, these are types that are convertible to both a `Span` and to `IEnumerable`. There is no commonality between those two types, other than `object`, so the overloads are ambiguous. There are two questions here: * Do we want to mimic a subtype relationship between `IEnumerable` and `Span` as best we can without `Span` actually implementing the interface? * Is `Span` special here? Are there other user-defined `ref struct`s that can run into this same problem with ambiguous overloads? We're a bit concerned with chasing a subtype relationship here. Ideologically, `Span` is indeed an `IEnumerable`, but since `ref struct`s cannot implement interfaces today, the relationship isn't there. But that relationship plays in more than just overload resolution. Generics, for example, which is another place `ref struct`s cannot be used today. It feels like trying to mimic the behavior as best as possible would be a large rabbit hole to go down, when we do want `ref struct`s to be able to implement interfaces at some point. Implementing that would solve this issue and in a more general fashion. We also talked a bit about workaround the runtime could do for these cases. Defining more overloads is an obvious answer for some scenarios, such as the `ImmutableArray` case above. That could define `CreateRange(T[] array)` and `CreateRange(string s)`; indeed, this is the solution that `DefaultInterpolatedStringHandler` used to avoid ambiguities in `AppendFormatted`. But that doesn't work in every case. For example, constructors of generic types cannot have specialized versions for a specific generic. In other words, `HashSet` (and only `HashSet`) cannot have a constructor that takes a `string` to resolve that ambiguity. Extensions might be able to solve that, but it doesn't seem like it would give the actual end experience we want, namely the subtype relationship between `Span` and `IEnumerable`. Given this, we plan on waiting until `ref struct`s can implement interfaces and then all of these issues will work themselves out with no additional rules needed. #### Conclusion No language change here. ### Collection literals https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/pull/7284 We also looked at the proposed type inference for collection literals. We explored our existing behavior here around several areas: * Collection initializers on arrays - Arrays are not target-typed today, so conversion-from-expressions do not carry through the collection initializer here. We think this actually makes array collection initializers a _poor_ analogy for where to draw behavior from. * Switch expressions - These are target typed, and arm types can influence the type inference of a method. * Tuple literals - These might be the best analogy. They are target typed, and have a general desired behavior of "you should be able to replace the tuple with the elements and have the same behavior". In other words, for a signature of `M((T, T) tuple)`, passing a tuple literal should have the same outcome as passing each element individually to a signature of `M(T t1, T t2)`. We like the tuple analogy for collection literals. The behavior we want is the equivalent, spelled out as: If there is a signature `M(T[] array)`, passing a collection literal of `[1, 2]` should have the same effect as if the signature was `M(T t1, T t2)`. #### Conclusion Treat collection literals transparently for type inference. ================================================ FILE: meetings/2023/LDM-2023-07-12.md ================================================ # C# Language Design Meeting for July 12th, 2023 ## Agenda - [Collection Literals](#collection-literals) - [`Create` methods](#create-methods) - [Extension methods](#extension-methods) - [Interceptors](#interceptors) ## Quote of the Day - "I prefer the correct pronunciation, in-foof" ## Discussion ### Collection Literals https://github.com/dotnet/csharplang/issues/5354 #### `Create` methods https://github.com/dotnet/csharplang/blob/52b90748d1ebab0268468eb5dc8e954bc98c2834/proposals/collection-literals.md#create-methods We started by looking at the initialization pattern the working group and the BCL team have been working on. This pattern has changed a bit from the last time it was presented: now, instead of an `out Span` that the compiler would write into, the compiler passes in a `ReadOnlySpan`, which the collection will then copy out of. Methods would be discovered by looking for a specific attribute on the target type, which then tells the compiler what the build type is and what method name to look for. This has some amount of `methodof` to it, but in a much more restrictive sense; overload resolution is not involved at all. The compiler looks for a method in the type explicitly referenced, with the name explicitly specified, with exactly the parameter list `ReadOnlySpan`. Questions of things like default parameters or element type conversions don't play into it. Given these restrictions, we're ok with this pattern. It might be expanded in the future, but we think that our bases on breaking changes are well covered. ##### Conclusion Pattern approved, modulo BCL naming feedback. #### Extension methods https://github.com/dotnet/csharplang/blob/e566e33620e3f6671a7042a3e23cd1120ff04c76/proposals/collection-literals.md#extension-methods Next, we turned our attention to extension methods on collection literals. There were a few components to our discussion. First, assumed that extensions were allowed, and thought about element type conversions; put simply, would this work? ```cs var byteArray = [4].ToArray(); // Converts from int->byte ``` [Previously](LDM-2023-06-19.md#collection-literals), we made an analogy between collection literals and tuples. We think that analogy applies here, as an equivalent extension method would not work on a tuple, nor would it work for just an integer literal. Next, we looked at the broader scenario. These extension methods really exist for two reasons: * Allowing the compiler to infer the element type of a collection, while still providing explicit information about the collection type. * Allowing the type information to be put on the right side of the collection. Both features seem orthogonal to collection literals (https://github.com/dotnet/csharplang/issues/1349 is the former, and https://github.com/dotnet/csharplang/issues/4076 is very similar to the latter). Moreover, other typeless expressions seem like they could benefit from similar treatment. There's therefore a 3rd possible orthogonal feature: * Allow extension methods on expressions with no natural type. In any case, we think that this is a separate enough feature that we don't want to roll it in with collection literals, but instead want to take the time to explore the 3 options here to their conclusions for C# 13. ##### Conclusion Extension support specifically for collection literals rejected, we will explore the broader features for C# 13. ### Interceptors https://github.com/dotnet/roslyn/blob/d71ec683082104e9122a4937abc768710c5f7782/docs/features/interceptors.md Finally today, we took another look at the current state of interceptors. When we last discussed them and decided that they should be a compiler feature, we changed how we thought of the general idea; we now consider it effectively an instrumentation step, similar to inserting sequence points for debugging purposes. Since then, there's been a decent amount of development progress, one of the most controversial among the LDM is the removal of `InterceptableAttribute`. There's two main lines of argument here: * Opposing the change, there's concern about spooky action at a distance. In some ways, removing the attribute turns this feature into an unrestricted `comefrom`, otherwise known as the satirical inverse to `goto`. There were also arguments that this is very similar to `InternalsVisibleTo` vs `IgnoreAccessChecksTo`; the compiler supports the former, but not the latter. * Supporting the change, if a user came to the BCL and asked them to put `Interceptable` on some public API, when could the BCL realistically say no? These are are public APIs, not like the internal implementation details that access checks exist to protect. The user _can_ work around them by just calling a different method (possibly even just adding an extension with the same signature, just with `Interceptable` appended to the name), so the original method author being involved in the decision is odd. Another thing that's important to note is that this feature is very explicitly still experimental, and will still be experimental in .NET 8, so we're not locked into any decision permanently if we determine that it's incorrect after some end-user testing. We did not reach any conclusions here today, but the interceptors group has a good amount of feedback to go discuss and come back to in a couple of weeks. ================================================ FILE: meetings/2023/LDM-2023-07-17.md ================================================ # C# Language Design Meeting for July 17th, 2023 ## Agenda - [Compiler Check-in](#compiler-check-in) - [`readonly` parameters](#readonly-parameters) ## Quote of the Day - "Entra (ɛntrə) or Entra (ɑːntrə)?" "Depends on where you're from" "I can azure you there will be no questions about pronunciation" ## Discussion ### Compiler Check-in https://github.com/dotnet/roslyn/blob/673caaadc9780ad19d2295874607b635b017e0e3/docs/Language%20Feature%20Status.md To start, we wanted to check in with the features that the compiler is currently working on, when they're going to be shipping, and whether we need to rename any of them. * Inline Arrays * We're good with the name of this one. * Shipping in C# 12 * nameof accessing instance members * We're calling this `enhanced nameof` to a broader audience, but this is fine for a status page and for more specific documentation. * Shipping in C# 12 * Primary Constructors * We have existing name precedence from records and from other languages, so we're good with this name. * Will be shipping in C# 12. * Semi-auto props * Unfortunately, this one won't make C# 12. * Name isn't great. Many of us think it should have `field` in it somewhere, as that's what we've been internally calling it. * `field access for auto properties` is the proposal. * `params Span` * This won't make 12, as it needs inline arrays to be implemented. * There are also potential issues around https://github.com/dotnet/csharplang/issues/7276 when adding new overloads that take `params Span` that we'll need to address. * We're ok with the name of the feature at least. * lambda default parameters * We're fine with this name. * Will be shipping in C# 12. * Default in deconstruction * Fine with the name, there are still some LDM questions to answer. * Not shipping in C# 12. * Collection literals * We're worried that using the word literal here conveys the wrong first impression. Literals in C# are immutable (`unsafe` string buffer manipulation nonwithstanding), while these expressions are not; they give you a collection that is as mutable as the target type allows for. We suggested a few alternatives, and settled on Collection Expressions. * Will be shipping in C# 12. * Roles/extensions * The name in this file needs to be renamed to catch up to the current feature name `extensions`, but as this will only be in preview for C# 12 we aren't tied to the name yet. * In preview for C# 12. * Interceptors * The name is the least controversial thing about this feature. * In preview for the C# 12 compiler. * ref readonly parameters * Name works for the specific feature, though some of the broad docs might put it in a general ref enhancements bucket. * Will be shipping in C# 12. ### `readonly` parameters https://github.com/dotnet/csharplang/issues/188 https://github.com/dotnet/csharplang/blob/ee2c62fbacf3e84457198624c71abdca326c2cd8/proposals/readonly-parameters.md Finally today, we looked at a proposal for `readonly` parameters. This is a long-requested and highly-upvoted issue on the csharplang repo, but various members of the LDM have pushed back on it as not delivering enough value for the level of change it would bring. In particular, much of the LDM is concerned that adding `readonly` to parameters and locals changes what default "good C#" looks like. This is similar to initial designs of the nullable reference type feature, where we indicated non-nullability with a `!`, instead of indicating nullability with a `?`; for that feature, we felt that the ideal version of code requiring `!` modifiers everywhere was surprising and not where we wanted the language to be in 5 years. In the case of nullable, the scope of the problem being addressed was big enough that we felt comfortable adding an entire new dialect (via the `#nullable` directives) to move the language over to the new defaults. For `readonly` on parameters and locals, we feel similarly that we don't want the default of the language to be long modifiers on all parameters and locals, but are unconvinced that the scale of the problem is large enough to warrant language dialects like we did for nullable, given the narrow scope of locals and parameters. This calculus changes in the face of primary constructor parameters. These can get captured into the state of an entire type, potentially spread across multiple partial parts, and the safety benefits are therefore more substantial. We've received significant feedback, both internally and externally, that capturing primary constructor parameters is being considered "off limits" until they can be marked `readonly`. Given the previous concerns about the look of "good" C#, we are very inclined to restrict this to just primary constructors, at least initially, though the proposal does somewhat dictate the course of what both locals and parameters would look like if we were to do so. There's also an interesting question on `readonly struct`s, as we make the all the parameters implicitly `readonly` today; we require `readonly` on all fields in such a struct, do we need to require it on all parameters as well? Another question raised was whether we should re-examine the default mutability of primary constructor parameters. Perhaps the least surprising thing is to have the parameters as immutable, and require explicit field declaration when mutability is desired. There's some conflict here: how does that interact with `record struct`s, where the generated properties are mutable by default? And will that impact future designs in this space? Finally, we want to make sure that we don't lock ourselves out of any future feature work here. There's some feeling in the LDM that we'll eventually allow ways of declaring that a primary constructor parameter is a part of the class state, either as a field or as a property. We need to more completely explore this space and make sure that allowing `readonly` on a primary constructor parameter does not lock us out of such a future feature. #### Conclusion Enough interest to look at this in more depth, but in a more restricted form than initially proposed. ================================================ FILE: meetings/2023/LDM-2023-07-24.md ================================================ # C# Language Design Meeting for July 24th, 2023 ## Agenda - [Method group natural types with extension members](#method-group-natural-types-with-extension-members) - [Interceptors](#interceptors) ## Quote of the Day - "This conversation feels like it's gotten a little too much information" ## Discussion ### Method group natural types with extension members https://github.com/dotnet/csharplang/issues/7364 First up today, we looked at a potential source of confusion that was brought up during the prototyping of extensions. Our current rule was created to protect against a potential breaking change in this scenario: ```cs var c = new C(); var d = new D(); d.Del(c.M); // Today, this calles DExt.Del. With the change, it would call D.Del public class C { public void M() { } } public static class CExt { public static void M(this C c, object o) {} } public class D { public void Del(Delegate d) {} } public static class DExt { public static void Del(this D d, Action action) {} } ``` However, this seems like an unlikely scenario, and it's inconsistent with how extensions behave in this same scenario when all the signatures line up. It is also in conflict with general extension lookup, which goes scope by scope. We are generally in favor of making this change, though we do need to validate that it is not breaking to ASP.NET scenarios. The other remaining question is whether we can make this change retroactively, or if it needs to be a full language change in C# 13. We do have some precedence for making "spec bugfixes" after the initial release of the feature, but those are nearly always done within months of the initial release of a feature; we're over a year and a half past the release of C# 10 at this point, and there are very realistic scenarios where developers could have VS 17.8/9 on their local machines, observing one behavior, and then have the .NET 6 SDK in CI, where a different behavior would be observed. We therefore don't think we can make this change as a bugfix, and that it will need to be a gated change in C# 13. #### Conclusion Change is approved for C# 13. ### Interceptors https://github.com/dotnet/csharplang/issues/7009 https://github.com/dotnet/csharplang/discussions/7373 [Continuing](LDM-2023-07-12.md#interceptors) our review of interceptors, we looked at where experimental feature is being depended on in .NET 8. ASP.NET is planning on shipping it as part of their AOT scenario work, and has committed to doing the servicing work in the .NET 8 SDK to make sure that they adapt to any potential breaking changes that the compiler may introduce in the feature, up to and including falling back to extension method overload resolution tricks if the feature is pulled entirely. Such tricks do have cost, particularly in startup time of ASP.NET apps; given that AOT and startup time will continue to be a big focus of .NET, this would be an option of last resort. Previous LDM meetings suggested that we wanted to look at interceptors as a compiler feature, rather than a language feature. Unfortunately, the general space is basically impossible to make such a distinction with. It still needs to have tooling support at some point, is still C# specific, and is basically hijacking overload resolution. This approach is necessitated for all the reflection-based scenarios that use information that exists outside the type system to affect runtime code; because these scenarios use information not statically available during compilation, it is hard to make them AOT-compatible. To address this, we think that we need to take another look at the scenarios that are considering interceptors and see if we can put that information back into the type system: are there features like string value types that we could leverage to avoid needing these type-system adjacent features? We will be forming a working group to drill down to the specific cases looking at interceptors, dive more deeply into what exactly they need, and what we can do to address them within the language. ================================================ FILE: meetings/2023/LDM-2023-07-26.md ================================================ # C# Language Design Meeting for July 26th, 2023 ## Agenda - [Primary constructor parameters and `readonly`](#primary-constructor-parameters-and-readonly) ## Quote of the Day - "`record`s, the other white meat" ## Discussion ### Primary constructor parameters and `readonly` https://github.com/dotnet/csharplang/issues/2691 https://github.com/dotnet/csharplang/issues/188 https://github.com/dotnet/csharplang/discussions/7377 https://github.com/dotnet/csharplang/blob/bc2c69e6f2bd28373e341effaa1182ebcb7ea72c/proposals/readonly-parameters.md Continuing on from [last Monday](LDM-2023-07-17.md#readonly-parameters), we took a look at the broader space of primary constructor parameters, their default mutability, and how we want to address community feedback in this space. With the exception of `!!`, this is the most feedback we've ever gotten on a preview feature, and if we want to address this in any way other than adding a `readonly` modifier at a later date, we need to decide that very soon. Broadly speaking, the feedback we've seen is requesting that users want to be able to be confident that type state is not being modified, and that the compiler will help them enforce this. We see a few ways of addressing this: 1. Do nothing. Leave primary constructors as mutable, and do not plan on addressing this. If a user wants to enforce readonly class state, they should declare a class member (field or property), explicitly assign into that member, and use that member, not the parameter. 2. Plan on adding a `readonly` keyword. This keyword may come with C# 12, but might also be delayed until C# 13. There are open questions as to what this keyword would mean: would it follow field `readonly`, where the parameter capture would actually be mutable during initialization, or not? But by default, primary constructor parameters would always be mutable unless otherwise specified. 3. Follow the default established by record primary constructors. This would mean that all primary constructor parameters would be mutable during initialization (as in all `record` types today), but for `class`es and `readonly struct`s, they would be readonly after construction. 4. Have all primary constructor parameters on non-record types be always `readonly`. This is a big list of options, and worse, they come with various correspondences to existing things in the language. Option 1 is parameters as they exist today: they're always mutable, for better for or for worse. Option 2 corresponds with thinking of primary constructor parameters as baby type members: they start with the same set of defaults that normal type members have, and then we might at some point add the ability to promote them inline to full type members, with all the same defaults that other type members get. Option 3 corresponds to records as they exist today, and if we take an opinionated stance on primary constructor parameters now, it might allow us to continue to take an opinionated stance for future features more easily. For example, maybe `public int I`, as a primary constructor parameter, could always mean a `get`-only property. Option 4 is the one the stands out here, as it doesn't really correspond to existing language principles in any way today. It is a position that many LDM members might wish we'd taken from the start of the language, but isn't really in line with any prior art in the language, which we feel would be a negative surprise. Any form of readonly by default is proving to be a contentious topic. There is a nasty footgun that comes with this for mutable value type parameters, where silent copies can occur without an explicit `readonly` somewhere in the user's code. This is something that can bite even experienced users with today's semantics where `readonly` must be opted into, and it seems likely that a `readonly`-by-default decision might make that worse. At the very least, we may want to consider a warning when calling a non-`readonly` member on a primary constructor parameter. Another point of concern is when `readonly` comes into force. It seems likely to us that some amount of validation and value clamping may want to be done as part of initialization; should that require declaring a full member? Or should the parameter be mutable during initialization, so that validation steps can occur? This is more of a future correspondence question than it is a concern for C# 12/13; if we do choose to allow `readonly` primary constructor parameters to be modified during initialization, that sets a precedent for what `readonly` on a parameter means. This then informs, and potentially harms, the ability to use `readonly` as a general parameter modifier later on in the language's lifetime, since it would have a different meaning there. While the LDM's opinion on that feature hasn't changed, it is something important to consider. We also looked at the topic of boilerplate, and how much we would harm the general reduction of boilerplate by making things `readonly` by default. We're pretty universally against adding a new `mutable` keyword to C#; that would mean that, in order to get mutable state, users would be required to fall back to using a full field. In the other direction, in order to have `readonly`ness in a feature where mutable parameters are the default, users would be required to put `readonly` on the parameter, just as they are required to do for fields. While we do think that more object state is effectively readonly than is not, some members are concerned that enough state is mutable (and actually mutated) that the `readonly`-by-default solution would not help as many users as the mutable-by-default version. Importantly, this isn't just a war on boilerplate for the sake of brevity: there is mental tax that comes with repetitive boilerplate that we would like to avoid. Another argument (yes, there's still more to go) in the future evolution space was how far we're prepared to go with primary constructor parameters. We've looked at promotion to members: what about when those members need to have more complicated logic? Where is the dividing line between "This can be a primary constructor parameter" and "this must be a full member"? We don't want to ship something that needs a decoder ring to understand, which is something that we're concerned about if we did option 3 and then allowed promoting to full type members. We think we'd have reasonable and understandable defaults for `private` and `public`: `private` fields, `public` properties. But we are worried about `protected`; sometimes they're effectively part of the public API, and properties are the best choice. Other times, they're effectively `private protected`, and fields are the better choice. There's a potential soup of modifiers and features here that could be extremely confusing to readers, which is something we want to be very careful of. Considering all of this, we are about split between options 2 (add `readonly` as a modifier) and 3 (match records). This means that option 1 (do nothing) and option 4 (always `readonly`) can be excluded. We think we need a few days to consider these options before coming back as a group on Monday to make a final decision. #### Conclusion We've eliminated two options. We'll come back Monday to conclude on this topic. ================================================ FILE: meetings/2023/LDM-2023-07-31.md ================================================ # C# Language Design Meeting for July 31st, 2023 ## Agenda - [Primary constructor parameters and `readonly`](#primary-constructor-parameters-and-readonly) ## Quote of the Day - "Maybe I damaged my brain in the sun" ## Discussion ### Primary constructor parameters and `readonly` https://github.com/dotnet/csharplang/issues/2691 https://github.com/dotnet/csharplang/issues/188 https://github.com/dotnet/csharplang/discussions/7377 https://github.com/dotnet/csharplang/blob/bc2c69e6f2bd28373e341effaa1182ebcb7ea72c/proposals/readonly-parameters.md We picked up again from [last Wednesday](LDM-2023-07-26.md#primary-constructor-parameters-and-readonly). To restate the options we'd started with in that meeting: 1. Do nothing. Leave primary constructors as mutable, and do not plan on addressing this. If a user wants to enforce readonly class state, they should declare a class member (field or property), explicitly assign into that member, and use that member, not the parameter. 2. Plan on adding a `readonly` keyword. This keyword may come with C# 12, but might also be delayed until C# 13. There are open questions as to what this keyword would mean: would it follow field `readonly`, where the parameter capture would actually be mutable during initialization, or not? But by default, primary constructor parameters would always be mutable unless otherwise specified. 3. Follow the default established by record primary constructors. This would mean that all primary constructor parameters would be mutable during initialization (as in all `record` types today), but for `class`es and `readonly struct`s, they would be readonly after construction. 4. Have all primary constructor parameters on non-record types be always `readonly`. We'd eliminated option 1 and 4 during that meeting (4 very decisively, 1 not very decisively), leaving options 2 and 3. One important clarification we made in this meeting, though, was that 1 and 2 are not actually mutually exclusive. Many members of the LDM would be fine shipping C# 12 with 1, and then doing a `readonly` keyword in C# 13. So we're really debating between 2 and 3, but not necessarily committing to doing 2 before C# 12 ships. The main thrust of the debate between 2 and 3 is the presence of opinion in mainline C# code that is different than C# opinion elsewhere in mainline code. Parameters are always mutable by default in C#, as are fields; it's only in automatically generated `record class` that we default to `readonly` (or more specifically, `init`). So no matter where we end up on this issue, we have a decoder ring for users: either we need to explain why we chose to make primary constructor parameters `readonly`, or why we chose to make `record`s different than the rest of the language. This distinction revealed an important point in the debate, however: are we bringing primary constructors, as created by `record`s, back to the rest of the language, or are we bringing existing creation patterns forward with primary constructors, and viewing `record`s as a step further? One thing LDM was immediately unanimous on was not wanting a `mut` or `mutable` keyword, even if we were to go with option 3. We don't have such a concept elsewhere in the language, and needing to add it here is immediately concerning. We also investigated whether we can confidently promote primary constructor parameters to full type members in a simple fashion: could we, for example, have `private` automatically turn into a `private readonly` field, and `public` automatically turn into a `public { get; init; }` property? We're not sure about this either: `private` seems fine, but the `public` version feels iffy. Is `init`, as in `record`s, the right default? And if it's not, is that yet another area where we'll need to explain the differences between `record`s and non-`record`s? In terms of evolution, there also a number of LDM members who are concerned about the slippery slope that is evolution in the first place. How far should we plan on taking this? We start with simple member declarations, but then those declarations might need validation, default values, differing public and private names, the ability to specify partial when we eventually allow `partial` properties, etc. At what point do we say that we've gone far enough and that any further member-like things need to be actual type members? It's clear that will be a question for our future selves, and that it's unclear where we'll land when we get there. We finally turned back to the question of what direction of evolution we are intending for primary constructor parameters. Another way of looking at this question is: if we had flipped the design order, delivering primary constructor parameters before `record`s, instead of the other way around, would we be doing anything differently? We don't think so: when we look at how we want to explain the language, we think that there's a core set of rules around mutation that apply everywhere in the language, _except_ for `record`s. `record`s exist to allow C# users to work with data more easily, and they contain a number of opinions that make this easier, from value-based equality to `readonly`ness for reference types. We think that this is the C# we want to explain; the alternative would be explaining that primary constructors differ from the rest of C# by bringing in opinions from `record` types. It would potentially be easier to explain to existing users, but harder to explain the language philosophy as a whole. We therefore conclude that primary constructor parameters will ship in C# 12 as is. We will prioritize work on a `readonly` modifier for primary constructor parameters, including figuring out whether this modifier will mean `readonly` like it does for fields, where it is mutable during initialization, or if will mean fully `readonly`, with no ability to mutate at all. This may not make it into C# 12, but we will target early previews of C# 13. #### Conclusion Primary constructor parameters will ship as mutable-by-default, and we will investigate the `readonly` parameter modifier shortly. ================================================ FILE: meetings/2023/LDM-2023-08-07.md ================================================ # C# Language Design Meeting for August 7, 2023 ## Agenda - [Improvements to method group natural types](#improvements-to-method-group-natural-types) ## Quote of the Day - "I've never said I'm not a hypocrite" ## Discussion ### Improvements to method group natural types https://github.com/dotnet/roslyn/issues/69222 The proposal is a possible improvement over the mechanism decided on [July 24th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-07-24.md#method-group-natural-types-with-extension-members), where the uniqueness of a method group that allows it to have a natural type is determined scope by scope rather than across all possible instance and extension methods. In that proposal, an instance or extension method can still be considered to apply, even if it would later be rejected by subsequent checks. For instance, type arguments might not satisfy constraints: ``` c# var x = new C().M; // CS8917 The delegate type could not be inferred. public class C { public void M() { } public void M(object o) where T : class { } } ``` The proposal is to move these checks earlier, removing failing candidates so another candidate can be unique. This additional pruning would cause us to depend on more details: It would succeed in more scenarios but at the cost of being more vulnerable to changes in those details. However, we accepted those same tradeoffs in method invocation scenarios several versions ago during a round of overload resolution improvements. This seems equivalent. The proposal would incur slight breaking changes. They seem fairly negligible, but we should give it time in preview to confirm that we are not missing scenarios. #### Conclusion Change is approved. It will not be in C# 12, as we need bake time in preview to confirm that breaks are acceptable. ================================================ FILE: meetings/2023/LDM-2023-08-09.md ================================================ # C# Language Design Meeting for August 9, 2023 ## Agenda - [Lambdas with explicit return types](#lambdas-with-explicit-return-types) - [Target typing of collection expressions to core interfaces](#target-typing-of-collection-expressions-to-core-interfaces) - [Loosening requirements for collection builder methods](#loosening-requirements-for-collection-builder-methods) ## Discussion ### Lambdas with explicit return types https://github.com/dotnet/roslyn/issues/69093 For method arguments that are lambdas with explicit return types (and hence also explicit parameter types), this proposal removes the requirement that the lambda body bind correctly from overload resolution. As best we can tell, the requirement is vacuous, and no breaking behavior would come of this change. The check is known to be algorithmically heavy in nested scenarios, and can cause an IDE to be bogged down for a considerable time. This is the case for implicit lambdas as well, but by removing the check from explicit lambdas, those could be a fallback for code that currently runs into this problem. #### Conclusion Approved. We couldn't think of any way this would cause a change in semantics. ### Target typing of collection expressions to core interfaces https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/Core-interface-target-type-proposal.md What should C# do when a collection expression is target typed (implicitly converted) to one of the core interface types ( `IEnumerable`, `IReadOnlyCollection`, `IReadOnlyList`, `ICollection` and `IList`, plus their non-generic counterparts)? ``` c# IEnumerable numbers = [ 1, 2, .. otherNumbers, 3 ]; ``` If we allow this at all, we need to decide how a value of a concrete type is obtained for each interface. Our choice here will impact several key aspects of the design: - Simplicity - it's easy to explain. - Universality - it works everywhere. - Brevity - you rarely need to embellish (with casts or the like). - Performance - it maintains or improves performance compared to manually written creation code. - Safety - avoids unwanted and surprising mutations through downcasting. We considered the following general approaches: 1. Do not allow target typing to interfaces 2. Specify and guarantee corresponding concrete types 3. Transparently pick concrete types as an implementation detail The argument for supporting target typing to the core interfaces is that it will be very common. Especially many APIs (about 25% of all collection-taking APIs) take `IEnumerable`. If we go with option 1, collection expressions will not satisfy the goal of replacing the overwhelming majority of collection creation scenarios. We've postponed other aspects of collection expressions (natural types, dictionary expressions) to future versions of the language, but this scenario seems much more mainline, and would be hard to live without. The language already has special knowledge of these interfaces: Arrays are allowed to implicitly convert to them, and the `IEnumerable` interfaces are consumed by `foreach` and produced by iterators. So building specific behavior into the language for them would be nothing new. Option 1 would only be the best option if we cannot make a good choice on behalf of the user. Option 2 and 3 are different strategies for how to make that default choice for them. With option 2 we specify exactly which type gets instantiated for each interface. The option is very simple to understand if we pick a single type across all interfaces (e.g. `List`), but that is not great for performance (`List` always comes with two allocations, extra space to support mutation, etc.) or for safety (you can cast and mutate from the read-only interfaces). Alternatively we can pick different concrete types for each interface, but now it's a lot less simple! Also, for the read-only interfaces (including `IEnumerable`) we don't actually have ideal concrete types in the BCL today. Option 3 gives us full freedom to pick ideal concrete types for each interface, or even different types for the same interface, depending on specific circumstances. In fact, the compiler can synthesize a brand new type for a given occurrence if that's the right trade-off, or the runtime can provide specifically optimized types that aren't in the normal public line-up. We could change our minds on the specifics from release to release, since users can't (easily - and definitely shouldn't!) take a dependency on the specific choices we make. For the readonly interfaces, we could probably get significant performance optimizations. We could inline values, eliminate the count, reuse the object as its own enumerator to save an allocation (we use this trick in iterators today), etc. For empty collections we could reuse the same object every time. For the mutable interfaces, `List` would actually be a fine choice. However, even if we use it, we wouldn't guarantee it version over version. The approach of not telling you the exact type already has precedence in the language in the form of iterators, as well as in the BCL through LINQ query operators. The philosophy is also similar to pattern matching, where the logic of a `switch` is highly optimized, and usually faster than what the user would have manually written. Looking to the future, there aren't good existing dictionary types to implement `IReadOnlyDictionary`, and the optimal implementation strategy would depend heavily on e.g. the size of the dictionary. We could probably generate much better ones than could be provided in the BCL, because we can let the circumstances decide. With option 3 users can't get a clear answer to what type we create. On the other hand, being able to tell people we will give them *a really good one* has its own simplicity! The upshot is that option 3 is simple only if the user can trust us to do so remarkably well in the overwhelming majority of cases that they never have to think about it. We can't ignore the non-generic core interfaces (`IEnumerable`, `ICollection` and `IList`). They probably don't change the overall decision, but they should be handled. Many APIs and frameworks, especially older ones, rely on these, but not actually often as a target type. More commonly they come in as `object` and are type-tested against these interfaces at runtime. So while target typing *should* of course work for the non-generic interfaces, it is in fact more important that the concrete types we provide for the *generic* interfaces also implement the appropriate corresponding *non*-generic interfaces. That way, those type-discovery scenarios would work well over collections generated from collection expressions. There's a more general question about type tests in option 3. If we do use a public type, people could discover it. Or they could discover any other collection interfaces that the thing implements. The latter might not be uncommon, so we should make deliberate decisions about that. As a final consideration, this decision might intuitively seem linked to the (currently postponed) question about natural types for collection expressions - what do you get when you use `var`?. However, the situations are different: With interfaces there's a clearly stated surface area to match, and it's ok for the concrete type not to be able to do anything else. For natural types there's a much more complex decision about what surface area to provide by default when people don't give a type, and there's no particular reason for that design choice to be linked to target typing. ### Conclusion We unanimously support option 3. We don't want to give even the most performance-conscious users any reason to shun collection expressions. We like the wiggle room for further optimizations in the future, and we think it is simplifying for the user not to have to care about exactly what gets generated. It fits the declarative motto of saying the "what", not the "how". The working group will decide on the specifics for each target interface. ### Loosening requirements for collection builder methods https://github.com/dotnet/csharplang/issues/7396 One of the ways types can support collection expressions is through a builder pattern. An example in the BCL is `ImmutableArray`. We put a `[CollectionBuilder(...)]` attribute on it to say where to find a suitable factory method, in this case the existing `ImmutableArray.Create` static method. In the BCL we have an interface `IImmutableList` as well. The BCL could choose to point it to *another* factory method, but not the same one as above, because our current rule says the method must return the *exact* target type. This seems overly restrictive, and the proposal is to allow certain implicit conversions from the return type of the method to the type carrying the attribute. There's a range of choices around which conversions to allow. In order from loosest to tightest: 1. Allow any implicit conversion. 2. Allow standard conversions (excludes user-defined conversions). 3. Allow reference and boxing conversions. 4. Allow identity conversions only (i.e., don't fix the scenario). #### Conclusion We support option 3, reference and boxing conversions. It's a reasonable but conservative compromise that solves the scenario we have today, without forcing us to think through too much weirdness. ================================================ FILE: meetings/2023/LDM-2023-08-14.md ================================================ # C# Language Design Meeting for August 14, 2023 ## Agenda - [Betterness for collection expressions and span types](#betterness-for-collection-expressions-and-span-types) - [Type inference from collection expression elements](#type-inference-from-collection-expression-elements) - [Collection expression conversions](#collection-expression-conversions) ## Discussion ### Betterness for collection expressions and span types https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-LDM-2023-08-14.md#overload-resolution Overload resolution will generally consider a more "specific" overload better. Normally "specificity" is expressed through the existence of an implicit conversion from the more to the less specific, but occasionally we need another measure of specificity in the betterness rule. One previous example of this is [interpolated string handlers](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated#compilation-of-interpolated-strings). The proposal is to explicitly declare the span types `Span` and `ReadOnlySpan` more specific - "better" - than arrays and the core collection interfaces when targeted by a collection expression, even though an implicit conversion does not exist. The motivation is that picking a span overload is likely to be more efficient, since the span created from the collection expression may potentially be allocated on the stack instead of the heap. One question is whether this should apply only to the "official" span types, or whether it should generalize to all ref structs that satisfy the requirements for being created by a collection expression? After all, it is not unreasonable to expect that other people have ref struct collection types of their own. Also, should this apply only when compared to arrays and interfaces, as currently formulated, or also against concrete (class or struct) types like `List`? Would an ambiguity error better match expectations? #### Conclusion Yes to the overall proposal: Span types should be better. The working group will come back with recommendations for the open questions. ### Type inference from collection expression elements https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-LDM-2023-08-14.md#type-inference The proposal allows type inference to recursively inspect the element expressions of a collection expression, similar to tuples and lambdas. This allows for better inference results, and fewer meaningful cases that lead to errors. It gracefully handles empty collections, and "coordinates" better with other parameters. #### Conclusion Adopted! ### Collection expression conversions https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-LDM-2023-08-14.md#conversions There are five ways in which types can qualify for having a conversion from collection expression. For four of them, there must be an implicit conversion from each element expression, and from the iteration type of each spread element, to the iteration type of the target type. For the fallback case of collection initializer types, however, the iteration type is not used, because the expression is instead mapped to individual `Add` calls, and each could resolve to a different overload. (Are there situations where a collection expression conversion should be considered an identity conversion in order to be allowed as an `in` argument? The working group will explore this question.) This does not support "legacy" collection initializer scenarios where `Add` methods take more than one value. This is mostly used for dictionaries today, and when we start supporting dictionaries we would include those scenarios. #### Question 1 Should the list of core collection interfaces for which conversions a supported be given as an explicit list, or simply "the generic interfaces implemented by `List`"? ##### Conclusion It should be an explicit list. This should not "automatically" grow in the future if `List` implements new interfaces, but each should be a result of a deliberate decision. #### Question 2 Should conversions to `Memory` and `ReadOnlyMemory` be supported? ##### Conclusion This doesn't fall out automatically. The working group should explore this further. #### Question 3 Should conversions to [inline arrays](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/inline-arrays.md) be supported? ##### Conclusion Yes, but it is low priority to expose in this release. Efficient implementation of interface target types will probably use inline arrays under the covers regardless, so this might not be all that expensive to implement. ================================================ FILE: meetings/2023/LDM-2023-08-16.md ================================================ # C# Language Design Meeting for August 16th, 2023 ## QOTD - "We're done voting so can I finally start making cracks about 2B or not 2B?" - "That is the question!" ## Agenda - [Ref-safety scope for collection expressions](#ref-safety-scope-for-collection-expressions) - [Experimental attribute](#experimental-attribute) ## Discussion ### Ref-safety scope for collection expressions https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/LDM-questions-2023-08-15.md#ref-safety When a collection expression is targeted to a ref struct type, such as `Span` and `ReadOnlySpan`, what should be the ref-safety scope - the "lifetime" - of the created value? Take code like this: ``` c# Span s = [x, y, z]; ``` There is a fundamental trade-off between efficiency and usefulness. If we make the scope local then `s` can't escape, and we can e.g. allocate on the stack. If we make the scope global, then it will be allowed to escape, in which case we need to allocate it on the heap. If we decide on global scope, there is an option to try to be smart about this, deducing from usage that the value does not in fact escape and still allocating on the stack in those cases: we'd effectively be inferring the scope from usage. So this leaves us with the following options: 1. Try to be smart and infer scope from usage 2. Decide scope from declaration alone A. Make the scope always local B. Make the scope always global Option 1 seems immediately attractive, as it supposedly minimizes the set of situations where this becomes an issue. However, it really means global scope with potential optimizations. How can a user reason about whether the optimizations kick in? How can they trust that a subtle code change doesn't suddenly lead to heap allocations? In that sense it is almost the worst of both worlds. An analyzer could help catch when this leads to allocations. It would in practice have to be implemented in the compiler, even if it isn't presented that way, because of the complexity of the analysis. If we do go down the path of optimizing implicitly, then people will justifiably take a dependency on the performance profile of that, so we would be bound by our behavior here even if we didn't specify it. We could only ever improve, not deteriorate. For option 2 we need to look at what a developer can do if we make the "wrong" choice for them: - If we pick local and they want global, they would have to explicitly allocate it as an array rather than a span (using an array creation expression `new [] { ... }` or casting a collection expression to an array type `(int[])[ ... ]`), then implicitly convert to the span type in question. This leads to less than elegant code, and may be hard to discover the need for. - If we pick global and they want local, in the example of the local variable `s` above, they could explicitly add `scoped` to the variable declaration. However, in other scenarios, e.g. when passing the collection expression as a method argument, there is no place to put `scoped`. They would need to factor it out to a local variable or something to achieve that. Both are pretty inconvenient. How to choose? When dealing with span types, it feels like people generally expect optimal behavior, i.e., no allocations. They would like to be told if they can't get that, and make any allocations explicit. This certainly points in the direction of option 2B. The fact that the scope of a ref struct variable is currently set based on what it's initialized to is surprising to many people who aren't used to spans, but that is something you just have to get used to. If we said collection expressions assigned to ref structs are always local scope, the performance community would just say okay. It does feel like it is relatively rare that ref struct arguments escape, so 2B would be a good default. However, the syntactic choices for when you need to go against the default are unappetizing. The array creation expression approach also has subtly different type inference from collection expressions! Perhaps we could have a new keyword (`global`? `new`?) that you could put in front of a collection expression to force globalness? That could be added later if this turns out to be a bigger problem than we expect. #### Conclusion 2B: always local. We'll keep an eye on how inconvenient this choice becomes in practice, and consider better options for switching to global scope in the future if necessary. ### Experimental attribute https://github.com/dotnet/csharplang/blob/2c86608e9326845a94438fd512cb3df54e1170fd/proposals/csharp-12.0/experimental-attribute.md The intent of the `[Experimental]` attribute is to signal that an API is experimental, and force clients to explicitly opt in to the risks of that dependency, e.g. the risk of breaking changes. It's mostly the same as obsolete in behavior, with the difference that when you're in the context of an `Experimental` attribute it turns off warnings about the use of experimental things inside. Also it can be provided at the assembly/module level to apply to a whole library. The attribute takes a diagnostic id as a parameter, so that it's not all or nothing: You can turn on or off on a per-experimental-API level. That is expected to typically be done in project files, not using pragmas in code. It would be used equally for BCL features and third party libraries. A library author that depends on an experimental feature should either shield their consumers from breaking changes in the feature, or in turn advertise the attribute to *their* users. Unlike `[Obsolete]` the expectation is that you would eventually remove `[Experimental]` from an API. Are there thoughts on how a client can clean up their suppression when that happens? It's probably hard to tool, because of the combinations of versions that would go into it. As a potential slight hole, if you somehow get your hands on a value of an experimental type without mentioning the type itself, you get to use it without warning. This is especially observable through extension methods, which are often usable without their containing type being explicitly mentioned: If the containing type is marked experimental but not the extension method itself, the consumer will then not get a warning. #### What do assembly-level attributes mean? Should assembly-level use of `[Experimental]` apply recursively to members and nested types, or just to top-level types? Options: 1. apply to both types and members 2. only apply to types (including nested types) 3. only apply to top-level types ##### Conclusion We prefer option 1: both types and members. #### Warnings or errors At the language level, the diagnostics about use of experimental APIs cannot be errors, since errors cannot be silenced. However, it's desirable that these warnings are promoted to error severity by default, as unintended dependency on an experimental API is quite bad. For other attributes, such promotion to error can be done in e.g. an editor config file, but because this one trades in an open-ended set of custom diagnostic IDs for each experimental API, it seems infeasible to list them all in a file. Could we have an overarching diagnostic ID that refers collectively to all experimental features? If so, we can use existing tools to get the desired severity. ##### Conclusion Keep the language-level diagnostic a warning and use an overarching diagnostic ID to allow changing the severity of all experimental API use. ================================================ FILE: meetings/2023/LDM-2023-09-18.md ================================================ # C# Language Design Meeting for September 18th, 2023 ## Agenda - [Collection expression questions](#collection-expression-questions) - [Optimizing non-pattern collection construction](#optimizing-non-pattern-collection-construction) - [Avoiding intermediate buffers for known-length cases](#avoiding-intermediate-buffers-for-known-length-cases) ## Quote of the Day - "I want to know why you're laughing" "Just the concept of making 100 temps" ## Discussion ### Collection expression questions https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/issues/7542 #### Optimizing non-pattern collection construction Our first topic today was a discussion around whether we can or should call `.ctor(int capacity)` constructors for collection types, when available. This is a bit different from other method-based patterns that we've used in the past, as we would need to look for the actual parameter name `capacity`; our previous examples in this space do not look for parameter names, or even require the parameter to be named at all. However, we definitely have some examples of where not looking for the name would be a problem for existing collection types. `System.Collections.Concurrent.BlockingCollection`, for example, has a single-`int` constructor that does not do what the user would want here. There's some concern, though, that even checking for `capacity` isn't good enough. For example, the BCL has a type called `System.Net.CookieContainer` with a constructor with a single `int` parameter `capacity`. This type _isn't_ constructible with a collection expression, as it's not `IEnumerable` and doesn't implement the new collection builder pattern, but it's concerning how close it is to being adversely affected by this change. There are a few options we could take to mitigate this. The first is some sort of opt-out attribute, as we've discussed [before](https://github.com/dotnet/csharplang/discussions/5278), [2](https://github.com/dotnet/csharplang/issues/3211). We could also take an opt-in approach instead, where type authors need to add some sort of attribute to their types or constructors in order to allow them to participate in this pattern. However, there's a tension with this change around how much we want to change/can change from older-style collection initializers. On the one hand, most of the time that users will hit this optimization, it'll be because they're using a type that hasn't adopted the new builder pattern. For those types, this is more likely to mean that it wouldn't have seen _any_ modifications to work with the new feature, so would an opt-in approach actually help anyone? Potentially, but certainly less than if the optimization was more broadly applied. Ultimately, we don't think we're at the right point in the release cycle to make a large assumption about `capacity` at this time. We'll look at special casing some well-known BCL types that won't be moving to the collection builder pattern, but won't make broad assumptions in the C# 12 timeframe. We'll look at it as part of the collection expression changes we're planning for C# 13. ##### Conclusion We'll optimize well-known BCL types, not all types at this time. #### Avoiding intermediate buffers for known-length cases Second, we looked at whether or not the compiler needs to guarantee that intermediate buffers are avoided for cases with spreads where we need to evaluate the spread to get the length. This could cause the compiler to need a large number of temp variables to ensure that left-to-right semantics are preserved for the expression. We boiled this down to a set of questions about the principles that we want the language spec to declare, given this example: `[a, b, c, .. d, e, .. f]` * What is the ordering of evaluating the expressions themselves? Do we guarantee that `a`, `b`, and `c` are evaluated before `d` is evaluated? * This is pretty historically ingrained in C#. * What is the ordering of evaluating implicit expansions, namely the enumerations? Do we guarantee that `d` is enumerated before `e` or `f` is evaluated at all? * Guaranteeing this would mean that using multiple spreads in a collection expression would be a performance hit, as we can't find out what the length of `f` unless we fully enumerate `d`. We'd need to either have temporary storage for the elements of `d`, or couldn't presize the collection. * What is the ordering of the constructor call of the target type during all of this? Do we guarantee that it will always be called after `f` has been evaluated? * We have some prior art with changing existing code semantics here: interpolated string handlers in C# 10 caused `StringBuilder.AppendFormatted` to change evaluation orders slightly. We have seen a bit of user confusion around that, but not big. And, importantly, that was changing _existing_ code. This change would not affect existing code, only new code. We unanimously felt that we need to at least guarantee LTR ordering of the expressions themselves. It seems perfectly reasonable that someone might have side-effecting expressions inside a later expression that would affect an earlier expression if run out of order, especially given our strong legacy around LTR semantics in C#. We're more conflicted on requiring _enumeration_ of the collection, however; it feels particularly weird that a collection that exposes a `Length` would have an enumerator that would have a side effect on a later element in the containing collection expression. We also feel comfortable not specifying exactly when the collection expression constructor occurs within the collection expression; we do have prior art here with interpolated strings, and this isn't nearly as concerning a case given the explicit step users need to take to move to the new semantics. Given all of this, we think we're fine with requiring that collection expressions guarantee the ordering of expression evaluation, but not exactly when spreads will be enumerated, or exactly when the constructor will be called. We are also comfortable not requiring the compiler to avoid all intermediate collections for large numbers of temps; the compiler is free to do what it can determine is best for the scenario. If it heuristically believes that it would be better to allocate a temp array on the heap, it is free to do so. ##### Conclusion Compiler is free to use temp buffers if it believes it should, and is not required to enumerate spreads before evaluating arguments following a spread. It is required to ensure that expressions are otherwise evaluated from left-to-right. ================================================ FILE: meetings/2023/LDM-2023-09-20.md ================================================ # C# Language Design Meeting for September 20th, 2023 ## Agenda - [Collection expressions](#collection-expressions) - [Type inference from spreads](#type-inference-from-spreads) - [Overload resolution fallbacks](#overload-resolution-fallbacks) ## Quote of the Day - "I live in a double-wide future" ## Discussion ### Collection expressions https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/issues/7542 #### Type inference from spreads Our first question for today is around type inference from spread elements contributing to inferring the element type of a collection expression. This is how the working group spec'd the feature, and how it was implemented, but we're confirming with LDM that the rules behave as we'd desire. We do have some concerns with the specificity needed for this feature: it seems like we have a similar feature in tuples, and it would be great if there was a general rule about inferring element types from nested element expressions that subsumed all of these cases. We do generally think that behavior specified is correct, however, and will proceed with spec'ing it as having a single set of rules that can affect all the scenarios like this in the language. ##### Conclusion Behavior is approved. We will work on making a general spec rule that covers this, tuple cases, and other similar scenarios without having to make bespoke rules for each location. #### Overload resolution fallbacks Our second and final topic today relates to overload resolution in ambiguous scenarios with the ref struct preferencing rule. For example, this scenario is ambiguous today: ```cs static void A(Span value) { } static void A(string[] value) { } A([string.Empty]); // error: ambiguous ``` There's some precedent for this type of behavior in the language, with how conversions are handled between an API like this: ```cs static void B(IReadOnlyList e) => Console.WriteLine("List"); static void B(IEnumerable e) => Console.WriteLine("IEnumerable"); B(new List() { "one", "two" }); ``` For both of these scenarios, the LDM is in general agreement that we don't want the scenario to work. It's not immediately obvious to anyone, reader or compiler, which of these two overloads is the better one to call. We also don't believe API patterns like this are realistic. However, there are similar scenarios where we _do_ need to have some sort of element convertibility rule in order to have the right API chosen. As an example of this: ```cs static void C(ReadOnlySpan r); static void C(object[] r); C(["1", "2"]); // Ambiguous today, because object[] and ROS cannot be converted between ``` In this scenario, the LDM believes it's perfectly reasonable for such an API pattern to arise as a library evolves. We also believe that it's unambiguous which one is the "better" API; the `ReadOnlySpan` can be allocation free, and the inner type is also more specific than the element type of `object[]`. However, since we cannot convert between `ReadOnlySpan` and `object[]`, the rules as written today would make this ambiguous. We have a few different rule proposals that might make this possible, given our previous rule of preferencing ref structs over other types: 1. Require that there be an identity conversion between the iteration types. This would enable `string[]` vs `ReadOnlySpan`, but would prevent `object[]` and `ReadOnlySpan`. 2. Require that there be an implicit conversion from the ref struct's iteration type to the other type's iteration type. This would enable `object[]` vs `ReadOnlySpan`, but not `string[]` vs `ReadOnlySpan`, as there is no implicit conversion from `object` to `string`. 3. Always prefer the ref struct, no consideration of element type convertibility at all. 4. No special casing whatsoever. After some deliberation, we settled on 2, feeling that it's a good match for user expectations of the feature. However, we then looked at how far we want to apply this rule. So far, all of the examples we've looked at relate to `(ReadOnly)Span` vs one of the interfaces that arrays implement. We're concerned that applying the rule more broadly, particularly with so little time left in C# 12, will have some unintended consequences. We don't think we can categorically say that all ref structs should be preferred over non-ref struct counterparts, so we'll restrict the rule to just being for `(ReadOnly)Span` vs arrays and all the interfaces arrays implement. ##### Conclusion We will prefer `(ReadOnly)Span` over arrays and interfaces implemented by arrays in overload resolution for an argument with a collection expression conversion when there is an implicit conversion from the element type of the `Span` to the element type of the array or array interface. ================================================ FILE: meetings/2023/LDM-2023-09-25.md ================================================ # C# Language Design Meeting for September 25th, 2023 ## Agenda - [Primary constructors](#primary-constructors) - [Defining well-defined behavior for collection expression types](#defining-well-defined-behavior-for-collection-expression-types) ## Quote(s) of the Day - "So we're finally going to have the highlander betterness rule?" - "Let them live in fear if they're going to do something like this" ## Discussion ### Primary constructors https://github.com/dotnet/csharplang/issues/2691 https://github.com/dotnet/csharplang/blob/960ffda90c05c212870d509a45651197a6cd1791/proposals/csharp-12.0/primary-constructors.md#lookup-order-for-type-parameters First up today we looked at an issue with lookup order of type parameters, and how our existing `record`s work conflicts with the rules we decided on for non-`record` primary constructors. We have 4 options for addressing this discrepancy: 1. Adjust the rules to match the behavior of the implementation (which is the same between `record` and non-`record` types). 2. Adjust the behavior to match the rules in all cases (a possible breaking change for `record`s). 3. Disallow a primary constructor parameter to use a type parameter's name (a possible breaking change for `record`s). 4. Do nothing, accept the inconsistency between the spec and implementation. We quickly determined that we don't think 2 or 4 are the correct answers, and instead felt divided between 1 and 3. Importantly, the LDM feels that this scenario is a pathological case, and not representative of C# code outside the compiler's test suite, so we don't feel the cost of a breaking change is hard. However, because this case is so pathological, we also are unsure whether it's worth spending the effort that taking a breaking change would require: updating the compiler error detection code, documenting the breaking change, and dealing any fallout from the change, and all this within the next month when C# 12 releases. We ultimately think that the best choice we can make here is to simply update the spec to match the behavior of the compiler, and revisit in the future if we have sufficient motivation from real world scenarios. #### Conclusion Option 1, update the primary constructors spec to match the existing compiler behavior and align with `record`s. ### Defining well-defined behavior for collection expression types https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/issues/7542 For our second and last issue today, we looked at defining what behavior the compiler can assume from a collection type. This is important for allowing the compiler room to change emit of collection expressions for performance, and there's a tension here between what could be reasonably observed by a user program vs what should be able t reordered. One thing we think is iron-clad is that anything the user has explicitly written needs to be evaluated in traditional C# fashion. It is reasonable that `[i++, i++, i++]` would result in a collection of `[0, 1, 2]` with `i == 3` afterwards, and not any other order. However, enumeration of nested collections is not an explicit user action, and we want to able to control the location that occurs in; enumeration should be able to occur before evaluating subsequent elements, or after evaluating subsequent elements, as needed by the compiler to support efficient intermediate storage allocation. We like the spirit of the proposed rules in 7542 but think that they need to explicitly spell out these conditions. Right now, they imply that the compiler can reorder the location that nested enumeration can occur, but don't explicitly state it. Rather than leaving this as undefined and letting the compiler fill in the blanks, we'd rather explicitly carve it out that the compiler is free to optimize assuming that `IEnumerable` instances can be enumerated at any time, so long as the element expressions that the user writes are evaluated in left-to-right order. We also don't feel that we should specify that the compile needs to enumerate spreads in the order they appear; while we currently don't have any plans for enumerating a later spread before an earlier spread, we don't think that we should restrict ourselves from optimizations in the space in the future if we find them. We support spreading `IEnumerable`s, not `IEnumerator`s, and those should be iterable multiple times without producing different results each time. #### Conclusion The compiler is allowed to reorder spread enumerations assuming that `IEnumerable` implementations don't have side effects, and the rules should be updated to explicitly call this out. ================================================ FILE: meetings/2023/LDM-2023-09-27.md ================================================ # C# Language Design Meeting for September 27th, 2023 ## Agenda - [Collection expressions](#collection-expressions) ## Quote of the Day - "Note today's date. Today is a historical day when we finish collection expressions" *collective laughter* ## Discussion ### Collection expressions https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/issues/7542 Today in our wrapping up of the last issues in collection expressions, we took a look at what optimizations the compiler is allowed to make. There's a tension here between allowing the compiler to make user code fast, and how understandable the feature will be. There are a number of features that we want the compiler to be able to take advantage of that might not normally be visible, or not be chosen, at the location of the collection expression. For example, .NET 8 is introducing a new set of `AddRange` extension methods on `List` for adding a `ReadOnlySpan` to a `List` efficiently. We'd like to pick these methods for spreading an array into a collection expression target-typed to a `List`, but because `AddRange` is an extension method, it's never even discovered before the instance `AddRange(IEnumerable)` method is picked. This is not just a problem for collection expressions, of course; it's a problem for regular invocations as well. But do we want to limit collection expressions to behaving exactly as if the user had written the `AddRange` call themselves, or can we make it better? And if we can make it better, where do we limit ourselves? For user-defined collection types, should we search non-imported namespaces for better extension `AddRange` methods? If so, where do we stop? And how do API authors know how to design their collections to take advantage of these rules? One thing we wanted to do as part of determining what APIs can be taken advantage of is to determine what our definition for the Base Class Library, or BCL, actually is. With input from the runtime team, we determined that what we should consider the BCL is anything in the `System.*` namespace. With that definition, we decided to split up our decisions: should we be allowed to assume well-behavedness and call any method at all for BCL types? And, as a separate question, should we be allowed to do that for non-BCL types? For BCL types, we think this is fairly safe. The `System` namespace has been "special" to the .NET ecosystem since the very beginning, and we feel ok with assuming that we can call things like the extension `AddRange` for BCL types in collection expression spreads. We're also ok with calling things like `CopyTo` on BCL types. We're more wary about non-BCL types, particularly around type author discoverability. How do we make sure that type authors know what their collections should do to get optimal performance, for example, or how do we know that a random `AddRange` method would actually do what we expect for specific BCL types like `List`? After much debate, we think we need to be more specific for non-BCL types, provide a specific list of things that we will call, and respect the normal rules that users will see if they write out the code manually. Separately, we would like to address some of the problems that caused `AddRange` to be an extension in the first place. Because arrays and `string`s are both convertible to `ReadOnlySpan` and implement `IEnumerable`, adding that `AddRange` as an instance method would create ambiguities when passing a parameter of that type to `AddRange`. The array problem can be fixed with another more specific overload, but the `string` case cannot, as it would require generic specialization. This is a problem that we keep running into with various language features, and we think it's time to address it in C# 13. #### Conclusion The compiler can rely on all implementation details of BCL types, and potentially call methods that would not normally be preferred by the language in that location. For user types, we need to enumerate the list of methods that the compiler can call, and respect the normal language rules in those locations. This include methods from well-known BCL interfaces implemented by non-BCL types. We will look at potential ways to unify this behavior in C# 13. ================================================ FILE: meetings/2023/LDM-2023-10-02.md ================================================ # C# Language Design Meeting for October 2nd, 2023 ## Agenda - [Collection expressions](#collection-expressions) ## Quote(s) of the Day - "Can I add a 4th bullet? Don't design it now, do it now" "This isn't a Punnett square" - "Ok, that was three deep breaths" "I only did 2" "I have small lungs, what can I say" - _ posts a picture of the IEnumerable hierarchy_ "Thank you, , that was... horrifying. Horrifying and super useful" ## Discussion ### Collection expressions https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/issues/7542 Today, we looked at how the conversion for collection expressions is defined for `IEnumerable` types. This is the rule that allows collection expressions to be fully compatible with types that can be initialized by a collection initializer today; because of this broad compatibility, it has some issues that we think may cause the feature to be brittle in the face of user interaction. Specifically, determining whether the conversion exists requires the compiler to attempt to bind the call to the constructor, and every `Add` or `AddRange` call, before determining that a conversion exists. This can potentially lead to unstable behavior in the editor in the face of user errors, and it's a problem we've seen before. In particular, interpolated string handlers were originally specified this way, but we revised the specification after considering this issue. We are therefore considering updating the spec here to avoid the same problem. The new rules do have a marked disadvantage: they aren't fully compatible with all types that can use a collection initializer types today. If there are extension `Add` methods that convert inputs to the element type of the collection, they would not be considered by this new rule. We do expect that these cases are somewhat rarer though, and that we can address this later if it becomes a problem. These rules also prompted a discussion on support for non-generic `IEnumerable`. The WG was unclear on the exact outcome from a [prior LDM](LDM-2023-08-09.md#target-typing-of-collection-expressions-to-core-interfaces), and whether the non-generic `IEnumerable` and descendent interfaces (such as `ICollection`) need to be supported for target-typing as well. We clarified that we don't think we can make a decision about whether to consider the non-generic `IEnumerable` "deprecated" by ourselves; it would need to be a larger decision among all the .NET teams. We also don't think supporting the non-generic types will cause any new ambiguities that didn't already exists: for example, if an API is overloaded on `ICollection` vs `ICollection` today, it's already ambiguous, because `ICollection` doesn't inherit from `ICollection`. As part of this consideration, we also confirmed that we do want to be able to target the non-generic interfaces with collection expressions. IE, this should work: ```cs ICollection c = ["a", 2, null]; ``` However, we think we may not be able to get this in C# 12, and it may have to wait for C# 13 and be considered in tandem with natural types for collection expressions. Finally, we thought about how the new rules apply to `string`s. There are two core scenarios to think about here: ```cs // Scenario 1 void M1(string s) {} void M1(ReadOnlySpan r) {} M1(['a', 'b', 'c']) // Scenario 2 void M2(string s); void M2(char[] characters); M(['a', 'b', 'c']); ``` Scenario 1 is extremely realistic, and in fact already exists in the BCL. We'd much prefer that it call the `ReadOnlySpan` API, which does not need to allocate; the `string` API needs an instance of a `string`, which could very well allocate if the items were not constant. To accomplish this, we need to update the betterness rule from a [previous LDM](LDM-2023-09-20.md#overload-resolution-fallbacks) to consider `Span` better than both array _and_ `string`, or it will become ambiguous. The second API, though, is not ambiguous by today's rules, as `string` is not constructable and therefore no conversion exists. By the new rules though, `string` is a valid target type for collection expressions, and will simply fail at a later stage. This makes the `M2` API ambiguous. After some more consideration, we think that there's good reasoning for making `string`s constructable with collection expressions. List patterns already work on them, and we think that syntax could be useful for combining multiple string slices together. Support for this, though, is likely not going to make C# 12, and will be considered for future C# versions. #### Conclusions The proposed change to the collection expression rules is adopted as worded. Collection expressions can be converted to `string`s, though the conversion will fail to compile today. We will hope to make it work in the future. We update the overload resolution betterness rules to consider `Span` and `ReadOnlySpan` better than `string`, just as we did for array and interfaces implemented by arrays. We will support non-generic collection interfaces as target types, but possibly not until C# 13 in tandem with natural types for collection expressions. ================================================ FILE: meetings/2023/LDM-2023-10-04.md ================================================ # C# Language Design Meeting for October 4th, 2023 ## Agenda - [Trimming and AOT](#trimming-and-aot) ## Quote of the Day - "PHONE ALERT TIME" ## Discussion ### Trimming and AOT Today, we mostly listened to a presentation from the AOT lead and C# LDM emeritus Andy Gocke, which can be viewed [here](LDM-2023-10-04%20Intro%20to%20Trimming%20and%20AOT.pdf). We had a few small discussions during and after the presentation, which will be summarized here, but we'll let the slides speak for themselves for the majority of the content. On alpha-renaming with attributes (slides 9 and 10), Andy pointed out that our closure renaming strategy today doesn't preserve attributes on type parameters, which makes it hard for AOT annotations to be preserved for lambda scenarios. We agreed that this is something that the compiler can likely address; how lambdas are lowered is not well-specified in the C# language, giving us the freedom to add more information if needed. However, this is a double-sided blade: because it's unspecified, it's not necessarily a good idea for an AOT framework to take advantage of a specific lowering strategy, even if more advantageous. We may want to think about whether we can specify enough to get AOT the information it needs without over-constraining the compiler. The serialization models adopted by newer systems, particularly Rust and Swift, are attractive, and potentially doable in C# when we add extensions that can implement interfaces on unowned types. But there's a few potential problems we'll need to think about: 1. Rust has an orphan rule for trait implementation, preventing users from implementing traits they don't own on types they don't own. They get away with the trait-based deserialization models in part due to the fact that they've been implementing serialization traits like this from the very beginning, so the whole ecosystem has effectively opted itself in. C# doesn't have that weight of momentum, so an orphan rule might kill any attempts by users to mix new serialization patterns in with existing code they don't own. However, we also don't think we need an orphan rule currently, as C# extensions will be actual named types, unlike in Rust. It is still something that needs to be investigated more in depth, however. 2. The momentum argument also has impact on broader adoption: just like with nullable reference types, this would be a gradual adoption of a new serialization pattern across the entire ecosystem. Even if individual users can gracefully bring old, outdated patterns into new serialization patterns, it's going to be a long road before users wouldn't have to introduce lots of adapter interface extensions, and that's not including any frameworks that may simply decide to avoid the new patterns. Regardless of these problems though, we think that the extensions working group needs to keep this use case in mind and prioritize it as a potential use case for the feature. There's also some concern about compiler structures that aren't represented at runtime, and how AOT would be able to view these things. Currently, .NET AOT only works with C#, which is a detriment to the feature. Any further addition of language features that doesn't have real runtime representation makes it harder to work with from a cross-language perspective; for example, one possible implementation of reference could be simply treat it as `object` (or some other base type of the hierarchy) with some additional metadata to say which cases are possible. Would this then constrain how an AOT-compatible serialization framework needs to behave, and lock out other .NET languages besides C#? Similar issues exist for AOT based purely on source generating from C# syntax. These are hard questions that need to be dug more into, and we would like to do so. There are no conclusions here today; serialization is an interesting scenario that we will continue to look at more. ================================================ FILE: meetings/2023/LDM-2023-10-09.md ================================================ # C# Language Design Meeting for October 9th, 2023 ## Agenda - [Triage](#discussion) - [ReadOnlySpan initialization from static data](#readonlyspan-initialization-from-static-data) - [Embedded Language Indicators for raw string literals](#embedded-language-indicators-for-raw-string-literals) - [list-patterns on enumerables](#list-patterns-on-enumerables) - [Make generated \`Program\`\` for top-level statements public by default](#make-generated-program-for-top-level-statements-public-by-default) - [CallerCharacterNumberAttribute](#callercharacternumberattribute) - [Add private and namespace accessibility modifiers for top-level types](#add-private-and-namespace-accessibility-modifiers-for-top-level-types) - [Require await to apply nullable postconditions to task-returning calls](#require-await-to-apply-nullable-postconditions-to-task-returning-calls) - [`is` expression evaluating `const` expression should be considered constant](#is-expression-evaluating-const-expression-should-be-considered-constant) ## Quote of the Day - "I think the disagreement was primarily around Javascript, so we'll leave that out" ## Discussion Today was a triage day. We went through championed issues without a milestone to assign them an appropriate milestone for future work. The query we use for this is https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22+no%3Amilestone. ### ReadOnlySpan initialization from static data https://github.com/dotnet/csharplang/issues/5295 Much of the desire for specific syntax here has been addressed by a combination of things in C# 12: * Collection expressions provide a syntax that doesn't contain `new` and necessarily imply that it is actually creating a new collection. * Collection expressions for this scenario have a set of promises enshrined in the spec for when they won't allocate, and we have a warning for when that's violated. * There are additional scenarios that the compiler does not allocate today, and we have a hidden diagnostic that users that care about this scenario can turn on to ensure that they aren't violating these rules, either now or in the future. This set of rules is implementation-defined, which is why the diagnostic must be opted into by users, but it does cover the scenario. We could in the future think about potential `const` expansions to ensure that nothing is violated, such as `const [1, 2, 3]`, but that's a much bigger proposal and potentially gets into the larger weeds of broad constexpr support in C#, and can be revisited later. #### Conclusion Rejected, request subsumbed by C# 12 features. ### Embedded Language Indicators for raw string literals https://github.com/dotnet/csharplang/issues/6247 This is, in terms of compiler work, an extremely small feature. However, the broader concern is tooling support. If we enshrine this in the language itself, we may then need a tooling support bar we currently don't have. Colorization of languages today is driven by comments or `StringSyntaxAttribute`, and there's a bit of a chicken-and-egg problem here. If the language had more explicit support for the feature, this may drive more investment into tooling; but because we don't have super broad adoption of the existing feature, it can be hard to justify the investment. We think the feature would be a good one to have, but that we're not going to prioritize it at this time. #### Conclusion Backlog ### list-patterns on enumerables https://github.com/dotnet/csharplang/issues/6574 This is follow-up work from C# 11 that we did not have time in C# 12 to invest in. We intend to continue the work here now; collection expressions supporting more than just indexable and countable types show where our list pattern support falls short. #### Conclusion Working set ### Make generated `Program`` for top-level statements public by default https://github.com/dotnet/csharplang/issues/6769 The primary argument for this feature is improving testability, but we think this feature alone wouldn't be enough to fix testing. For example, users wanting to test their entrypoint still can't do so, because it's unnameable. We think a more holistic look at the testing space is required before we move forward with this one. #### Conclusion Needs more work ### CallerCharacterNumberAttribute https://github.com/dotnet/csharplang/issues/3992 This is an interceptors-adjacent feature. As we continue to develop interceptors or possible replacements for C# 13, we will want to consider this alongside it. It shouldn't be considered alone. #### Conclusion Grouped with https://github.com/dotnet/csharplang/issues/7009. ### Add private and namespace accessibility modifiers for top-level types https://github.com/dotnet/csharplang/issues/6794 This is in many ways a follow-on to the improvements we made with `file`, but this time targeted more at standard users, and less at source-generator authors. We think that there's room to improve the story around encapsulating functionality, particularly for larger codebases, and want to flesh this proposal out and see where it leads us. #### Conclusion Working set ### Require await to apply nullable postconditions to task-returning calls https://github.com/dotnet/csharplang/issues/6888 This still needs to be updated to address feedback from the last time that it was [brought up](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-01-18.md#nullable-post-conditions-and-asyncawait). #### Conclusion Needs more work ### `is` expression evaluating `const` expression should be considered constant https://github.com/dotnet/csharplang/issues/6926 This is a consistency issue: you can use `==` in a constant expression, but not `is`. We think this can be addressed, but needs a complete specification. It should probably also look at `switch` expressions at the same time. #### Conclusion Any time, needs an approved specification. ================================================ FILE: meetings/2023/LDM-2023-10-11-specification-update.md ================================================ # Status of C# 7 standard In our last meeting, the ECMA committee voted to submit the C# 7.3 standard to ECMA for final approval. This is the first version we've created in the open, on GitHub.Now that we've completed the work on the standard, the `draft-v7` branch has been renamed `standard-v7`. > GitHub redirects any links to the `draft-v7` branch to the updated name. There's no need to updated any existing proposals due to that change. The default branch has already changed to the `draft-v8` branch. Merging v7 into the v8 branch is in progress. ## Nomenclature changes, and how to handle them going forward The committee changed some of the nomenclature used in the C# 7 specs. The original terms were either confusing, or some words in a multi-word term were already used in the standard with a different meaning. Notably, *safe-to-escape-scope* and *ref-safe-to-escape-scope* became *safe-context* and *ref-safe-context* respectively. The possible values are now *declaration-block*, *function-member*, and *caller-context*. (The speclet didn't use standard terms). Question: Should we update the speclets from C# 7.2 and later to use the standard terms? Or wait until the standard incorporates all these versions? Or add a note in each relevant speclet of the changed terms? The first provides consistency now. The second preserves the historical nature of the speclets. The third is a compromise position. ## A look at C# 8 efforts and beyond Rex has been creating draft PRs for C# 8 (and later) features. None of these are completed. We will start assigning work to committee members at our next meeting. We're making some [process changes](https://github.com/dotnet/csharpstandard/issues/960) that we believe will help us move faster. You can see the mapping from any speclet to the corresponding standard text in the [admin folder](https://github.com/dotnet/csharpstandard/blob/draft-v8/admin/v8-feature-tracker.md) on the csharpstandard repo. ## Looking back C# 7.3 was likely the longest cycle the committee has or will have. There were a number of reasons: 1. *New process*: This was the first version using Markdown and GitHub. We were finding issues with the conversion from Word, in addition to fixing existing spec bugs and adding new features. 1. *No Microsoft standard*: This was the first release where the Microsoft version of the C# spec wasn't updated. That's a positive because we're not trying to reconcile different descriptions, and we don't introduce regressions when fixes didn't make both version of the spec. On the other hand, it was our first experience working with feature specs and writing the standard language from the feature specs. 1. *Long release and point releases*: The committee decided to work up through 7.3 instead of 7.0 for two reasons: 1. C# 7.3 is the last version compatible with .NET Framework. 1. Small fixes introduced in point releases were easier. 1. *Spelunking LDM notes and roslyn repo*: The C# 7 timeframe also including creating the `csharplang` repository, and building the culture to update the specifications. Some notes were in `roslyn`, in issues or docs. Some in LDM notes, in either repo, and some in the speclets. 1. *Smaller committee*: The committee has room for more members. (Microsoft is under-represented at this point). The newer feature specs have more language directly tied to the specification language. Other ongoing improvements have been noted above. ## Other questions for LDM 1. *Branch cleanup in csharplang*: Rex and I had used a [branch in csharplang](https://github.com/dotnet/csharplang/tree/standard-proposals) for some initial C# 7 integration with the Microsoft spec. That was abandoned some time ago. Is anyone using it? If not, I'll delete branch. 2. *Do we want to direct customer issues in speclets directly to csharplang?*: I sent mail about this a week ago. We have a new feedback mechanism in docs where I can configure a link to use a YML template for new issues in the C# lang repo. Currently, they go to the docs repo, and I triage them before moving or just fixing them. ================================================ FILE: meetings/2023/LDM-2023-10-11.md ================================================ # C# Language Design Meeting for October 11th, 2023 ## Agenda - [C# spec update](#c-spec-update) - [Collection expressions](#collection-expressions) ## Quote(s) of the Day - "I think that's a great segue, because \ voice has always reminded me of cardamom syrup." \: "_what_?" - "The chat has descended into madness" "We're solving \ coffee problem" "\ and \ are about to open a Starbucks franchise in the C# team room" ## Discussion ### C# spec update [C# 7 specification update](LDM-2023-10-11-specification-update.md) The specification effort for C# 7.3 has completed, and the formal spec has been submitted to ECMA for ratification. Following this effort, the committee will be moving onto C# 8, but there were a few questions to clarify with the LDM before getting started on that: 1. The committee renamed `safe-to-escape` and `ref-safe-to-escape` in the specification for clarity. Should we update the existing documents with this info, and if so, should we update the C# 7 speclets as well as the C# 8+ speclets? * After some deliberation, we think the right answer is to update the C# 8+ speclets with this info. The C# 7 speclets are now effectively obsolete; the features have been integrated into the real specification, and we don't link to them from the documentation anymore. We'll put a note in the root of the C# 7 speclet folder on csharplang that the speclets there are outdated, and that should be all we need to do. For the C# 8+ speclets that refer to the outdated names, these are still active documents, so we'll update them with the new names. We'll also include a note that the term was renamed for past readers who look at the document again. We don't plan on updating issues or notes with these new terms. 2. There are a few outdated branches on csharplang from before the ECMA committee moved to the csharpstandard repo. These need to be cleaned up. 3. Finally, we currently have docs issues on the C# speclets opened on the dotnet/docs repo. This usually means that we go through a multi-level ping before getting to the right person to answer the question. The content itself is hosted from the csharplang repo, so should we move the issues to be filed on csharplang repo as well? * We think we should do this. The volume of issues is pretty low, and it will remove a level of indirection for getting questions answered or docs fixed. ### Collection expressions https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/roslyn/issues/70318 Finally today, we considered whether collection expressions should prefer `ReadOnlySpan` over `Span` for APIs that are overloaded on these two types. This isn't a super common case, but it can happen. Some examples include extension methods (these may be overloaded because extensions on `ReadOnlySpan` do not appear on `Span` receivers), or `ImmutableArray.Create`. Our existing rules prefer `Span` here, because it can be converted to `ReadOnlySpan`, and is thus the "more specific" type. And, at least conceptually, it's possible that `Span` is the better type in some scenarios: perhaps the API that's being called can be more efficient if it can mutate the input buffer. However, looking at the existing ecosystem, this isn't the case; the cases we see would indeed prefer an allocation-less `ReadOnlySpan` if at all possible, rather than `Span`. We also think that we have more leeway with collection expressions to choose the better type if possible; unlike, say, `new[] { 1, 2, 3 }`, we're not explicitly allocating new space with `[1, 2, 3]`. We also considered what exact wording to use here; we ended up at very similar wording to how we specified the `Span` vs array type betterness, requiring an implicit conversion between the element types to avoid the same problems we discussed [here](LDM-2023-09-20.md#overload-resolution-fallbacks). #### Conclusion `ReadOnlySpan` will be preferred over `Span` in overload resolution for a collection expression parameter when `T1` is implicitly convertible to `T2`. ================================================ FILE: meetings/2023/LDM-2023-10-16.md ================================================ # C# Language Design Meeting for October 16th, 2023 ## Agenda - [Triage](#discussion) - [Breaking change warnings](#breaking-change-warnings) - [Determine natural type of method group by looking scope-by-scope](#determine-natural-type-of-method-group-by-looking-scope-by-scope) - [u8 string interpolation](#u8-string-interpolation) - [Lock statement pattern](#lock-statement-pattern) - [String/Character escape sequence \\e as a short-hand for \\u001b ()](#stringcharacter-escape-sequence-e-as-a-short-hand-for-u001b-) - [New operator %% for canonical Modulus operations](#new-operator--for-canonical-modulus-operations) ## Quote of the Day - "If you have a ref struct as a synchronization primitive that's pretty useless" "Hey, we were discussing readonly setters today, just because it's useless doesn't mean we shouldn't do it" ## Discussion Today we continued with triage of championed issues without a milestone. ### Breaking change warnings https://github.com/dotnet/csharplang/issues/7189 We continue to work on this, but it was filed after our last triage session so it didn't have a milestone. **Conclusion**: Added to the working set. ### Determine natural type of method group by looking scope-by-scope https://github.com/dotnet/csharplang/issues/7364 We've determined this is a duplicate of https://github.com/dotnet/csharplang/issues/7429. Closing out. **Conclusion**: Closed as a duplicate. ### u8 string interpolation https://github.com/dotnet/csharplang/issues/7072 .NET 8 has mostly addressed this request with some JIT work to make `TryWriteUtf8` extremely efficient. The remaining work here would be allowing usage outside of that API, and we're unsure whether the existing are addressed with this new work. We'll wait to see if the new work has sufficiently addressed requests here before proceeding with more design work. **Conclusion**: To the backlog. ### Lock statement pattern https://github.com/dotnet/csharplang/issues/7104 Some initial discussion revealed that there are some split opinions in the LDM on whether we think `lock` should natively support this new type, or if a new language feature should be used (or if it should be done via `using`). However, what's clear is that we do need to have more discussions on this in the .NET 9 timeframe, or our ability to address this request without potential breaking changes will slip us by. **Conclusion**: Into the working set for .NET 9 discussions. ### String/Character escape sequence \e as a short-hand for \u001b () https://github.com/dotnet/csharplang/issues/7400 This is one of the smallest possible language features that could possibly exist, and we're generally fine with the idea, especially as there is prior art in other languages for `\e` as an escape (regex, for example, uses `\e` for \u001b as well). We think the existing issue is sufficiently well-specified to proceed to put this into the needs implementation bucket of any time, open for community contribution. **Conclusion**: Into Any Time, needing an implementation. ### New operator %% for canonical Modulus operations https://github.com/dotnet/csharplang/issues/7599 We think this request is better served by library functions. There's a large number of potential follow-on requests here: requests for floored division, requests for other types of remainder operations, and we don't think that adding increasing numbers of `%` or `/` characters to other operators is a sustainable path forward for these requests, either for human understanding of the operators or for the language to continue designing. We think that extension methods are probably the best path forward for getting an infix-style calling pattern for these operations. **Conclusion**: Likely never. ================================================ FILE: meetings/2023/LDM-2023-11-15.md ================================================ # C# Language Design Meeting for November 15th, 2023 ## Agenda - [`params` improvements](#params-improvements) - [Nullability analysis of collection expressions](#nullability-analysis-of-collection-expressions) - [Evaluation of implicit indexers in object initializers](#evaluation-of-implicit-indexers-in-object-initializers) ## Quote of the Day - "That is a divide by object error" ## Discussion ### `params` improvements https://github.com/dotnet/csharplang/issues/7700 https://github.com/dotnet/csharplang/pull/7661 We started today by reviewing the latest proposal for improving `params`. This has morphed several times over the past few years; previous issues included https://github.com/dotnet/csharplang/issues/179 and https://github.com/dotnet/csharplang/issues/1757; this proposal encompasses both of these, and tries to unify with the C# 12 feature collection expressions. The general driving principle of this proposal, and how the LDM is thinking of the feature, is that if the user can make a collection of the type, they should be able to mark it `params`. This may require some spec work between the two features to extract out common elements without requiring invasive changing of everywhere that `params` exists today, but it's a good driving principle. Overall, we are very interested in the feature. It's a rare example of something that both adds expressivity to C# while actually _simplifying_ the language: with this change and a bit of spec work, we have a much simpler story around collections in the language. You can deconstruct them via pattern matching, index into them, construct them via collection expressions, and provide implicit construction via `params`. We consider whether we could simply not do this feature and let collection expressions fill the gap, but there's one clear problem we need to solve: the BCL would like to add `params (ReadOnly)Span` overloads to many APIs to make them more efficient. This isn't a gap that can be solved by collection expressions, and we think that, if we're going to make a change to `params`, we should do the entire feature to make reasoning about collections in the language easier, rather than just changing the special cases users need to think about. We also considered some ref-struct specific design questions. In terms of allocations, we think the right approach is to again align with collection expressions; if a collection expression wrapping the arguments passed to a `params Span` allocates, then the expanded invocation form should behave the same. We left a lot of leeway in the language for optimizations here, so we think continuing to keep to the same guarantees is the obvious thing to do. It also helps prevent surprise behavioral differences if a user passes a collection expression to a `params` parameter vs calling in expanded form. The other question we considered is whether to have the `params` parameter be `scoped` by default. Some members were concerned that `params` is not an obvious enough indicator of `scoped`ness, but we have somewhat already crossed this bridge with `out` parameters. We also don't have any current scenarios that need an unscoped `params` parameter, only ones that need `scoped`. Given that, we think we should start with `scoped` by default, and let feedback inform whether we've made the right decision. Finally, we noticed that the overload resolution tiebreaking rules may be missing a few clauses that collection expressions have around non-ref struct comparisons, so we'll make sure that's in the spec if needed. #### Conclusion Proposal is accepted. We will follow the same allocation guarantess as collection expressions, and `ref struct` `params` parameters will be `scoped` by default. ### Nullability analysis of collection expressions https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/issues/7626 This issue came up late in the design cycle and presented somewhat of a challenge to the compiler. In particular, some types may violate what we'd otherwise consider an idiomatic implementation, having an `Add` method that allows `T?` while the collection iteration type is `T`. While we don't think that's a totally unreasonable pattern, it does generally violate our pre-existing rules for collection expressions. For example, if a collection iteration type is `T1`, we don't allow using an unrelated `T2` as an expression, even if the collection type technically has an `Add(T2)` method. This differs from collection initializers, and we think that we should carry that difference through here. This also means that any collection expression that are used with older collection types that only implement `IEnumerable` will not get nullability warnings; this is because `IEnumerator.Current` is unannotated. Given that we already skip nullability warnings there for `foreach`, we're ok with skipping them on construction as well. #### Conclusion We will do nullability analysis based on the iteration type of the collection expression. ### Evaluation of implicit indexers in object initializers https://github.com/dotnet/csharplang/issues/7684 This is more of a bugfix clarification to the range feature in C# 8. For scenarios where an indexer is used in a nested member initializer, we need to decide what counts as the "indexer" as defined in the spec: > When an initializer target refers to an indexer, the arguments to the indexer shall always be evaluated exactly once. Thus, even if the arguments end up never > getting used (e.g., because of an empty nested initializer), they are evaluated for their side effects. The question becomes: is the "indexer" here the virtual `Index`-based indexer, or is it the real `int`-based indexer that is called under the hood? This ends up having an effect on whether we call `Length` on the collection multiple times or not. While pathological, this _could_ potentially be observable if the nested member initializer appends to the containing collection in some fashion. If we treat the `Index`-based indexer as the indexer referred to in the spec here, then appends will be observed. If we instead say the `int`-based indexer is the indexer referred to by the spec, appends would not be observed, as `Length` would only be evaluated once. This has an even further wrinkle for empty nested initializers: do such initializers actually evaluate `Length`? If the `int`-based indexer is the "indexer" in the above, the wording would suggest yes, as it is an "argument" to the indexer. However, we also think that there's a line of reasoning where "argument" is only referring to the user-written code: `Length` is not user written code, so by that logic, it would be safe to elide in the empty case. That leaves us with a few options: 1. Cache the argument to the virtual `Index` indexer. Revaluate `Length` on every nested member initializer. 2. Cache the argument to the real indexer. Evaluate `Length` even when the nested object initializer is empty. 3. Cache the argument to the real indexer. Do not evaluate `Length` when the nested object initializer is empty. 4. Adopt more aggressive caching across the board. While we think that, if we were redoing the feature today, we'd pick option 4, we don't think that we can make such a change at this point in the language evolution. After some discussion, we settled on option 3, falling back to 2 if it ends up being too complicated to implement. #### Conclusion Option 3, cache the argument to the real indexer, do not evaluate `Length` when the nested object initializer is empty, falling back to option 2 if it proves an implementation challenge. ================================================ FILE: meetings/2023/LDM-2023-11-27.md ================================================ # C# Language Design Meeting for November 27th, 2023 ## Agenda - [Pattern order optimizations](#pattern-order-optimizations) - [Subarray slicing breaking change](#subarray-slicing-breaking-change) - [Making patterns constant expressions](#making-patterns-constant-expressions) ## Quote of the Day - "Dark mode is hard to see, can you switch to light mode?" *switches to light mode* "Aaaargh!" ## Discussion ### Pattern order optimizations https://github.com/dotnet/roslyn/issues/60091 Currently, Roslyn generates less-than-optimal code for list patterns, doing more expensive slicing before attempting to see if easily-accessible elements of a collection match. This is because, today, we emit the collection expression tests LTR (at least when considering a single pattern). The language has always reserved the space to reorder pattern tests as it sees fit, but this is the first time that we're actually looking at a specific case within a single pattern where we think we should reorder for performance reasons. There are certain cases where the guarantees around reordering can be painful: for example, `immutableArray is { IsDefault: false, Length: > 1 }` is not guaranteed to check `IsDefault` first, and the `Length` check could null ref if it was checked on a default immutable array. However, we don't think that this is one of those cases. There could be some collection types where slicing is actually cheaper than indexing (a linked list, for example, needs to walk the whole thing anyway), but we don't think that this is the majority case. At the same time, we do think that we should take another look at our ordering rules sometime during the C# 13 timeframe and see if we can make stronger guarantees. We're not sure about this; in particular, the way that we deduplicate checks across patterns in a switch statement or expression makes it extremely hard to guarantee anything if properties are checked in different orders across those patterns. But we think it's worth a bit of effort to see if we can make it better. #### Conclusion We are ok with reordering collection pattern testing. We will see if we can be more specific about the types of reordering in general. ### Subarray slicing breaking change https://github.com/dotnet/roslyn/issues/69053 There's an interesting issue with array variance that was introduced in .NET 7 that breaks some expectations of pattern matching; when getting the subarray of a variantly-converted array, the runtime will now make the subarray the variantly-converted type, not the original type of the array. This breaks code that was type testing the subarray against the original type. Morever, we think the code that was broken was perfectly reasonable. We'll talk with the runtime team to understand what the costs for reverting this change would be, and how we might be able to work around it in the compiler if we cannot revert it. #### Conclusion We will follow up after talking with the runtime team around the behavior here. ### Making patterns constant expressions https://github.com/dotnet/csharplang/issues/6926 https://github.com/dotnet/csharplang/pull/7589 Finally today, LDM looked at a community-proposed specification for making `is` expressions constant when possible. On initial look, we didn't find much objectionable in the specification, just a few comments on how it could be cleaned up to be more general. We also were interested in expanding the proposal to switch expressions. However, when we started drilling down on the existing subsumption behavior, we started to be a bit more concerned about behavioral complexity. Specifically, we looked at some examples like this: ```cs public const int A = 4; public bool B1 = A is 4; // warning CS8520: The given expression always matches the provided constant. public bool B2 = A switch { 4 => true }; // warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '0' is not covered. public bool B3 = true is true and false; // error CS8518: An expression of type 'bool' can never match the provided pattern ``` Today, these warnings and errors are all sensible (except perhaps `B2`), as they would affect the code that the compiler would generate. However, if we started making patterns constant expressions everywhere, these warnings and errors might go away in more places than we expect, as the language would be unable to tell when we should warn about subsumption and when we shouldn't. There could be a version of this where we suppress these warnings when the expression occurs within a location that must be a constant expression; ie, when in a `const` variable initializer, parameter default value, or other such location, but we're a little concerned about the potential complexity of such a change compared to the potential benefit of using patterns in these locations. After all, there's no new behavior with these patterns, it would just be a more concise way of expressing existing constant expressions. Given that, we think we want to see an updated version of the specification with these rules to understand how big of a change it would be, and decide whether we're comfortable with it at that point. #### Conclusion We need to see an updated specification with rules for dealing with subsumption before making a final decision on this feature. ================================================ FILE: meetings/2023/LDM-2023-12-04.md ================================================ # C# Language Design Meeting for December 4th, 2023 ## Agenda - [Lock statement pattern](#lock-statement-pattern) - [`BinaryCompatOnlyAttribute`](#binarycompatonlyattribute) ## Quote(s) of the Day - "New syntax?" "`lock(ness) // monster`" - "Protect against Murphy, not Machiavelli." ## Discussion ### Lock statement pattern https://github.com/dotnet/csharplang/issues/7104 We started today by looking at an update for locking in .NET. .NET 9 is looking at adding a new locking primitive, `System.Threading.Lock`, and we need to decide what, if anything, the language needs to do to react to this change. There are three main points of contention here: 1. Should we do anything at all? 2. If we do, do we try to protect against accidentally observing the wrong behavior? 3. Do we support a general pattern here, or just one specific type? To the first question, there is some question as to whether we're ok with adding a new behavior for the `lock` keyword. For users of C#, does `lock` specifically mean `Monitor.Enter()`, as it has for the entire lifetime of C#, or does it have a broader meaning? There are certainly other types of locks in the BCL that do not do anything special with `lock` today; for example, `System.Threading.SpinLock` will not actually do a spin lock if `lock (spinLockInstance)` is done, but will just call `Monitor.Enter()`. This may confuse some users, but it's worked this way since the type was introduced (.NET 4.0). Is `System.Threading.Lock` special enough that it needs separate handling? After some discussion, we think the answer is not only yes, we think it's unfortunate that `SpinLock` doesn't do similar. Instead of "`lock` calls a specific API", we think the general user intuition is "`lock` enters a mutual exclusion zone and helps keep me safe from races", which is a much broader intuition. We also think it would be generally unfortunate if the advice we give to users is "Don't use the built-in `Lock` type with the `lock` keyword." Next, we looked at the immediate concerns brought up by 1: what about times when the lock is observed as an `object`, rather than as a `Lock`? The compiler cannot handle this case in codegen, so the options for dealing with it are having the runtime handle it in `Monitor.Enter()` somehow, or performing some static analysis to try and warn/error about when `Lock` is upcast to `object`. Having the runtime magically handle it is attractive from a language design standpoint, but unfortunately not realistic from an implementation standpoint, particularly not unless we want to penalize all the existing locking code in .NET. Therefore, we turned to looking at static analysis. One thing that comes to the top of mind here is that, while there are lots of fun corner cases (unconstrained generics, for example), most of these corners aren't likely anything that a user will actually do with a `Lock`. The use pattern for these types of objects is to simply store them as a class field and `lock(this.lock)` where necessary. Locks usually don't get passed around through generics or other such areas, as that generally violates encapsulation. So the number of bugs that errors for upcasting will prevent are likely small. That being said, we don't think we should just brush this potential risk off as being not realistic either. Instead, we think that the compiler should perform some amount of static analysis, and warn where possible; this will catch most potential issues, but leave an escape hatch for when users really want to do something more clever. Finally, we thought about generalizations. As mentioned earlier, we think it's unfortunate types like `SpinLock` aren't able to participate in `lock` as might be expected. That being said, we don't particularly like the pattern-based approach this proposal is taking. This is something that could be an interface with a type parameter, except for the fact that the `Scope` type is a `ref struct` and cannot go in an interface. Given that we are investigating allowing `ref struct`s into generics, and that the runtime doesn't plan on changing anything but `System.Threading.Lock` for C# 13/.NET 9, we think we should just special case the new type for now, and look at a broader pattern later when we have more use cases. #### Conclusion We generally accept the changes for special casing how `System.Threading.Lock` interacts with the `lock` keyword, but we will not adopt the full pattern at this time. We will look at static analysis warnings to help make sure that accidental missuse of the type does not occur. ### `BinaryCompatOnlyAttribute` https://github.com/dotnet/csharplang/issues/7706 https://github.com/dotnet/csharplang/pull/7707 Finally today, we took a (very) brief look at this proposal. There was strong support from LDM for a general feature, and the BCL also would make heavy usage of this if it was added. There are still plenty of open questions to be answered (and indeed, we need to answer if this is quite the right feature, or if the feature we really want is a knob to control overload resolution at a finer grain), but we think we should take those questions on in the near term. #### Conclusion Feature moves into the working set. ================================================ FILE: meetings/2023/LDM-2023-12-11.md ================================================ # C# Language Design Meeting for December 11th, 2023 ## Agenda - [Extensions](#extensions) ## Quote(s) of the Day - "Welcome to the last regular LDM of the year." "You missed the chance to do last explicit meeting." *Sigh* "No, **you** could have done that" "I wanted you to pick it up implicitly." "If you keep making bad jokes we may have to extend the meeting." ## Discussion ### Extensions https://github.com/dotnet/csharplang/issues/5497 https://github.com/dotnet/csharplang/issues/7771 Now that we're entering the next cycle of C# design, we're bringing back issues that have been on the backburner while we focused on C# 12. First on the docket is extensions; we started LDM with a quick recap on the general proposal, then dove straight into looking at the progress on lowering that has been made in the past few months. This design has evolved a great deal since we initially looked at it, and has undergone multiple iterations as we discuss with runtime architects on what is feasible and what is not. Rather than restating the lowering document, these notes will just point out some comments that we had while going over it. * We're unsure about the Punnett Square of extension inheritance, in particular whether implicit->implicit inheritance is actually necessary. The only question is whether we'll need that for multiple implicit interface implementation; if there are multiple implicit extensions that implement different interfaces on a type, and then that type is used in a locaton that constrains to both of those interfaces, will we need to synthesize a new implicit extension that inherits from each original extension to combine them? Will the user need to manually introduce that extension? * We continue to have no good ideas on how to solve nested generics in the general case that can also handle extensions that add interfaces. The representation we've chosen currently is representation-preserving at the top level, but `List` cannot be easily converted to `List` without an allocation somewhere. * We could consider erasure here, but that will only postpone the problem, as extensions can add interfaces (eventually). * Interface member lookup through extensions will be tricky. If the runtime definition of an interface adds a member, and the underlying type has a method that would normally be automatically hooked up to that member at runtime, what will do that for an extension implementation and its underlying type? * May need some runtime help for this one. Ultimately, there are no conclusions today. We'll keep iterating on these questions and bring lowering back to LDM for continued discussion in future sessions. ================================================ FILE: meetings/2023/README.md ================================================ # C# Language Design Notes for 2023 ## Mon Dec 11, 2023 [C# Language Design Meeting for December 11th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-12-11.md) - Extensions ## Mon Dec 4, 2023 [C# Language Design Meeting for December 4th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-12-04.md) - Lock statement pattern - `BinaryCompatOnlyAttribute` ## Mon Nov 27, 2023 [C# Language Design Meeting for November 27th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-27.md) - Pattern order optimizations - Subarray slicing breaking change - Making patterns constant expressions ## Wed Nov 15, 2023 [C# Language Design Meeting for November 15th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md) - `params` improvements - Nullability analysis of collection expressions - Evaluation of implicit indexers in object initializers ## Mon Oct 16, 2023 [C# Language Design Meeting for October 16th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-16.md) - Triage - Breaking change warnings - Determine natural type of method group by looking scope-by-scope - u8 string interpolation - Lock statement pattern - String/Character escape sequence \\e as a short-hand for \\u001b - New operator %% for canonical Modulus operations ## Wed Oct 11, 2023 [C# Language Design Meeting for October 11th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-11.md) - C# spec update - Collection expressions ## Mon Oct 9, 2023 [C# Language Design Meeting for October 9th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-09.md) - Triage - ReadOnlySpan initialization from static data - Embedded Language Indicators for raw string literals - list-patterns on enumerables - Make generated \`Program\`\` for top-level statements public by default - CallerCharacterNumberAttribute - Add private and namespace accessibility modifiers for top-level types - Require await to apply nullable postconditions to task-returning calls - `is` expression evaluating `const` expression should be considered constant ## Wed Oct 4, 2023 [C# Language Design Meeting for October 4th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-04.md) - Trimming and AOT ## Mon Oct 2, 2023 [C# Language Design Meeting for October 2nd, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md) - Collection expressions ## Wed Sept 27, 2023 [C# Language Design Meeting for September 27th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-27.md) - Collection expressions ## Mon Sept 25, 2023 [C# Language Design Meeting for September 25th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-25.md) - Primary constructors - Defining well-defined behavior for collection expression types ## Wed Sept 20, 2023 [C# Language Design Meeting for September 20th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-20.md) - Collection expressions - Type inference from spreads - Overload resolution fallbacks ## Mon Sept 18, 2023 [C# Language Design Meeting for September 18th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-18.md) - Collection expression questions - Optimizing non-pattern collection construction - Avoiding intermediate buffers for known-length cases ## Wed Aug 16 2023 [C# Language Design Meeting for August 16th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-08-16.md) - Ref-safety scope for collection expressions - Experimental attribute ## Mon Aug 14, 2023 [C# Language Design Meeting for August 14th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-08-14.md) - Betterness for collection expressions and span types - Type inference from collection expression elements - Collection expression conversions ## Wed Aug 9, 2023 [C# Language Design Meeting for August 9th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-08-09.md) - Lambdas with explicit return types - Target typing of collection expressions to core interfaces - Loosening requirements for collection builder methods ## Mon Aug 7, 2023 [C# Language Design Meeting for August 7th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-08-07.md) - Improvements to method group natural types ## Mon Jul 31, 2023 [C# Language Design Meeting for July 31st, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-07-31.md) - Primary constructor parameters and `readonly` ## Wed Jul 26, 2023 [C# Language Design Meeting for July 26th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-07-26.md) - Primary constructor parameters and `readonly` ## Mon Jul 24, 2023 [C# Language Design Meeting for July 24th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-07-24.md) - Method group natural types with extension members - Interceptors ## Mon Jul 17, 2023 [C# Language Design Meeting for July 17th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-07-17.md) - Compiler Check-in - `readonly` parameters ## Wed Jul 12, 2023 [C# Language Design Meeting for July 12th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-07-12.md) - Collection Literals - `Create` methods - Extension methods - Interceptors ## Mon Jun 19, 2023 [C# Language Design Meeting for June 19th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-06-19.md) - Prefer spans over interfaces in overload resolution - Collection literals ## Mon Jun 5, 2023 [C# Language Design Meeting for June 5th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-06-05.md) - Collection literals ## Wed May 31, 2023 [C# Language Design Meeting for May 31st, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-31.md) - Collection literals ## Wed May 17, 2023 [C# Language Design Meeting for May 17th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-17.md) - Inline arrays ## Mon May 15, 2023 [C# Language Design Meeting for May 15th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-15.md) - Breaking Change Warnings - Primary Constructors ## Mon May 8, 2023 [C# Language Design Meeting for May 8th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-08.md) - Primary Constructors ## Wed May 3, 2023 [C# Language Design Meeting for May 3rd, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md) - Inline Arrays - Primary constructors - Attributes on captured parameters - Warning for shadowing base members - Collection literal natural type ## Mon May 1, 2023 [C# Language Design Meeting for May 1st, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-01.md) - Fixed Size Buffers - `lock` statement improvements ## Wed Apr 26, 2023 [C# Language Design Meeting for April 26th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-26.md) - Collection literals ## Mon Apr 10, 2023 [C# Language Design Meeting for April 10th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-10.md) * Fixed Size Buffers ## Mon Apr 3, 2023 [C# Language Design Meeting for April 3rd, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-03.md) - Collection Literals - Fixed-size buffers ## Wed Mar 15, 2023 (No notes) - Discriminated Unions - Interceptors ## Mon Mar 13, 2023 (Shorter meeting) [C# Language Design Meeting for March 13th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-13.md) - Unsafe in aliases hole - Attributes on primary ctors ## Wed Mar 8, 2023 [C# Language Design Meeting for March 8th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-08.md) - Discriminated Unions - Limited Breaking Changes in C# ## Wed Mar 1, 2023 (Shorter meeting) [C# Language Design Meeting for March 1st, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-01.md) - Discriminated Unions Summary ## Mon Feb 27, 2023 [C# Language Design Meeting for February 27th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-27.md) - Interceptors ## Wed Feb 22, 2023 [C# Language Design Meeting for February 22nd, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-22.md) - Primary Constructors - Extensions ## Wed Feb 15, 2023 [C# Language Design Meeting for February 15th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md) - Open questions in primary constructors - Capturing parameters in lambdas - Assigning to `this` in a `struct` ## Wed Feb 1, 2023 [C# Language Design Meeting for February 1st, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-01.md) - Position of `unsafe` in aliases - Roles and extensions ## Wed Jan 18, 2023 [C# Language Design Meeting for January 18th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-01-18.md) - Nullable post-conditions and `async`/`await` - Semi-colon bodies for type declarations ## Wed Jan 11, 2023 [C# Language Design Meeting for January 11th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-01-11.md) - `using` aliases for any types - Pointer types in aliases - Reference nullability in aliases - Value nullability in aliases ## Mon Jan 9, 2023 [C# Language Design Meeting for January 9th, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-01-09.md) - Working group re-evaluation ================================================ FILE: meetings/2024/LDM-2024-01-08.md ================================================ # C# Language Design Meeting for January 8th, 2024 ## Agenda - [Collection expressions](#collection-expressions) - [Iteration type of `CollectionBuilderAttribute` collections](#iteration-type-of-collectionbuilderattribute-collections) - [Iteration type in conversions](#iteration-type-in-conversions) ## Quote of the Day - "Can we do another one in 15 minutes?" "I think it'll take another whole meeting" ## Discussion ### Collection expressions #### Iteration type of `CollectionBuilderAttribute` collections https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/blob/1f55d3c05d549edc817589502bfee90db887d56e/proposals/csharp-12.0/collection-expressions.md#specification-of-a-constructible-collection-type-utilizing-a-create-method-is-sensitive-to-the-context-at-which-conversion-is-classified First up today, we looked at a change to how collection expressions determine the iteration type of a collection expression, motivated by our work on `params` improvements. Our specification for `CollectionBuilder` types does not require that they define their own iteration types, but can instead pick them up through extension methods; this means that it is possible that a `params` parameter is only valid as `params` in some contexts, not all. This is potentially undesirable for users, and it means that it is very hard to give correct errors for `params` parameters. The current behavior was intentional, as it is mirror to `foreach`, but we are sympathetic to the idea that, if a type can be created by a collection expression, it should also be generally foreachable. Extension `GetEnumerator` can be used to add `foreach`ability to a type, but we are fine with saying that such types cannot be constructed with a collection expression, and that types that use `CollectionBuilder` should actually define their own iteration types. We will take this for a C# 12 update (in the 8.0.2xx or 8.0.3xx branch of .NET 8), not hold it until C# 13. We also thought about whether to require that the `Create` method specified by the `CollectionBuilder` attribute is public, for symmetry with `GetEnumerator`. We're not convinced of this one: it seems like perfectly reasonable public API policy to expose a type that is publicly foreachable, but not publicly buildable. It does mean that users can put `params` on a parameter in a method that is more visible than the `Create` method for creating the parameter, but that seems squarely a mistake of API design, and not something that C# should prevent. ##### Conclusion We will require that types with a `CollectionBuilder` have a public iteration type. This means either implementing one of the `IEnumerable` interfaces, or providing a `GetEnumerator` method. We will not require that their `Create` methods are any particular visibility, as today. #### Iteration type in conversions https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/blob/1f55d3c05d549edc817589502bfee90db887d56e/proposals/csharp-12.0/collection-expressions.md#the-notion-of-iteration-type-is-not-applied-consistently-throughout-conversions The wording of how collection expression conversions are determined can lead to some confusion in particularly weird scenarios, when a type that implements `IEnumerable` does so privately and its iteration type is actually completely different. We will therefore change the wording to reflect the iteration type directly, rather than special casing the various `IEnumerable` interfaces. This has the benefit of simplifying the specification, and we expect that it will affect no real user code except the compiler test base. ##### Conclusion Change is accepted. We will reword the conversion existence part of the specification to be based on iteration type directly. ================================================ FILE: meetings/2024/LDM-2024-01-10.md ================================================ # C# Language Design Meeting for January 10th, 2024 ## Agenda - [Collection expressions: conversion vs construction](#collection-expressions-conversion-vs-construction) ## Quote of the Day - Nothing particularly amusing was said today ## Discussion ### Collection expressions: conversion vs construction https://github.com/dotnet/csharplang/issues/5354 https://github.com/dotnet/csharplang/blob/d7bbc5456e51bf29ece89112a2bb10153e98a524/proposals/csharp-12.0/collection-expressions.md#should-collection-expression-conversion-require-availability-of-a-minimal-set-of-apis-for-construction Today we looked at another scenario in collection expressions brought up by our work on `params` improvements. This time, we considered scenarios that would be an error when used as `params`, but due to how collection expressions were specified, cannot be considered an error at definition, only at usage; basically any type that implements `IEnumerable` or `IEnumerable` is valid to be used as a `params` type, even if it cannot be constructed in any real scenario. This means that, rather than the method author getting an error that their `params` parameter is invalid, the end user gets a worse error about being unable to construct the parameter type. To address that, we are looking at two potential changes: require that the type has an accessible constructor, and an accessible `Add` method that can be invoked with the _iteration type_ of the collection expression. We would then further restrict `params` to require that these are as accessible as the method itself, to ensure that if something is defined as `params`, it can be used as `params` when seen. We do, however, have some concerns, particularly around API evolution. There's a tension here between what we want to be considered convertible to collection expressions, and what we want to prevent. A prime example is `System.Collection.ImmutableArray`; this type has always supported collection initializers at compile-time, because it has an `Add` method, but it then blows up at runtime because `Add` doesn't actually mutate the underlying array, it instead returns a new `ImmutableArray`, and produces a `NullReferenceException` when doing so on a `new ImmutableArray()`. We could try to prevent the compiler from recognizing invalid `Add` methods, but we _do_ still want to recognize the older version of `ImmutableArray` for collection expressions. By doing so, we ensure that overload resolution gives good errors (`ImmutableArray` isn't constructible), rather than giving errors that no applicable methods could be found; worse, not recognizing older versions of `ImmutableArray` as valid conversion targets for collection expressions could then mean that upgrading `System.Collections.Immutable` potentially causes a source-breaking change in overload resolution. The point is somewhat moot for `ImmutableArray`, as it has already been upgraded and the compiler has taken a bit of liberty with older versions to mark them as bad at compile-time, but we have no guarantees that other community-created types that have similar construction foibles have indeed upgraded to use `CollectionBuilderAttribute`. We debated a few different possible restrictions: * Only allow `Add` methods that return `void` or `bool`. * This would mean that older collection types like `ImmutableArray` version 7.0 wouldn't be considered convertible. * Only forbid `Add` methods that return the type itself, like `ImmutableArray.Add` does. * This doesn't solve the v7.0 problem from the above problem. * It may still exclude valid types that have a fluent calling style. * What if we made the existence of a conversion only look for an accessible `Add`, and then further restrict "creatability" further down the line? * This would us to keep overload resolution stable for older APIs, and then let them upgrade to `CollectionBuilderAttribute` in new versions without potentially introducing a source-breaking change. We distilled an important characteristic from these discussions: we don't think that `IEnumerable`, by itself, is enough of a signal to make a type be a creatable collection type. Instead, what we're looking for is a set of restrictions that signal the intent of the type author that users should be able to construct the collection type. For `CollectionBuilderAttribute`, that is enough of a signal by itself. However, we need a similar rule for `IEnumerable`, and we think the existing signal for collection initializers serves as a good, well-established signal. Given this, we re-examined our handling of `string`. We intentionally left `string` as a valid conversion target for collection expressions, but under the newly proposed rules it would not be. After some more thinking, we're fine with this. In particular, we think that, even if we were to implement support for collection expressions to create `string`s in the future, we'd need to deprioritize it in overload resolution compared to `char[]`. Given that, it doesn't make sense to hold a place for it, and we'll let the new rules mark it as not a valid conversion target. Finally today, we looked at additional restrictions for `params` parameters, on top of what we considered today. In particular, we want to make sure that, when a user declares a method with a `params` parameter, you don't have to have a specific `using` in order to use it in that format. To that end, we will require that the accessible `Add` method for `params` be an instance member, rather than an extension method. #### Conclusions We accept the proposed rules for collection expressions: > For a struct or class type that implements System.Collections.IEnumerable and that does not have a create method conversions section should require presence of at least the following APIs: > > * An accessible constructor that is applicable with no arguments. > * An accessible Add instance or extension method that can be invoked with value of iteration type as the argument. We will not save a spot for `string`. For `params` parameters, we additionally require that the `Add` method be an instance method. ================================================ FILE: meetings/2024/LDM-2024-01-22.md ================================================ # C# Language Design Meeting for January 22nd, 2024 ## Agenda - [Ref struct interfaces](#ref-struct-interfaces) - [Interceptors](#interceptors) ## Discussion ### Ref struct interfaces https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md Ref structs are not currently allowed to implement interfaces. The reason is that we cannot allow them to be boxed, because they are not permitted in heap memory. However, this prevents ref structs from participating in the core abstraction mechanism we use to describe patterns and shapes of types in C#. For example, `Span` cannot implement `IEnumerable`, and methods that rely on that need to have dedicated span overloads with virtually the same implementation. The proposal allows ref structs to implement interfaces, but does not allow them to be boxed to those interfaces. Default implementations cannot be called, because that involves boxing the receiver. The way you can take advantage of the interface is by using it to satisfy a generic constraint. Generic code today does allow type parameters to be converted to their constraints, which would again lead to boxing of ref structs used as type arguments. So the proposal introduces the idea of an "anti-constraint", whereby a type parameter explicitly permits ref structs at the cost of not being able to box it inside the generic code. Such a type parameter would also participate in the lifetime tracking that we apply to refs and ref structs. The proposal anticipates other possible "anti-constraints" in the future - extra permissions on a type parameter allowing type arguments that wouldn't be admissible by default - and suggests a syntax for them that can grow to encompass these over time. Here's an example from the proposal: ``` c# T Identity(T p) allow T : ref struct => p; // Okay Span local = Identity(new Span(new int[10])); ``` We discussed where we would add the `allow T : ref struct` anti-constraint in our existing libraries. The answer is, probably nearly all our core abstractions. There might be breaking changes here, but we haven't thought of any serious ones, beyond the overload-resolution-with-lambdas ones that we usually accept. If we were to add anti-constraints to existing library types we would need to check for problems in VB and F# too. The feature requires runtime support, and the runtime team has already done some work on this, but we need to make sure things line up. Could we use this to make LINQ work with span types as source collections? That's complicated because many query methods store the source for later - deferred - query evaluation, and we can't store ref structs in objects! It may be disappointing to users that this feature doesn't enable LINQ scenarios. On the syntax front, we have some skepticism: One problem is that "allow" and "where" aren't the same kind of word, grammatically, and so reading the proposed syntax may get confusing and inelegant. Another is that you now sometimes need two separate clauses that talk about the same type parameter; a `where T : ...` and an `allow T : ...` clause. Perhaps we could address both of those problems by folding the anti-constraint syntax into the constraint syntax as an optional suffix: ``` c# where T : IEnumerable, allows ref struct ``` Finally, a more nebulous concern is how this could interact with other things we might do with generics in the future - the "unknown unknowns". #### Conclusion There are several open questions around the specifics of the design, but we think the feature is valuable and want to keep working through it. ### Interceptors https://github.com/dotnet/csharplang/issues/7009 We've had an early prototype of interceptors out with C# 12/.NET 8, and are now looking at making the feature stable. The prototype relies on the use of file paths in a way that has turned out to be problematic, since it makes the source code not portable. To that end we want to allow relative paths - in practice you'd rarely have a situation where that won't suffice. Another current problem with using file paths is that files sometimes aren't emitted to disk (yet). We could address this by making such files identifiable using the path that they *would* be emitted to. An alternative to file paths would be to have a more opaque location token that doesn't have source-level "meaning". If we recognize that interceptors will always be generated, why does this need to be something that can be written and understood by a human? However, such an approach comes with its own challenges when you try to implement it in practice. We'll keep investigating it as a possible future addition, but for now the file-path-based approach is what we'll aim at shipping. It's important that tools can identify that a given call is intercepted, so there needs to be good APIs for that which don't incur the cost of binding everything. This feature relies on source generation, but where many other source generators are about enhancing the surface area for the developer, it's desirable that interceptors are *hidden* from being manually called, and only affect the call site that they are intercepting. This can also lower the cost of the source generation, and the frequency at which it runs. #### Conclusion This is not a feature area that is primarily driven from the language. There is no new proposed syntax, but there is some language-level behavior. We should keep the LDM in the loop as this feature area evolves, so that language-level concerns continue to be factored in. ================================================ FILE: meetings/2024/LDM-2024-01-29.md ================================================ # C# Language Design Meeting for January 29th, 2024 ## Agenda - [`params` collections](#params-collections) - [Better function member changes](#better-function-member-changes) - [`dynamic` support](#dynamic-support) - [`dynamic` and `ref` local function bugfixing](#dynamic-and-ref-local-function-bugfixing) ## Quote of the Day - "I got a new webcamera, I think I look kinda washed out... Insert C# joke" ## Discussion ### `params` collections https://github.com/dotnet/csharplang/issues/7700 #### Better function member changes https://github.com/dotnet/csharplang/blob/7a506890f909ea06d8b8396eb5e86a92c8482ade/proposals/params-collections.md#better-function-member We started today by reviewing the proposed rules for how `params` collections will handle better function member. These rules generally try to apply the pre-existing rules we have around `params` arrays to the new scenarios of `params` collections. However, we can't just think of these as "wrap the arguments with `[]`, and that's the result you should get, as that already doesn't work for `params` arrays. As an example: ```cs Test([1,2,3]); // error CS0121: The call is ambiguous between the following methods or properties: 'Program.Test(params int[])' and 'Program.Test(params long[])' Test(1, 2, 3); // We pick 'static void Test(params int[] x)' partial class Program { static void Test(params int[] x) {} static void Test(params long[] x) {} } ``` There are also some scenarios where `params` may be ambiguous where an explicit collection expression would not be, such as: ```cs static void Test3() { M3("3", ["4"]); // Span overload is used, better on the first argument conversion, none is better on the second M3("3", "4"); // Ambiguity, better-ness of argument conversions goes in opposite directions. // Since parameter types are different ("object, string" vs. "string, object"), tie-breaking rules do not apply } static void M3(object x, params string[] y) {} static void M3(string x, params Span y) {} ``` However, we don't think these are common scenarios that need to be particularly concerned about, and they don't significantly affect the overall goal of making the way that users conceive of the language simpler, even if some of the nitty-gritty edge cases will be more complex than they appear. ##### Conclusion The rules are approved as proposed. We will watch early adopters (particularly the BCL) to make sure that there's no cases in the real world that we didn't think of during this work. #### `dynamic` support https://github.com/dotnet/csharplang/blob/7a506890f909ea06d8b8396eb5e86a92c8482ade/proposals/params-collections.md#dynamic-vs-static-binding Next, we looked at `dynamic` support. We don't expect the runtime binder to be updated to understand `params` collections, so we need to consider how to handle when we detect that users are potentially going to encounter a runtime exception. We overall think that it's a good idea to try give warnings or errors when we know that candidates will either be excluded or that there are no callable candidates. ##### Conclusion The rules around `dynamic` binding are accepted as proposed. ### `dynamic` and `ref` local function bugfixing https://github.com/dotnet/roslyn/issues/71399 Finally today, we looked at a bug in the C# compiler that was uncovered during the investigation into `params` collections. The decision on the previous section makes fixing this more important, as it affects what "applicable" candidates appear during overload resolution. The main question we have is not whether to fix the bug, but how broad to make the fix; do we report the error in _all_ language versions, or just in C# 13? This seems fairly low-risk: the compiler only has 1 test that is affected by this, and the test covers a scenario that will fail at runtime anyway. We can revisit this later if it turns out to be a more broad breaking change. #### Conclusion We will fix this bug in all language versions. ================================================ FILE: meetings/2024/LDM-2024-01-31.md ================================================ # C# Language Design Meeting for January 31st, 2024 ## Agenda - [Relax "enumerable" requirement for collection expressions](#relax-enumerable-requirement-for-collection-expressions) - [`params` collections evaluation orders](#params-collections-evaluation-orders) ## Quote of the Day - "It doesn't matter so much because it's already so bad" ## Discussions ### Relax "enumerable" requirement for collection expressions https://github.com/dotnet/csharplang/issues/7744 We started today by looking at a proposal to relax the requirement that types attributed with `CollectionBuilderAttribute` be enumerable in some fashion, as defined by `foreach`. This was done because want the type to "signal" that it is a collection type, and to give the specification some way of determining the element type of the collection, which we do by looking at the iteration type from enumeration. However, a few cases have since been brought up of what are essentially collection builder types; they cannot be directly enumerated, but can be materialized into iterable collections, and it may be idiomatic for us to allow using collection expressions to construct them. We could therefore use `CollectionBuilderAttribute` as an explicit signal that the type it's applied to is a collection of some kind. We then need to figure out how we update the spec to account for this, given our current heavy use of the "iteration type throughout. We also need to consider what to do when the iteration type and the type of a create method don't line up. For both of these scenarios, we're currently thinking that we should maintain backwards compat; ie, if the collection defines an iteration type, then that's the one that's preferred, as today. This would just be a fallback rule in the case that no iteration type was defined. However, the exact rules to flow through the rest of the spec will need to be looked at by the collection expressions working group and brought back as a more complete proposal, as they're more complex than what was initially brought today. #### Conclusion The proposal is approved and put in the working set, and we will work on the specific wording as part of the collection expression changes in C# 13. ### `params` collections evaluation orders https://github.com/dotnet/csharplang/issues/7700 https://github.com/dotnet/csharplang/blob/951276cbc2f0ec3d688747109e904a3ddd6b29c5/proposals/params-collections.md#order-of-evaluation-with-non-array-collections-in-non-trivial-scenarios Lastly today, we looked at evaluation orders for `params` collections in several non-trivial nested examples. In general, we approved the proposed rules without significant discussion. The main point of evaluation was around how reuse of the collection will differ from how `params` arrays work in nested indexers in object initializers. The existing `params` array behavior is, as far as we can tell, actually unspecified behavior in C#; the spec appears to permit reuse of the array instance, but it does not require it. We think that for `params` collections, we can do better, but we do not currently intend on changing the behavior of `params` arrays. #### Conclusion Rules are adopted as written. ================================================ FILE: meetings/2024/LDM-2024-02-05.md ================================================ # C# Language Design Meeting for February 5th, 2024 ## Agenda - [First-class span types](#first-class-span-types) - [Collection expressions: inline collections](#collection-expressions-inline-collections) ## Quote of the Day - "Did I spell legite correctly? My Memory isn't that great" "No, it's legit. Trailing e would make it like vegemite." ## Discussion ### First-class span types https://github.com/dotnet/csharplang/issues/7905 https://github.com/dotnet/csharplang/pull/7904 We started today by looking over a new proposal aimed at addressing a few pain points with `Span` and `ReadOnlySpan` that the language and library authors have encountered since their introduction. These are areas that arrays work well in, but `Span` and `ReadOnlySpan` do not. To that end, we reviewed a proposal for treating these types more specially in the language, much as we do for array types. We had a few thoughts on specific parts of the proposal: * Even though `ReadOnlySpan` exposes the `T` by `ref`, it's always a `readonly ref`, so we're fine to treat it as if it's covariant. If the `T` was just by `ref`, it wouldn't be safe. * We're fine with `is` not working covariantly. This is the same thing as `int` to `long` conversions: C# can do it, but the runtime doesn't know about the conversion in type checking. * We thought about whether this should extend to `Memory`, but are wary of it. `Memory` does not have an implicit conversion to `Span`/`ReadOnlySpan` because, depending on the backing storage, it can have a cost to convert, and we'd be worried about suddenly creating such conversions in the language. * At the same time, though, we are also sympathetic to the concern that, if a code path needs to be `async`ified and converted to `Memory`, that may necessitate more changes to allow what works for `Span` to work in the `async` world. * We may need to keep special cases for betterness to keep `ReadOnlySpan` better than `Span`; in the psuedo type hierarchy, `Span` is better than `ReadOnlySpan`, because it is more specific. However, this is not what we want overload resolution to choose, since `Span` offers worse performance in most cases. Overall, we like the direction of this proposal, and want to proceed with it. We'll answer open questions as implementation progresses. #### Conclusion Proposal is accepted. ### Collection expressions: inline collections https://github.com/dotnet/csharplang/issues/7913 https://github.com/dotnet/csharplang/pull/7864 Next, we looked at one of the collection expression topics we want to handle in C# 13, inline collection expression usage. These are scenarios where a collection expression is immediately used and never observed outside an expression, usually to handle some kind of conditional addition to a collection, or to enumerate in a `foreach`. The main question here is whether we should rely purely on natural typing for these scenarios, or whether target element typing should flow in somehow. For example: ```cs // If `byte` doesn't flow into the conditional as an element type, the natural element type will be `int`, and the conversion will fail byte[] x = [1, .. b ? [2] : []]; // If `bool?` doesn't flow into the iteration expression, there will be a compile error because no natural type can be found foreach (bool? b in [true, false, null]) {} ``` We do have some disagreements on how exactly that information would flow into the expression to give it a final type: do we have to have some stopgap solution until we have a natural type concept, or can we just integrate with that? We're generally in agreement that these examples should be able to compile, meaning that we are generally in favor of flowing in an element type in some fashion. With this direction, we think the working group can take the lead on integrating with natural typing work and then propose the final approach to LDM (or propose that it doesn't integrate with specific reasoning for why it shouldn't). #### Conclusion We are in favor of target element typing flowing into collection expressions. ================================================ FILE: meetings/2024/LDM-2024-02-07.md ================================================ # C# Language Design Meeting for February 7th, 2024 ## Agenda - [Partial type inference](#partial-type-inference) - [Breaking change warnings](#breaking-change-warnings) ## Quote of the Day - "It's easy to dismiss as well, that's just math" ## Discussion ### Partial type inference https://github.com/dotnet/csharplang/issues/1349 https://github.com/dotnet/csharplang/pull/7582 We started today by looking at an Any Time proposal being worked on by a community member, @TomatorCZ, for partial type inference. He's been working on this proposal as part of his master's thesis, and it's now at the point that LDM can take an initial look. One important thing that we wanted to establish was the motivation of the proposal. We're not just trying to put `_` or `var` in more places; instead, what we're trying to do is ease the cliff between scenarios where the compiler can fully infer a type, vs when it needs a bit of prodding to get the desired result. Thus, we are interested in the nested levels of type inference, because they allow the user to prod the compiler into, for example, choosing a specific type of collection (`IEnumerable` over `List`) for a specific generic method, while not forcing the user to restate the element type that is forced by some other input to the generic method. One immediate concern, though, is that while this nesting of inference is very powerful, it could also lead to unbounded computation if not done carefully. The proposal has been crafted with this in mind, as it is designed to ensure: * Inference is bounded by the statement level. Statements cannot have inference effects on other statements except through lambda bodies, as can happen today. * Inference cannot visit a single expression more than twice when calculating types. This ensures that we aren't looking at a Hindley-Milner level of complexity, and hopefully helps keep the error scenarios constrained enough to offer good diagnostics. The C# compiler already has some issues with giving good diagnostics in lambda scenarios, and we don't want to make it worse across the board by having non-local analysis failures. Finally, we briefly considered the tooling aspect of this feature; like `var`, some users may wish to explicitly turn inference off, or turn it off unless the type inferred is apparent. We think this is mostly a tooling problem, and that it should be solvable, though defining what "is apparent" will mean here may take some tweaking over time. We're happy with this proposal as a starting point and want to keep working on it. It's not going to make C# 13, but we have high hopes for the proposal, and think it's heading in the right direction. We'll tackle specific open questions and design points in later sessions. #### Conclusion Proposal will keep moving forward. ### Breaking change warnings https://github.com/dotnet/csharplang/issues/7189 https://github.com/dotnet/csharplang/issues/7918 Next, we looked at the newest proposal for breaking change warnings. We had a few decisions we wanted to make here: 1. Are the set of breaking change criteria generally reasonable? 2. Do they apply to `field`? 3. Are we ok with changing or removing the `latest` language version? For the 1, we think we're ok with the criteria laid out here, but we do want to go even further: we'd like to have users be able to opt-in to warnings _after_ an upgrade, in case they did not migrate using whatever garden-path approach we create. In other words, to apply to `field`, we'd like a warning after the user is on C# 13 for "there was a `field` in scope that we're not binding to anymore, did you mean to do that?" warning. Next, we looked at applying the criteria specifically to `field`. We didn't come to any hard conclusions here: there is some amount of verbal consensus that `field` would fit, but we also do want to make sure that we're not holding the feature hostage for breaking changes any more than we already have. Finally, we thought a bit about changing the `Latest` langversion. This ended up being a spirited discussion; there are ~6000 usages of the flag on GitHub, with a decent number in internal repos as well. We're not entirely certain that changing it would meet the breaking change criteria we laid out in 1. #### Conclusions We're generally in favor of the criteria, but we want to look at post-upgrade opt-in warnings as well to help with non-linear upgrade paths as well. ================================================ FILE: meetings/2024/LDM-2024-02-21.md ================================================ # C# Language Design Meeting for February 21st, 2024 ## Agenda - [Declaration of ref/out parameters in lambdas without typename](#declaration-of-refout-parameters-in-lambdas-without-typename) - [`params` collections](#params-collections) - [Metadata format](#metadata-format) - [`params` and `scoped` across `override`s](#params-and-scoped-across-overrides) - [`required` members and `params` parameters](#required-members-and-params-parameters) ## Quote of the Day - "People just stopped coming in" "I stopped coming in on Mondays. Today" "is not a Monday!" ## Discussions ### Declaration of ref/out parameters in lambdas without typename https://github.com/dotnet/csharplang/issues/338 https://github.com/dotnet/csharplang/blob/main/proposals/simple-lambda-parameters-with-modifiers.md We started today by looking at an Any Time proposal specification to address a common cliff: when using a delegate type with a modifier of any kind, users are required to spell out the parameter types of that lambda. This is a change that the LDM has long mentioned in passing, but has never actually gone through and specified. In addition to the main proposal, we also discussed few potential alternatives: * We could potentially infer the `ref`/`out` modifiers, rather than having the user restate them. We don't like this option; C# thinks that changes in calling convention are very important, to the point that we require `ref` or `out` at callsites. We think this would be the same cliff, and that therefore it should require the modifiers be stated explicitly. * We could go further, and allow some lambda parameters to be fully stated, and others to be just names. We don't currently have any real driving use cases for such a feature though, and it would be quite complex. It would also likely want to be designed at the same time as thinking about the partial type inference proposal. We therefore wish to move forward with this proposal. We turned to thinking about the open questions. For both of them, we think that there are some implementation complexities that, while not insurmountable, would be challenging. Given this, and the lack of requests for either feature, we think that we should simply say that neither attributes nor default parameter values will be supported without a fully-typed lambda, as it works in C# 12. We do note that the specese will need to be cleaned up a bit: _implicit_anonymous_function_parameter_ex_ isn't a great name to put in the spec, but that can be fixed up without blocking the feature. #### Conclusion The proposed specification is approved as proposed. Both open questions are rejected. ### `params` collections https://github.com/dotnet/csharplang/issues/7700 In the second half of the meeting today, we went over the remainder of the `params` collections specification and open issues. #### Metadata format https://github.com/dotnet/csharplang/blob/9d12a983190ba5d25fb037a2566bb1e99d486c49/proposals/params-collections.md#metadata The `params` collections proposal suggests using a new attribute type to declare when a non-array type is `params`, in order to avoid potential interop issues with non-C# compilers. After looking at the proposal and the reasons for it, the LDM is fine with this approach. The attribute will need to go through the BCL's design review. ##### Conclusion Proposed metadata format is approved. #### `params` and `scoped` across `override`s https://github.com/dotnet/csharplang/blob/9d12a983190ba5d25fb037a2566bb1e99d486c49/proposals/params-collections.md#consider-enforcing-scoped-or-params-across-overrides Next, we looked at a potential area of confusion: `scoped` across overrides can potentially be confusing because `params` can be inferred. Specifically, because `params` can be implicitly inferred, and it implicitly implies `scoped`, `scoped` can be inferred a way that it cannot be today. While there is no safety issue here, we are concerned about potential for user confusion. The `scope`dness here is very different than the existing meaning of `params` because unlike `params`, which has no effect inside the method body, `scoped` _does_ have an effect inside the method body; it changes where the parameter can be used or returned. Given this, we think that it would be best to require overrides to specify either `params` or `scoped` if they would have otherwise been required to do so. As an example of the proposed rules: ```cs class Base { internal virtual Span M1(scoped Span s1, params Span s2) => throw null!; internal virtual void M2(scoped Span s1) => throw null!; internal virtual void M3(params Span s2) => throw null!; } class Derived : Base { internal override Span M1(Span s1, // Today: error, missing `scoped` on override Span s2 // Proposed: error, missing `scoped` or `params` ) => throw null!; internal override void M2(Span s1) => throw null!; // Today: no error internal override void M3(Span s2) => throw null!; // Proposed: no error } ``` ##### Conclusion We will require explicitly stating `scoped` or `params` on override of a `params` parameter when a non-`params` parameter would be required to do so. #### `required` members and `params` parameters https://github.com/dotnet/csharplang/blob/9d12a983190ba5d25fb037a2566bb1e99d486c49/proposals/params-collections.md#should-presence-of-required-members-prevent-declaration-of-params-parameter Finally, we looked at an edge case of when a collection type has `required` members. We don't expect this to be commonly hit (or even hit at all outside the compiler test base), but we do want to consider it. Given that we have already mandated that `params` collections types must have an applicable parameterless constructor, we think this is appropriate to use for validation at the declaration point of such a parameter. The call site may end up using a different constructor due to the binding rules of `params` collections, but we don't think that it's necessary to try and validate this; the call site will do its own checking, and the pathological case of a collection type that can be used as a `params` parameter because it has a parameterless constructor with `SetsRequiredMembers` applied but the other constructors that would be used at the call site is not something we need to try and account for. ##### Conclusion We will validate `required` members against the constructor that is used to determine eligibility to be a `params` parameter at the declaration site. ================================================ FILE: meetings/2024/LDM-2024-02-26.md ================================================ # C# Language Design Meeting for February 26th, 2024 ## Agenda - [`ref struct`s in generics](#ref-structs-in-generics) - [Collection expressions](#collection-expressions) ## Quote(s) of the Day - "Sorry, I had a meeting run over" "Unacceptable" "Yeah, that never happens here" - "Do it British style. allouws" ## Discussion ### `ref struct`s in generics https://github.com/dotnet/csharplang/issues/7608 We started today with a timeboxed conversation to allow the implementation work on `ref struct` in generics to proceed with parsing work, and hopefully not need to redo much work later when the LDM makes more complete decisions. We looked at a couple different syntax proposals for how the anti-constraint of allowing `ref struct`s could work: * `where T : allows ref struct` - Put a new keyword inside the existing `where` clause. * `allows T : ref struct` - Add a new clause that can go beside a `where` clause. The crux of which form to choose comes down to the question of what we think the existing form actually means: is it _only_ narrowing information about `T`, or is more generally "information about `T`". If we think it's the former, then a separate clause for widening `T` would be appropriate; if the latter, then we can include widening information in the `allows`. After some quick debate, we lean very heavily towards the latter view. We also think that it will simplify reading and understanding method definitions, as the user won't have to keep track of multiple sets of information about `T`. We then looked at what keyword to use. We have two real options that were suggested: either `allow`, or `allows`. This choice was much murkier: does the constraint section reflect the author saying "I allow T to be a ref struct", or is it reflecting the user reading a constraint and saying "This allows T to be a ref struct". We have a slight lean towards the latter, but ran out of time in the box before coming to a definitive solution. The major parsing choice is the constraint form, while the keyword choice is fairly minor and easily changed later before shipping, so we called it in favor of `allows` for now, and will revisit later. #### Conclusion We will go with the "put the anti-constraint in the `where` clause" form, and we will use the keyword `allows` for now. ### Collection expressions https://github.com/dotnet/roslyn/issues/72098 https://github.com/dotnet/csharplang/issues/5354 Finally, we looked at some of the consequences of our [previous decision](LDM-2024-01-10.md#collection-expressions-conversion-vs-construction) with how the impact a few real-world types in WinForms and WPF. These are very old collection types; their design predates many of our modern collection designs. Specifically, they implement `System.Collections.IList`, and not any generic version. However, they try to add a bit of type safety by hiding `IList.Add(object)`, and instead expose strongly-typed `Add` methods that take the specific type they care about. For example, `ListView.ColumnHeaderCollection` exposes an `Add(ColumnHeader)` method. However, because it does not implement `IList`, its iteration type is `object`; this means, for the purposes of determining whether a collection expression conversion exists, we look specifically for the `Add(object)` method and don't find it. We have a few ideas to address this: 1. Remove the restriction we added previously for `IEnumerable` cases for non-`params` scenarios. 2. Do nothing and leave the rules as they are. These collections are: 1. Not modified, and collection expressions simply do not work for them. 2. Updated to include `IEnumerable` implementations, giving them a more specific iteration type. 3. Allow looking for `Add` methods that take a subtype of the iteration type. 4. Use the strongly-typed indexer of such types to narrow the iteration type. 5. Include explicit `Add` implementations in the search for the `Add(object)` method for the purposes of conversion. Complicating this discussion is a further wrinkle: these collections often have other `Add` methods that take other types that can effectively be converted to the effective iteration type of the collection. For example, `ListView.ColumnHeaderCollection` has an `Add(string)` method that constructs a `ColumnHeader` out of the `string` for ease of use. Some of the solutions we're looking at here wouldn't solve that scenario: 1 would work, as would 3, and possibly 5, but 4 would not. 2 likely would, but we also think it extremely unlikely that we'd be able to do 2.2, specifically adding an implicit conversion from `string` to `CollectionHeader`. Conceptually, there is an implicit conversion there; that's what the various `Add` methods are simulating. But adding a new implicit user-defined conversion to a type is always a risk, particularly a type that has been around as long as some of these WinForms and WPF types, and we think it's extremely unlikely that 2.2 would ever happen because of this. One important note for all of these options around loosening the requirements is that this would be a divergence from what we want to work for `params` scenarios; because we're very concerned about the effect of nested overload resolution on looking for unbounded `Add` methods on determining the applicability of expanded `params` methods, we don't have any plans to adjust the behavior for that feature. It will be an unfortunate difference in behavior, but we think it's a necessary one to avoid making overload resolution even more complex than it already is. This means that if we did option 1 or 3, there would be a difference in behavior. 4 or 5 may be able to apply to `params`, but we'd need to consider it before confirming that it will. We did not reach any conclusions on this topic today. We want the working group to more fully explore these options and come back with examples of where they will work, where they will fall over, and what the impact would be to various scenarios. ================================================ FILE: meetings/2024/LDM-2024-02-28.md ================================================ # C# Language Design Meeting for February 28th, 2024 ## Agenda - [Extensions](#extensions) ## Quote of the Day - "If we just don't make any mistakes it'll be fine" ## Discussion ### Extensions https://github.com/dotnet/csharplang/issues/5497 https://github.com/dotnet/csharplang/pull/7179/commits/520a2a40fc5a0fdb572e5f20f593f9eac59a88da Today we looked over the proposed set of lookup rules for extensions. Much of the meeting was going over the rules as proposed, which I won't reiterate in these notes; they can be found in the linked pull request commit above. We made a few comments along the way: * It may be a bit interesting that you can look up extension members without a qualifier when you're within the extension type that contains the members. We think this is fine, as this is a new context that gets to redefine what `this` binds to; `this` for such scenarios is not the underlying type, it's the current extension type. * We think that the proposed form of doing lookup when in an extension type as if the extension type's underlying type is an inheritance relationship is the correct decision. * Lookup order proved to be a contentious topic. The proposed rules prioritize one type of extension method over the other, but we think that this is likely a mistake. Users may start naturally migrating over to the new extensions slowly, and this may cause potential scenarios where the "wrong" version of an extension is picked; there's no clear answer to us whether the old or new should win beyond the overload resolution rules that we already have in the language. For example: ```cs static class Extensions { public static X ToX(this IEnumerable values) => ... } implicit extension ImmutableArrayExtensions for ImmutableArray { public X ToX() => ... } // or reverse: static class Extensions { public static X ToX(this ImmutableArray values) => ... } implicit extension IEnumerableExtensions for IEnumerable { public X ToX() => ... } ``` For either of these cases, it seems that the best solution is simply to give both `ToX` methods to overload resolution and let it sort out which one is preferred, erroring if neither is preferred. The precise details of this, and whether there will be any disambiguation of old vs new as a final tiebreaker, will need to come in a future meeting; what we are certain of at this point is that the proposed version, where there is a preference of one version for lookup, isn't workable. * There are also some disambiguation scenarios that we will need to consider that can't occur today, such as what will happen in the scenario where a simple name binds to both an extension property and an old-style extension method. We can likely look to similar scenarios that can occur for instance members in metadata today, but it's a scenario to think about. Example: ```cs class TableIDoNotOwn : IEnumerable { } static class IEnumerableExtensions { public int Count(this IEnumerable t); } implicit extension MyTableExtensions for TableIDoNotOwn { public int Count { get { ... } } } // What happens here? var v = table.Count; // Let's get a read from LDM ``` #### Conclusion Lookup in extension types should treat the underlying type as if it is the base type of the extension type. We need to go back and redesign the rules for lookup of extension members from outside of an extension type to mix old and new style extensions. ================================================ FILE: meetings/2024/LDM-2024-03-04.md ================================================ # C# Language Design Meeting for March 4th, 2024 ## Agenda - [Breaking changes: making `field` and `value` contextual keywords](#breaking-changes-making-field-and-value-contextual-keywords) - [Overload resolution priority](#overload-resolution-priority) ## Quote of the Day - "I think it's actually a good think to have that onericity. Onericity? I think that's a word I just made up." ## Discussion ### Breaking changes: making `field` and `value` contextual keywords https://github.com/dotnet/csharplang/issues/7964 https://github.com/dotnet/csharplang/issues/7918 We started today by exploring a piece of community feedback on the previous proposal for introducing breaking changes: what if, instead of trying to make `field` behave like `value` does today, we try to make it a contextual keyword when in a property accessor? One thing that became clear here, after multiple discussion directions, was that we needed to determine what the most important bit to the LDM is here: is it most important to the LDM that `field` behaves identically to `value` (whether that's as a contextual keyword or an implicit parameter), or can we view them as unrelated concepts? We mostly think the former, but there are some members of the LDM that would prefer to have `field` have their desired semantics, even if that means it differs from `value`. There is a potential that, by changing `value`, we will break a concept that is understood by a large number of our users. We do hear more about people misunderstanding the meaning of `value` than not, but this could simply be a biased opinion because we only ever hear the complaints. We then turned to talking about the advantages and disadvantages of using contextual keywords in these scenarios, and how broad we want to make the context. A number of LDM members were happy with the fairly constrained scope of the breaks and fixes here, and think that they end up being fairly straightforward. There are a few interesting wrinkles that need to be worked out: * What is the behavior of `nameof(value)`? Today, it's the string `"value"`, `value` is actually a parameter named `value`. We didn't arrive at a concrete conclusion on this today. * How does this behave for indexers? `value` is a legal parameter for get-only indexers, does it need to remain so? And does it need to continue to be illegal for settable properties? We also did not arrive at a conclusion for this today. After discussion, we are most in favor of `field` and `value` as contextual keywords. We then discussed the scope of this: does every usage of `field` and `value` within a property need to be escaped, just the simple names, or somewhere in between? The example we looked at for these is: ```cs int Prop { get { int field = 1; // If it's only simple names that need to be escaped, this is legal field = 2; // This needs to be escaped in every version; are we ok with the inconsistency between declaration and usage? this.field = 3; // This is unambiguous, but does it need to be escaped anyways? } } ``` We ended up with 3 options: 1. Only simple names need to be escaped. `int field = 1` is legal, as is `this.field`, but `field = 2` would need to be escaped to refer to the local or class field. 2. Simple names and declarations need to be escaped. `int field = 1;` needs to be escaped, but `this.field` does not. 3. All usages need to be escaped. Both `int field = 1;` and `this.field` need to be escaped. Consistency is again a key point here. Existing contextual keywords, such as `await` within an `async` method body, must always be escaped, no matter how it's used. `this.await`, for example, must be written as `this.@await`. This ultimately convinced us that option 3 is right: if the goal here is to take breaks to make the language simpler, let's not introduce another set of rules around when a contextual keyword is legal to use unescaped. #### Conclusion We will move forward with the proposal to treat `field` and `value` as contextual keywords. We will treat them as contextual keywords in all usages within accessor bodies. More work will need to be done to determine the behavior of indexers, `nameof(value)`, and whether `value` is a contextual keyword within a `get` body. ### Overload resolution priority https://github.com/dotnet/csharplang/issues/7706 https://github.com/dotnet/csharplang/pull/7906/commits/aa6ab11c7df001e807e956f5b056785588e8b12e Finally today, we took a brief look at the proposal for overload resolution priority. We've previously looked at it in the form of `BinaryCompatOnlyAttribute`, but some particularly gnarly challenges with OHI convinced us that it was not the right approach. Instead, we went with a narrower approach of allowing an API author to adjust the relative priority of their methods to ensure that something that is better for a given domain, such as the `Debug.Assert` overload that takes a `CallerArgumentExpressionAttribute`, is preferred over what C# would normally choose. The LDM appreciated the specificity and narrow target of the new proposal, though there are likely some finer details to work out with where exactly the specification needs to be modified, and how it will interact with extension lookup in the new extension type world. We also did a quick dive on some of the open questions around inheritance with this new attribute: with the exception of a few things like parameter names and default values, the original definition of a virtual method is always the one used for determining applicability, and it seems likely that we'd want to do the same here, and not allow derived overrides to change the priority of a member; we did not concretely decide this, however, so we'll need to confirm when implementation starts. #### Conclusion Proposal is approved, and we'll work on it in the upcoming development cycle. ================================================ FILE: meetings/2024/LDM-2024-03-11.md ================================================ # C# Language Design Meeting for March 11th, 2024 ## Agenda - [Dictionary expressions](#dictionary-expressions) ## Quote of the Day - _Someone in the room picks up a knife to slice some bread_ "That is going to make it impossible for the people online to hear" "Thank you for advocating for us" "Thank you for also drawing attention to the fact that we don't have any bread" ## Discussion ### Dictionary expressions https://github.com/dotnet/csharplang/issues/7822 https://github.com/dotnet/csharplang/blob/dcf46ba377e1ce356f69f981f36b2273c3179ca3/proposals/dictionary-expressions.md Today, we took a look at the first proposal for dictionary expressions. There are a number of open questions that need to be answered, including syntax decisions; however, in order to make progress on the more thorny semantics questions, we are using the `[key: value]` strawman syntax from the proposal for now. We will make concrete decisions on the syntax at a later time. The syntax we end up with is important, of course, but fairly unconnected to the semantics we need to delve into at the moment. The first two questions we looked at are highly related. They are: 1. Should we allow KeyValuePair expression elements in dictionary expressions? 2. Should we allow spreading an enumerable of KeyValuePair in a dictionary expression? The first question can even be expressed in terms of the second: after all, if we allow the second, and we give collection expressions a natural type, then users could just do `[.. [keyValuePairExpression]]` even if we don't allow 1. This question brought up a recurring theme through the rest of the open questions we looked at: we're currently calling these dictionary expressions, because that's a catchy name that immediately conveys the general target of these. However, what are they actually? Are they dictionary expressions, or are they actually collections of associated keys and values? The LDM is unanimously in favor of supporting both these scenarios. Next, we looked at whether these expressions should be able to initialize generalized collection types, or just "dictionary" types. Specifically, can these types initialize a `List>` and things like it, or are they stuck initializing things like `Dictionary`. This is a trickier question; our correspondence principle for creation and consumption does not generally hold up for such scenarios. As an example: ```cs List> list = ["mads": 21]; Console.Write(list["mads"]); // Can't consume like this because it's a List ``` No matter what syntax we choose for the initialization form, there won't be a correspondence with usage here, as `List<>` is accessed by index, not by key. This gives a few members of the LDM pause, but overall, we're in favor of allowing this. The next topic was on how restrictive we should be on `KeyValuePair` itself; namely, must the type involved be exactly `System.Collections.Generic.KeyValuePair`, or should it be expanded? One example is `(TKey string, TValue value)`. This tuple type is conceptually a KVP, and some collections use it or other similar tuple types instead of KVP. For example, `PriorityQueue` exposes an `UnorderedItems` property that is an `IReadOnlyList<(TElement Element, TPriority Priority)>`. We may want to enable merging of 2 queues by doing `PriorityQueue p = [.. originalQueue1.UnorderedItems, .. originalQueue2.UnorderedItems];`. This example raises several questions of its own (what APIs would we use, since there's neither `Add` nor indexers on `PriorityQueue`?), but there is at least some merit in considering the idea. There's another question here, too: what about conversions between keys and values in the source collection expression and the destination? For example: `Dictionary d = [shortKey: shortValue];`, where the key and value need to be subjected to a `short->int` conversion. Are conversions like this acceptable? This plays back into the question of what these collections actually are: if they're conceptually collection expressions, then the conversions around the keys and values may be less acceptable. However, if they're collections of associated keys and values, then subjecting the individual parts may be fine. Ultimately, LDM did not reach a decision on this question today. We'll need to dig more into it in future meetings. Finally, we looked at the semantics of how a dictionary expression will build its resulting value: does it overwrite existing values, or does it error when there are duplicate keys? There are few different interesting points here: we're currently calling these dictionary expressions. That, combined with how collection expressions are specified around `Add` methods, may give users a natural intuition that we'll be calling `Add` on the dictionary, which will throw when a duplicate is encountered. However, `System.Collection.Generics.Dictionary<,>` may not be the only dictionary a user would key off of, and .NET has very inconsistent behavior for what `Add` on a dictionary-like type will do. `ImmutableDictionary<,>` will not throw, and will keep the original value if the new value compares equal to the original value. `FrozenDictionary<,>` immediately throws `NotSupportedException`s; the list goes on, but the only consistent bit is that we're inconsistent in behavior. These discrepancies had us reconsider the question from another direction: rather than "What is the behavior", we instead want to consider "What APIs are we calling"; this will allow each scenario to tailor its APIs to handle the question directly, rather than forcing a single behavior on all collection types. There's some concern among members that using an indexer, rather than calling an `Add` method, will lead to concerning behavioral differences if a user decides to refactor existing collection initializers to dictionary expressions. We do have existing behavior in our IDE fixers that can help convey that a refactoring is not exactly semantics-preserving, but it would require the user understanding the exact difference and examining usages to ensure that they're not falling afoul of bad behavior in the presence of indexer assignment. However, a larger portion of the LDM is more concerned that, in the presence of spreads (which have no collection initializer analogy), `Add` is more often the wrong thing to call than using an object initializer. That is the direction we'll go with for now, and explore how we specify the rules given indexer usage instead of `Add` calls. #### Conclusions Questions 1 and 2 are wholeheartedly approved; KVP expressions and spreads of KVP collections will work in dictionary expressions. Question 3 is approved; dictionary expressions will be able to convert to collection types that have a KVP element type. Question 4 needs more time to be considered. Question 5 settled on using indexers as the lowering form. ================================================ FILE: meetings/2024/LDM-2024-03-27.md ================================================ # C# Language Design Meeting for March 28th, 2024 ## Agenda - [Discriminated Unions](#discriminated-unions) ## Quote of the Day - "I think [redacted LDT member] is a decent person... oh wait, did you turn the mic back on?" - `public sealed enum Planets { Earth, Jupiter, Mars, Mercury, Neptune, Pluto, Saturn, Uranus, Venus }` ## Discussion ### Discriminated Unions https://github.com/dotnet/csharplang/issues/113 Today, we took a look at the areas the discriminated unions working group has been investigating. A significant portion of the meeting was going over slides, which have been included [here](./LDM-2024-03-27-presentation.pdf). These slides have been helpfully annotated with speaking notes, so this set of notes won't go deep into the points covered in the presentation itself. We had a few reads of the room during the meeting, particularly at the end, to try and help focus on the next direction for the working group to move. One clear result from these reads was that we need to support existing "closed" type hierarchies in some fashion, even if it's just in exhaustiveness in pattern matching. This already has a [championed issue](https://github.com/dotnet/csharplang/issues/485), and we don't think that we necessarily need to tie it to DUs specifically, but that we should understand how the broader DU feature will behave so that we can integrate these existing hierarchies into that feature set, rather than having multiple sets of behaviors depending on how such a hierarchy was defined. We were also unable to clearly come to a decision around implementation strategies after this meeting; we have a much better understanding now of the tradeoffs that various strategies will have, both from a performance perspective, and from a versioning perspective, but we still need to narrow the scenarios we're trying to address further before we can determine what weights to put on those tradeoffs. ================================================ FILE: meetings/2024/LDM-2024-04-01.md ================================================ # C# Language Design Meeting for April 1st, 2024 ## Agenda - [Async improvements (Async2)](#async-improvements-async2) ## Quote of the Day - _Andy gets to the CancelScope slide_ "There's this idea called structured concurrency" _Disconnects_ "He definitely awaited an `async void`" ## Discussion ### Async improvements (Async2) https://github.com/dotnet/runtimelab/blob/e69dda51c7d796b8122d0f55b560bc44094a4bec/docs/design/features/runtime-handled-tasks.md [Presentation](./LDM-2024-04-01-presentation.pdf) Today, we looked at a proposal for improving `async` in .NET. The runtime has been experimenting with several different approaches here. Their first prototype, green threading, was designed to try and solve the metastability issue where sync-over-async can cause threadpool exhaustion. While this was an interesting experiment, their ultimate conclusion was to not move forward with this experiment, and instead focus on improving the overall performance of `async` methods; their results can be viewed [here](https://github.com/dotnet/runtimelab/issues/2398). To that end, we're looking at what language or compiler changes may be required if we were to update the `async` state machine generation. For the purposes of discussion, we refer to this new format as `async2`; this does not mean that we expect to introduce a literal `async2` keyword, it's simply a shorthand. The current runtime experiment moves the state machine generation directly into the runtime, rather than having the C# compiler do it. This can be done entirely without breaking changes in the behavior of code, but there is one major concern; `SyncContext`s and `AsyncLocal`s. The saving of the execution context today ensures that, when an async method returns, its modifications to an `AsyncLocal` are not observed by callers, because of the compiler's saving and restoring of execution contexts. It is possible to have the `async2` machinery behave in the same way as `async` here, but doing so is a potential perf hit, of ~30% or more of the gain in some cases. These scenarios will still be faster than the existing `async` mechanisms, but if we can take a breaking change here, we can make the performance gains with `async2` even greater. There's some argument that the current behavior of these methods is actually very unexpected, but even if that's the case, it's a potential break. One idea that was floated repeatedly was making this configurable; we could opt for behavior-preserving semantics by default, and let users opt-in to the breaking change if they chose to do so. Another point we considered is how to trigger the new generation strategy. The current prototype adjusted the compiler to simply put `async2` into the signature of the method; we don't think this is something we want to do long term. In fact, we think that specific syntax is likely the wrong solution to triggering the new generation strategy. We like our current `async` syntax, and think that it should continue to be the main syntax for the future. The goal with this change is that there's little-to-no semantic change in the meaning of `async` methods, and is instead just a code generation strategy change. This isn't something we want users to have to opt-in to, it should just be an improvement that they get as they move forward with the platform. There's also some concern about users who multi-target between various platforms; would users who target both .NET Standard 2.0 and modern .NET need to `#ifdef` their `async` methods so that they can take advantage of new features where they're available? All of this leads us to consider a very rarely used strategy; a configuration flag. We have very few of these in C#, intentionally, as we don't want to create language dialects. The good thing for us here is that we're not actually creating a language _dialect_; the emit strategy may be different, but the code inside the `async` method body means the same thing, semantically, whether the flag is on or off. There will be a cost to pay in compiler testing; we will forever have to test both `async` code generation strategies, which adds a _lot_ of new tests to the test matrix. But it seems like a cost we'd be willing to pay for the improvements we're looking at here. Further, we don't think we have to concern most users with the existence of a switch here. Instead, the compiler can simply look for a `RuntimeFeature` flag, and turn on the new strategy if it's available. The switch could be limited to simply being an escape hatch for users who need to switch back to the old generation strategy for some reason. The only wrinkle with this strategy is the potential `SyncContext` behavior change. This isn't something that really reflects in C# code itself, or in the code generated by the compiler at all. Instead, it affects the code that the JIT generates for the `async` method at runtime, as it converts the body to the `async` state machine. We therefore think it would be possible to leave this behavior change to a configuration option at runtime, either through some attribute on the assembly, or through runtime flags. Another topic we discussed briefly was `ConfigureAwait`. This issue may end up being the tipping point that forces an assembly-wide configuration solution, as we don't want to force developers to realize the `Task` return by calling `ConfigureAwait`. The `async2` mechanism avoids realizing `Tasks` when not necessary; this is where most of the performance gain comes from, as most `async` callstacks actually have very few true suspension points. If we have `ConfigureAwait` calls throughout the stack, it could force us to materialize the `Task`s where we otherwise wouldn't have to, costing a large part of the performance gains. It's possible that we could have the JIT recognize the scenario and elide the `Task` allocation, but that may end up being somewhat expensive to implement, so we will need to do the exercise of costing to determine what we do there. Finally, there were a few other topics mentioned: * While we're changing the async state machines, perhaps we could consider exposing a structured concurrency model, similar to F#'s `async` or `task` computation expression, or Kotlin's `CoroutineContext`. This would require new APIs from the runtime, and potentially change the way that asynchronous workflows are constructed, but if we're going to make such a change, perhaps now is the time. Moving to such a model may obviate the main use case of https://github.com/dotnet/csharplang/issues/6300 as well, as the cancellation could be implicitly checked during asynchronous calls by the runtime. * We also briefly mentioned debugging; in addition to the large changes in the runtime, debugging engines will also have to adapt to the new IL structure here. ================================================ FILE: meetings/2024/LDM-2024-04-08.md ================================================ # C# Language Design Meeting for Apr 8, 2024 ## Agenda - [Implementation-specific documentation](#implementation-specific-documentation) ## Quote of the day "if `true == 1` then why is that an error?" "cause u forgot parens" ## Discussion ### Implementation-specific documentation https://github.com/dotnet/csharplang/issues/7898 The proposal is for Roslyn to consistently specify behaviors that aren't covered by the [C# Standard](https://github.com/dotnet/csharpstandard/tree/standard-v7/standard#readme). There are various differences between what the C# Standard says and what the Roslyn C# compiler does. Some of these are places where the spec offers leeway - implementation-specific and implementation-defined. Others are deviations where the compiler doesn't follow the spec, e.g. because of a bug. Sometimes there are good reasons for such bugs not getting fixed; for instance there may be reasonable code depending on the current non-standard behavior, and it would be a breaking change to fix. The Standards committee is in the process of standardizing nullable reference types, which is quite unique in that much of its impact is through warnings, which often aren't considered the purview of the specification. The rules for when exactly the warnings are issued are quite intricate. How much of that behavior should be in the Standard? For the parts that wouldn't be, to what level of detail should the Roslyn behavior be specified elsewhere? Collection expressions are an example where the compiler needs wiggle-room around which exact types it creates and how it populates them, and reserves the right to change its choices without warning. The spec - and standard - should simply say what is guaranteed and what isn't. The Standard has an [annex](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/portability-issues) that lists undefined, implementation-defined and unspecified behaviors. Roslyn already has a [document](https://github.com/dotnet/roslyn/blob/main/docs/compilers/CSharp/Deviations%20from%20Standard.md) (although incomplete) listing known deviations from the Standard. We could expand that, or add sibling documents next to it, to include other Roslyn-specific behavior as suggested in the proposal. #### Conclusion For the issues in [section B3](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/portability-issues#b3-implementation-defined-behavior) of the annex, "Implementation defined behavior", we want to document Roslyn's approach, since these are gaps deliberately left by the Standard for the implementation to fill. In addition, with respect to [section B4](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/portability-issues#b4-unspecified-behavior), "Unspecified behavior", we want to specify the behavior of item 2 on the representation of the `true` value, since Roslyn has made decisions here that are observable and affect interoperability with other languages. ================================================ FILE: meetings/2024/LDM-2024-04-15.md ================================================ # C# Language Design Meeting for April 15, 2024 ## Agenda - [Non-enumerable collection types](#non-enumerable-collection-types) - [Interceptors](#interceptors) - [Relax `Add` requirement for collection expression conversions to types implementing `IEnumerable`](#relax-add-requirement-for-collection-expression-conversions-to-types-implementing-ienumerable) ## Non-enumerable collection types - Championed issue: [#7744](https://github.com/dotnet/csharplang/issues/7744) - Requested details: [#7895](https://github.com/dotnet/csharplang/pull/7895) Should collections that are not enumerable be usable in collection expressions and `params` collections, if they have a `CollectionBuilder` attribute that defines a single `Create` method? In this case, the element type of the collection can be determined from the `Create` method if it is not already known. Changes to the spec had been previously discussed and the details changes were reviewed, as shown in #7895 and #7744. This discussion was to confirm approval of the spec change. ### Conclusion These proposed changes were approved. ## Interceptors - [Open issues for interceptors](https://github.com/dotnet/csharplang/blob/ff2c82c8d702d70e2704cd9265c97859cc2eb296/proposals/interceptors-issues-2024-01.md) ### File location Interceptors must identify the location in code where interception occurs. The issue is how to identify that location for use in the interceptor attribute. Things that have been tried or considered for identifying the file: * Absolute paths, per the preview, are bad for portability. * Relative paths required the context be passed around or a project base path. The proposed arguments to `[InterceptsLocation]` are a version number and an opaque data string. The opaque data string is not intended to be human readable. It is expected that this string will be inserted by a generator that retrieves it via a call into the interceptors API that requests an interceptible location. The proposal is to create the opaque string by combining a checksum of the file contents, an integer location of the call in the syntax and a string file name for error reporting. Hand authoring interceptor locations is an unsupported scenario. A version identifier is included to allow future change to be made in the calculation of the data string. ### Other public API changes The team also mentioned that there is an experimental public API that lets you determine whether syntax is being intercepted and who is intercepting. This might be used in an analyzer, for example. ### Conclusion The proposed approach to identifying the file location was approved, including the proposed handling of version. ## Relax `Add` requirement for collection expression conversions to types implementing `IEnumerable` [#8034](https://github.com/dotnet/csharplang/issues/8034) We introduced a breaking change in 17.10 which had a larger impact than anticipated. The proposal is to resolve that breaking change. It occurs when: * Types that implement `IEnumerable` but not `IEnumerable` and have a strongly-typed `Add(T)`. * Types that implement `IEnumerable` and have a strongly-typed `Add(U)` where `U` is implicitly convertible to `T`. This is a generic form of category 1. * Types that implement `IEnumerable` and have a strongly-typed `Add(U)` where `U` and `T` are unrelated. The breaking change added in 17.10 was the additional restriction: * `Add` needs to have one parameter of the iterator type The proposal is to keep the `Add` method, still require a single `Add` parameter but no statement about the type of the argument. ### Conclusion No conclusion reached and this will be discussed again in the April 17 meeting. ================================================ FILE: meetings/2024/LDM-2024-04-17.md ================================================ # C# Language Design Meeting for April 17, 2024 ## Agenda * [Relax `Add` requirement for collection expression conversions to types implementing `IEnumerable`](#relax-add-requirement-for-collection-expression-conversions-to-types-implementing-ienumerable) * [Extensions](#extensions) ## Relax `Add` requirement for collection expression conversions to types implementing `IEnumerable` [#8034](https://github.com/dotnet/csharplang/issues/8034) ### Discussion This was a continuation of Monday's conversation on the conversion rules for collection expressions. These rules were tightened at LDM-2024-01-10 to require target types that implement `IEnumerable`, and that do not have create method, to have an accessible `Add` instance or extension method that can be invoked with value of iteration type as the argument. That is a breaking change for collection types where the `Add` method has a parameter type that is implicitly convertible but not identical to the iteration type. The proposal was to relax the requirement (for conversion) and simply require an instance or extension `Add` method that can be invoked with a single argument, but without requirements on the method parameter type. Later method resolution will reject any `Add` methods that will not work with the type. ### Conclusion The LDM ratified the proposal that was already checked in, which rolls back some of the changes from January and better aligns the three features: collection expressions, collection initializers, and `params` collections. ## Extensions ### Discussion Today we looked at [the syntax for extension types](https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/roles/roles-2023-02-15.md#what-keyword-for-underlying-type). This is the information that the syntax needs to be able to express: 1. It's an extension 2. Implicit vs explicit concept 3. It has an underlying type 4. (Later: base extensions and interfaces) The current interim syntax implementation is: ```csharp implicit extension Extension for UnderlyingType : BaseExtension, Interface { } ``` Proposed syntax alternatives for the declaration of type: - (implicit | explicit) extension - (role | extension) - view - shape - alias - this (as in this E of U) - ?? - Proposed alternatives for the keyword options for underlying type: - for - of - extends - on - over - is - override - using - from - parentheses or other glyph - after the colon with base extensions and interfaces Several other syntax ideas were also presented in the meeting. ### Other thoughts #### Extension methods vs. extension type members We have an open question around how lookup works when there are both implicit extension members and old-style extension methods in the mix. We need to make sure our decisions don't handcuff the BCL or others from adopting the feature. Traditional extension methods could infer type arguments from all arguments, but extension types can only use the receiver to determine compatible extension types and substitute them. ### Priorities Priorities: static -> instance -> inheritance -> interfaces ### Questions that were tabled for now: * Can `implicit` or `explicit` be omitted? What's the default? * What's the naming convention ### Conclusion We will move forward with the current syntax: ```csharp implicit extension Extension for UnderlyingType : BaseExtension, Interface { } ``` ================================================ FILE: meetings/2024/LDM-2024-04-22.md ================================================ # C# Language Design Meeting for Apr 22, 2024 ## Agenda - [Effect of language version on overload resolution in presence of `params` collections](#effect-of-language-version-on-overload-resolution-in-presence-of-params-collections) - [Partial type inference: '_' in method and object creation type argument lists](#partial-type-inference-_-in-method-and-object-creation-type-argument-lists) ## Effect of language version on overload resolution in presence of `params` collections (https://github.com/dotnet/csharplang/issues/8061) Issues were encountered when earlier C# versions were specified. For example, the runtime team added `params ReadOnlySpan` and users that combined .NET 9 and C# 12, such as with the `.csproj` fragment that follows, received an error: ```dotnetcli 12 net9.0 ``` > The feature 'params collections' is currently in Preview and unsupported. To use Preview features, use the 'preview' language version. This happens because the changes we made to overload resolution to prefer params `ReadOnlySpan` are unconditional but using the feature is guarded by `LangVersion`. Looking at how we have handled guarding other features on `LangVersion` finds variations where different features have been handled differently and features have been handled differently at different points in C# history. A primary reason for `LangVersion` was included in Roslyn is to support teams where some members are on a later version of the compiler - for example because they are using a later version of Visual Studio. For at least much of the Roslyn history, if using a feature would cause an error if used with an earlier compiler (SDK/VS version), then consumption was not supported. Otherwise it was. Available options for handling overload resolution in this case are given below. #### Ignore language version on consumption There are concerns on ignoring language version on consumption because of how much it is tied to collection expressions. We expect the compiler would inject C# 11+ code patterns into C# 10 (or early) code bases which would not be expecting collection expressions. #### Guard applicable function member changes on LangVersion This approach would result in the new rules only applying to C# 13 and above. _Applicable function member_ refers to expanding `params` at the callsite to act as a collection expression. You could still explicitly call the method, but it would not be chosen during overload resolution for `params`. #### Guard all overload changes on LangVersion This is a variation of the previous option. ### Breaking Changes While selecting a different overload is a breaking change, we sometimes take this kind of change. If code starts working in the C# 13 compiler's version of C# 11, that can create an issue in teams. And also, if the selected overload changes when you do not change your compiler, but upgrade your language version and call a different overload, that's a break. Additional language version guards in the compiler add complexity and increasing the test matrix. But not applying the guard means that if a user upgrades their compiler they could see different overload resolution, even if they continue to use the same lower version of C#. Since we have said we will evolve the behavior of collection expressions to make them better, you could get a change in code (although not a semantic change) every year. ### Conclusion We will guard based on language version - approach [Guard applicable function member changes on LangVersion](#guard-applicable-function-member-changes-on-langversion). ## Partial type inference: '_' in method and object creation type argument lists (https://github.com/dotnet/csharplang/pull/7582) ### Discussion We reviewed a proposal for skipping inferrable type arguments in the type argument list for invocation expressions and object creation expressions. In addition to type arguments, there are other potential uses (more in the proposal): ```csharp // This code is to show the possible application // and is not an indication of syntax. G<_,> _[] // arrays _? // nullable types ``` ### Possible syntax ```csharp - `_` (underscore) - `var` - `` (empty) - `..` - `*` - `__` ``` ### Conclusion We will continue to explore top level type inference. It is too early to make a decision on syntax, but we lean to using `var` as the most semantically correct. To preserve this space, we may begin disallowing `_` as a type name. If you wish to use it as a type name, you would need to prefix: `@_`. ================================================ FILE: meetings/2024/LDM-2024-04-24.md ================================================ # C# Language Design Meeting for Apr 24, 2024 ## Agenda - [Adjust dynamic binding rules for a situation of a single applicable candidate](#adjust-dynamic-binding-rules-for-a-situation-of-a-single-applicable-candidate) ## Quotes of the day "Once people are in dynamic-land they've already chosen" ## Adjust dynamic binding rules for a situation of a single applicable candidate (https://github.com/dotnet/csharplang/pull/8027) The params collection work includes some changes to how binding occurs if either the primary expression or an argument is dynamic. When there is only one candidate, it could be used. This change caused [unexpected breaking changes in certain cases](https://github.com/dotnet/roslyn/issues/72750): > ```csharp > using System.Text.Json; > > public class C > { > public static C M(IFoo foo, dynamic value)m > { > var result = foo.Bar("name", value); > return JsonSerializer.Deserialize(result); > } > } > > public interface IFoo > { > object Bar(string name, object value); > } > ``` > > After the recent changes around dynamic, the type of result is now object whereas previously it was dynamic. ### Discussion It is desirable to use use static binding to make more things possible with dynamics. This can be done when there is only one applicable candidate. #### Dynamic or static result We do not plan to change things that work today, such as local functions. We are only considering whether new cases should have a static result or be "dynamified" There were concerns about returning static results from operations with dynamic being surprising. Constructors do this, but that is because you are stating the return type explicitly in the call to `new`. There are some some opinions voiced that we should avoid to avoid unnecessary dynamic proliferation (although what is unnecessary may be opinion). It was pointed out that part of the core of the design of dynamic that it is contagious. There is worry about needing a decoder ring to know when the results are static or dynamic. General feeling that consistency along with back compatibility considered is desirable. Prior art: VB has had runtime dynamic since V1. `Object` in VB is dynamic. It delays resolution to runtime. When there is only a single candidate that is applicable it would do static binding and not change the result to `Object'. It does not proliferate dynamic. VB might also be considered a lesson learned because of ambiguity between dynamic and the base type. The dual role of object may drive VB being static when possible, dynamic when necessary. Whether we should use static or dynamic result types when we use static binding with dynamic arguments in new scenarios was not resolved today. We will return to this question. #### Static binding can uncover more errors during compilation There are cases where static binding allows us to know that certain cases, like assigning to void, would fail at runtime. We can now give a compile time error for these cases and it would be desirable to do so. #### Scenarios from proposal These scenarios from the proposal framed the discussion and conclusions: > 1. the candidate is not a local function; > 2. the candidate returns a value (doesn't have type `void`, doesn't return a `ref`); > 3. there is an implicit conversion from result type to `dynamic`; > 4. the receiver and argument list would be supported for dynamically bound invocation. - Bullet 1 is an existing scenario that is enshrined. - Bullet 2 and 3 are moving errors from runtime to compile time. - Bullet 4 is under discussion because success scenarios have non-dynamic results that we may choose to "dynamify". ## Conclusions We can use static binding as an implementation strategy when dynamic binding isn't supported. (From 30,000 feet - taking something that would have always failed, and making it work.) We can change runtime error scenarios to compile-time error scenarios. We should tie these changes to language version. ================================================ FILE: meetings/2024/LDM-2024-05-01.md ================================================ # C# Language Design Meeting for May 1st, 2024 ## Agenda - [Adjust binding rules in the presence of a single candidate](#adjust-binding-rules-in-the-presence-of-a-single-candidate) ## Quote of the Day - "I'm going to release you all early. I know your parents aren't here to pick you up yet." "So we should all go to the playground and stay there?" ... "Or if we're older we go to the library." ## Discussion ### Adjust binding rules in the presence of a single candidate https://github.com/dotnet/csharplang/pull/8027 [Last time](LDM-2024-04-24.md#adjust-dynamic-binding-rules-for-a-situation-of-a-single-applicable-candidate), we looked at an adjustment to the rules of dynamic binding that would see the compiler issue more errors for cases where `dynamic` operations could be resolved as never possible to work at compile-time. The new proposal suggests that, for assignments, we can statically bind during compile and issue errors for scenarios where no valid assignment is possible. For example, we'd error for this scenario: ```cs var c2 = new C2(); c2.M(0, 1); public class C2 { public void M(dynamic d, object o) { this[d] += o; // Would error, += cannot be applied to operands of type Base and object } Base this[int x] { get => new Base(); set {} } } class Base { } ``` It was hoped that this narrow restriction, only affecting assignments and not affecting invocations, would be small enough to provide meaningful compile-time errors while avoiding runtime breaks. However, we were able to come up with several counterexamples for this scenario. One such example: ```cs // Succeeds and prints 1 var c2 = new C2(); c2.M(0, 1); public class C2 { public void M(dynamic d, object o) { this[d] += o; // Succeeds today because `Derived.operator +` is found at runtime by dynamic } Base this[int x] { get => new Derived(); set {} } } class Base { } class Derived { public static Derived operator+(Derived x, object y) { System.Console.Write(1); return x; } } ``` The proposed rules would break this scenario, because `Base` has no `+` operator; at runtime, though, the dynamic binder would see that the return of `this[d]` is a `Derived`, and find the operator it defines. We're also concerned about interactions with `IDynamicMetaObjectProvider`, as those can also break in similar fashions with this scheme. Given these issues, we don't think that we're comfortable with even this narrow set of errors. Ultimately, use of `dynamic` in these scenarios intentionally moves errors from compile-time to runtime, and we don't think that the potential for breaking existing code is worth the additional validation here. We'll shelve this change for now, and revisit in the future if we have a better idea for how to avoid potential breaks. #### Conclusion Proposal is rejected. ================================================ FILE: meetings/2024/LDM-2024-05-08.md ================================================ # C# Language Design Meeting for May 8th, 2024 ## Agenda - [`readonly` for primary constructor parameters](#readonly-for-primary-constructor-parameters) ## Quote of the Day - "It'll work" "That doesn't sound like a ringing endorsement." ## Discussion ### `readonly` for primary constructor parameters **Champion Issue**: https://github.com/dotnet/csharplang/issues/8105 **Related Issue**: https://github.com/dotnet/csharplang/issues/188 **Proposal Link**: https://github.com/dotnet/csharplang/blob/db6cac459463fdcb53e87eb6f2f41ca534e44b43/proposals/readonly-parameters.md Today, we took a look at whether we should constrain the [existing `readonly` parameter proposal](https://github.com/dotnet/csharplang/blob/db6cac459463fdcb53e87eb6f2f41ca534e44b43/proposals/readonly-parameters.md) to just be for primary constructor parameters. Notably, we're not looking whether we should do `readonly` in general; we're only looking at whether we want to do `readonly` for primary constructor parameters separately and first. The full proposal takes the view that primary constructor parameters should be treated as parameters fully and completely, so marking one `readonly` would mean that no assignment of the parameter is possible (outside `unsafe` code), even in locations that can modify instance `readonly` members like instance member initializers or constructor bodies. Given this, and a general lack of belief that primary constructor parameters should be mutable in those locations, makes us wonder why we'd break `readonly` apart into multiple stages here. We did also discuss other approaches to the problem that some users are facing here; mainly, is there an analyzer approach that can work such that users can effectively get `readonly` by default, and then be able to opt into mutability? We think there is, and the idea is worth exploring, but it's important to note that when we've previously added `readonly` to new locations, it's been driven by codegen needs. The ability to mark `struct` members as `readonly`, for example, allowed the compiler to avoid `dup` instructions where it would otherwise need to make defensive data copies. The same is true for `readonly` on `ref`s. There's no real codegen advantages to speak of here; given that we don't guarantee that primary constructor parameters are even captured as fields, there's no guarantee we could make that `readonly` will be applied to any instance members that are generated from primary constructor parameters marked `readonly`. It may be worth exploring the analyzer avenue further, though we do want to be very cautious about effectively shipping a language feature as an analyzer, rather than a core part of C#, especially one that would affect a core way the language feels. Given these sentiments, we feel comfortable rejecting the narrow version of this proposal. We are not rejecting the overall proposal; there is still plenty of debate to be had on whether `readonly` on parameters is a general thing we want to have in C#. But we are confident that we don't want to do the narrow version where it only applies to primary constructor parameters. #### Conclusion Narrow proposal is rejected. ================================================ FILE: meetings/2024/LDM-2024-05-13.md ================================================ # C# Language Design Meeting for May 13th, 2024 ## Agenda - First-class span types questions - Delegate conversions - Variant conversion existence - Overload resolution priority questions - Attribute shape and inheritance - Extension overload resolution ## Quote(s) of the Day - "That's my interpretation, and you can't check my math anyways." - "[Who's on first](https://www.youtube.com/watch?v=sShMA85pv8M) should be on the citizenship test" ## Discussion ### First-class span types questions https://github.com/dotnet/csharplang/issues/7905 #### Delegate conversions https://github.com/dotnet/csharplang/blob/4e88fd3f9cb305b467bdc9c4f54f691733093cf5/proposals/first-class-span-types.md#delegate-signature-matching First up, we considered whether we should try and replicate covariance in method group assignment conversions. On the one hand, the example code seems sensible enough, and would certainly work today with arrays. However, we don't have precedent for emitting thunks for delegate assignments. Further, while we could make the initial conversion work easily enough, we couldn't then make converting from `Func>` to `Func>` transparent, like converting from `Func` to `Func` is seamless and preserves reference identity. Looking at the BCL use cases for this, we don't see anything that is planning to take advantage of this type of method-group variance; given these caveats, we have decided to not support variance in method-group assignment here. ##### Conclusion Variance in delegate assignment is not supported. #### Variant conversion existence We next considered whether we should support covariance in `ReadOnlySpan` when the helper method, `ReadOnlySpan.CastUp`, is not present. Supporting this down-level could be potentially quite complicated. We considered a few different approaches: 1. We could consider the conversion to only exist when `CastUp` exists. This ensures that if anyone depends on the behavior, it must be present, but it causes overload resolution to be unstable within a single language version, and increases the decoder ring of what a user would need to know to understand why a particular overload was or wasn't picked. It avoids breaking changes when a user turns on C# 13 and doesn't upgrade to .NET 9, but the complexity tradeoff seems like it will not be worth it. 2. We could consider the conversion to always exist, but issue an error when we cannot find the required `CastUp` method. This is fairly in-line with what we do for other language features that require specific features, but we're concerned about the breaking change potential here. When a user upgrades to C# 13, these new conversions may cause overload resolution to pick a new member. That resolution could then cause an error because `CastUp` doesn't exist. 3. We could generate a `` method for converting the span. On platforms with built-in `ReadOnlySpan` support, and in particular .NET Core 2.2 and later, this seems like it would work fine. However, we're concerned about other platforms; users can bring their own `ReadOnlySpan` implementations, and there's no guarantee that whatever bit-blit strategy the compiler choses would be safe for that implementation. We also aren't prepared to make strong statements on whether this would always be safe on the .NET Framework implementation of `ReadOnlySpan`. 4. We thought about whether we could ship an update to `System.Memory`. However, this would be quite complicated; we'd actually need to ship a new package, perhaps `System.MemoryEx` or `System.Memory2`, otherwise code that compiled against .NET Standard 2.0 and depended on the new functionality might break when run on .NET 8. 5. We considered whether we should abandon variance entirely. After all, the main thrust of the proposal is not variance; that's a "nice to have" on top of the type inference and extension method features. However, we think it would be a shame to avoid a useful language feature because we may issue an error down-level; especially given that the feature is a corner case scenario and users that want to use newer C# features on down-level platforms have to contend with far more obvious missing things. Given all these options, we eventually settled on 2: when in C# 13+, the conversion will always exist, but the compiler will issue an error when it cannot find `CastUp`. Finally, we thought about whether extensions should be able to provide `ReadOnlySpan.CastUp`. That method is a static method on `ReadOnlySpan`, so it could theoretically be provided by extension types. However, we don't think we're ready to decide, in case or in general. We'll have to consider extension types more generally with compiler pattern matching; for today, we'll simply say it cannot be provided by an extension type, and revisit when we're ready to consider the question holistically across C#. ##### Conclusion In C# 13, the variance conversion will always exist, and the compiler will issue an error if it cannot find `ReadOnlySpan.CastUp`. ### Overload resolution priority questions https://github.com/dotnet/csharplang/issues/7706 #### Attribute shape and inheritance https://github.com/dotnet/csharplang/blob/4e88fd3f9cb305b467bdc9c4f54f691733093cf5/proposals/overload-resolution-priority.md#systemruntimecompilerservicesoverloadresolutionpriorityattribute First up in overload resolution priority, we considered the shape of the attribute. As part of this, we need to consider the inheritance behavior, to at least some degree, in order to decide what the `Inherited` attribute usage property should be. For this, we looked to prior art in C#: method types, `params`, parameter names, and default values. Unfortunately, there's no singular consistent rule here, but we do have the principles to guide us; most things look at the least-derived members. For parameter names and default values, however, we look at the most-derived, which keeps us consistent with the semantics VB already had for these scenarios. We don't think that there's reason to use that most-derived logic here; if anything, we think that would run counter to the proposal itself, which is designed to only be used within a single type. Allowing overrides to change the priority of a member would defeat this; derived types can always hide the member and create their own version if they wish to adjust priority, as they can today. Given this, `Inherited` should be false, as we don't want it to show up in reflection on overriding members when it's not permitted in source. ##### Conclusion We are satisfied with the proposed shape of the attribute. Overload resolution will always look at the least-derived member definition for its priority. We still have open questions on whether we should hard-block the attribute application on overrides, and what we will do for implementations of interface members that have a priority. #### Extension overload resolution https://github.com/dotnet/csharplang/blob/4e88fd3f9cb305b467bdc9c4f54f691733093cf5/proposals/overload-resolution-priority.md#overload-resolution-priority-1 Finally today, we considered the question of whether overload resolution should behave identically for instance and extension lookups, or if we should not group extension methods by type before doing overload resolution priority sorting. We don't have any current use-cases for inter-extension priority, and we also think that not grouping would again run against the "this is a tool within a type only" principle from the proposal. ##### Conclusion We will always group by declaring type before removing lower priority overloads. The given example will print `"Ext2 ReadOnlySpan"`. ================================================ FILE: meetings/2024/LDM-2024-05-15-KeyValuePairCorrespondence.md ================================================ # KeyValuePair correspondence In .NET, dictionary types and the `KeyValuePair` (aka KVP, or KeyValuePair) types are intertwined. A dictionary is commonly defined as a collection of elements of that KVP type, not just a mapping from some `TKey` to some `TValue`. Indeed, this duality allows one to treat the two spaces mostly uniformly. For example: ```c# var dictionary = new Dictionary(); var collection = (ICollection>)dictionary; collection.Add(new KeyValuePair("mads", 21)); ``` What is special about dictionaries, over standard element-based collection expressions, is that the dictionary types have a general view that any particular key will only be contained once, and can be used to then more efficiently map to its associated value over doing a linear scan. Put more intuitively: A "dictionary type" is a "collection type" whose "element type" is some `KeyValuePair` and which has an available `V this[K key] { get; }` indexer. Because of this correspondence, we believe that dictionary expressions should not be considered very special and distinct from existing collection expressions. Rather, the "dictionary expression" language feature is actually a feature that allows KeyValuePairs to be naturally expressed within collection expressions, along with a sensible and uniform set of rules to allow KeyValuePairs to naturally initialize collection types. This "natural expression" happens both syntactically and semantically. Specifically: 1. There is a new special syntax for declaring a KeyValuePair within a collection expression: ```c# X x = [k: v]; ``` 1. It can be used with dictionary types: ```c# Dictionary nameToAge = ["mads": 21]; ``` 1. And also with existing collection types: ```c# List> pairs = ["mads": 21]; ``` 1. And, while the syntax allows for easy specification of the particular key and value, usage of that syntax is optional. Semantically, the feature works equally with normal KeyValuePair instances: ```c# KeyValuePair kvp = new("mads", 21); Dictionary nameToAge = [kvp]; ``` 1. The above allows for *uniformity* of processing KeyValuePair values, which we consider desirable so that users can expect them to work for all collection expressions elements: ```c# // Both 'spread' elements and 'expression' elements that evaluate // to KeyValuePair values work with dictionary types Dictionary nameToAge = [.. defaultValues, otherMap.Single(predicate)]; ``` Here, being able to 'spread' in another collection (which would normally be some `IEnumerable>`) is desirable. Similarly, being able to add individual pairs found through some means, without having to decompose into `k: v` syntax, is equally preferable. ## KeyValuePair transparency The existing "Collection Expression" feature has a guiding principle that elements and spreads can be thought of as being lowered to `Add` calls. This enables things to be included or spread into the final collection that have a more specific type than the collection's element type itself. For example: ```c# // The collection expression can be comprised of `int` values // despite the element type being `int?`. List ages = [18, .. Enumerable.Range(21, 10)]; ``` This allowance is implied by the lowered representation, where implicit conversions enable a straightforward scenario to appear equally straightforward in code without onerous explicit casts: ```c# var ages = new List(); ages.Add(18); foreach (var value in Enumerable.Range(21, 10)) ages.Add(value); ``` Dictionary expressions have a corollary. Both the key and the value can be more specific types than the key and value types of the dictionary being built, when lowered in the same manner: ```c# var map1 = new Dictionary(); map1["mads"] = 21; // Etc ``` To achieve this principle in dictionary expressions, we expect the exact type of the KeyValuePair values to be generally transparent. Rather than being strictly that type, the language will generally see *through* it to be a pair of some `TKey` and `TValue` types. This transparency is in line with how tuples behave and serves as a strong intuition for how we want users to intuit KeyValuePairs in the context of collection expressions. How does this transparency manifest? Consider the following scenario: ```c# Dictionary map1 = ["mads": 21]; ``` The above expression would certainly be expected to work. While `"mads"` is a string, and `21` an `int`, the target-typed nature of collection expressions would push the `object` and `int?` types through the constituent key and value expressions to type them properly. We would *not* disallow this, despite `KeyValuePair` and `KeyValuePair` being incompatible. This would also be expected to work in the following case: ```c# Dictionary map2 = [null: null]; ``` KeyValuePair transparency means that just as we expect the code for `map1` to be legal, we should consider the following legal as well: ```c# KeyValuePair kvp = new("mads", 21); Dictionary map1 = [kvp]; ``` After all, why would that be illegal, while the following became legal? ```c# KeyValuePair kvp = new("mads", 21); Dictionary map1 = [kvp.Key: kvp.Value]; ``` Requiring explicit deconstruction of the constituent key and value portions of a KVP, just to satisfy the compiler so it could target-type them, adds extra, painful steps. It would become doubly worse once all collection element expressions are considered. We would like users to be able to write: ```c# Dictionary map = [.. nameToAge, otherMap.Single(predicate)]; // Not: var singleElement = otherMap.Single(predicate); Dictionary map = [.. nameToAge.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value), singleElement.Key: singleElement.Value]; ``` ## Tuple analogy It turns out that this sort of behavior is *exactly* what already exists in the language today for tuples. Consider the following: ```c# List<(object? key, int? value)> map = [("mads", 21)]; ``` This already works today. The language transparently sees through into the tuple expression to ensure that the above is legal. This is also not a conversion applied to some `(string, int)` tuple type. That can be seen here which is also legal: ```c# List<(object? key, int? value)> map = [(null, null)]; ``` Here, the types of the destination flow all the way through (including recursively through nested tuple types) into the tuple expression in the initializer. This transparency is not limited to *tuple expressions* either. All of the following are legal as well, despite non-matching *ValueTuple types*: ```c# (string x, int y) kvp = ("mads", 21); // (string, int) and (object?, int?) are not compatible at the runtime // level. The language enables this at the C# level. List<(object? key, int? value)> map = [kvp]; ``` And ```c# (string? x, int? y) kvp = (null, null); List<(object? key, int? value)> map = [kvp]; ``` The language always permissively views tuples as a loose aggregation of constituent elements, each with their own type. Conversions and compatibility are all performed on those constituent element types, not on the top level `ValueTuple<>` type which would normally not be compatible based on .NET type system rules. ## KeyValuePair inference The tuple analogy above serves as an analogous system we can look to in order to see how we would like KeyValuePair to behave in collection expressions. For example: ```c# void M(List<(TKey key, TValue value)> list1, List<(TKey key, TValue value)> list2); // Note: neither tuple1 nor tuple2 are assignable/implicitly convertible // to each other. Each has an element that has a wider type than the // corresponding element in the other. (string x, int? y) tuple1 = ("mads", 21); (object x, int y) tuple2 = ("cyrus", 22); // Infers `M` M([tuple1], [tuple2]); ``` This works today and correctly infers `M`. Given the above, we would then desire the following to work: ```c# void M(Dictionary d1, Dictionary d2); // Note: neither kvp1 nor kvp2 would ever be assignable/implicitly convertible to each other. KeyValuePair kvp1 = new("mads", 21); KeyValuePair kvp2 = new("cyrus", 22); // Would like this to infer `M` as well. M([kvp1], [kvp2]); ``` ## Tuple analogy (cont.) The analogous tuple behavior serves as a good *bedrock* for our intuitions on what we want for KeyValuePairs. However, how far we want to take this analogy is up to us, and we can consider several levels of increasing transparency support. Those levels are: 1. No transparency support. Do not treat KVPs like tuples. Force users to explicitly convert between KVP types to satisfy type safety at the KVP level itself. For example: ```c# KeyValuePair kvp = new("mads", 21); Dictionary map1 = [kvp]; // illegal. user must write: Dictionary map1 = [kvp.Key: kvp.Value]; Dictionary map1 = [.. nameToAge, otherMap.Single(predicate)]; // illegal. user must write: var temp = otherMap.Single(predicate); Dictionary map1 = [.. nameToAge.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value)), temp.Key: temp.Value]; ``` 1. Transparent only when targeting some dictionary type, but not non-dictionary types: ```c# KeyValuePair kvp = new("mads", 21); Dictionary map1 = [kvp]; // legal. List> map1 = [kvp]; // not legal. User must write: List> map1 = [kvp.Key: kvp.Value]; // or List> map1 = [new KeyValuePair(kvp.Key, kvp.Value)]; ``` 1. Transparent in any collection expression, but no further: ```c# KeyValuePair kvp = new("mads", 21); Dictionary map1 = [kvp]; // legal. List> map1 = [kvp]; // legal. KeyValuePair kvp2 = kvp1; // not legal. User must write: KeyValuePair kvp2 = new KeyValuePair(kvp1.Key, kvp.Value); ``` 1. Transparent everywhere: ```c# KeyValuePair kvp = new("mads", 21); Dictionary map1 = [kvp]; // legal. List> map1 = [kvp]; // legal. KeyValuePair kvp2 = kvp1; // legal. ``` These four options form a spectrum, starting with doing nothing special, then only handling dictionaries, then handling any collection, all the way to the maximum support which effectively puts KeyValuePair handling at the same level as tuples for the language. Open question 1: How far would we like to take this transparency? All the way to full analogy with tuples? No transparency at all? Somewhere in the middle? ## Deconstruction All of the above so far has been about how the language would enable working more conveniently with the KeyValuePair type. And, there are good arguments to be made that KeyValuePair needs to allow these important scenarios to light up, due to how integral it is to the dictionary-type space to begin with. However, fundamentally, all of the above could be reformulated, enabling the same scenarios without specializing KeyValuePair at all. Specifically, all of the above works by stating that KeyValuePair can be seen transparently as a pair of two typed values (the `TKey Key` and the `TValue Value`). Fundamentally, as that's all that is truly required, a relaxation could be performed that restates all of the above as: > Any type that is *constructible* and *deconstructible* into two elements would be transparently supported in the context of collection expressions and the `k: v` element. That relaxation would consume all the KeyValuePair support. But would also then enable tuples to be used in all those cases *as well as* any appropriate type supporting two-element construction/deconstruction. As such, all of the below would be legal: ```c# Dictionary nameToAge1 = [("mads", 21)]; List<(string, int)> pairs = ...; Dictionary nameToAge2 = [.. pairs]; record struct NameAndAge(string Name, int Age); Dictionary nameToAge3 = [nameToAge1, nameToAge2]; List pairs = ["mads": 21, "cyrus": 22, "joseph": 23]; // etc. ``` Open question 2: How far would we like to take this? 1. Only support KeyValuePair. 2-element tuples and other 2-element deconstructible types have no special meaning in a collection expression. ```c# Dictionary nameToAge = [kvp]; // legal Dictionary nameToAge = [("mads", 21)]; // not legal record NameAndAge(string Name, int Age); NameAndAge nameAndAge = new("mads", 21); Dictionary nameToAge = [nameAndAge] // not legal ``` 1. Support KeyValuePair and 2-element tuples, but not other 2-element deconstructible types. ```c# Dictionary nameToAge = [kvp]; // legal Dictionary nameToAge = [("mads", 21)]; // now legal! record NameAndAge(string Name, int Age); NameAndAge nameAndAge = new("mads", 21); Dictionary nameToAge = [nameAndAge] // not legal ``` 1. Support any 2-element deconstructible types? ```c# Dictionary nameToAge = [kvp]; // legal Dictionary nameToAge = [("mads", 21)]; // legal record NameAndAge(string Name, int Age); NameAndAge nameAndAge = new("mads", 21); Dictionary nameToAge = [nameAndAge] // now legal! ``` ================================================ FILE: meetings/2024/LDM-2024-05-15.md ================================================ # C# Language Design Meeting for May 15th, 2024 ## Agenda - [`field` and `value` as contextual keywords](#field-and-value-as-contextual-keywords) - [Usage in `nameof`](#usage-in-nameof) - [Should `value` be a keyword in a property or indexer get? Should `field` be a keyword in an indexer?](#should-value-be-a-keyword-in-a-property-or-indexer-get-should-field-be-a-keyword-in-an-indexer) - [Should `field` and `value` be considered keywords in lambdas and local functions within property accessors?](#should-field-and-value-be-considered-keywords-in-lambdas-and-local-functions-within-property-accessors) - [Should `field` and `value` be keywords in property or accessor signatures? What about `nameof` in those spaces?](#should-field-and-value-be-keywords-in-property-or-accessor-signatures-what-about-nameof-in-those-spaces) - [Dictionary expressions](#dictionary-expressions) ## Quote of the Day - "What the lowering implies here" _screen goes blank_ "Oh, you lowered too far" ## Discussion ### `field` and `value` as contextual keywords Proposal: https://github.com/dotnet/csharplang/issues/7964 Related: https://github.com/dotnet/csharplang/issues/8130, https://github.com/dotnet/csharplang/issues/140 We started today by looking at a number of open questions in the `field` and `value` as contextual keywords proposal. #### Usage in `nameof` First up today, we considered whether `field` and `value` should be allowed in `nameof`. `nameof(value)` already works today, and there was no real pushback on keeping it working; it has an obvious value to return (no pun intended), and is an easy way to mitigate some of the scope of the breaking change. More controversial was `nameof(field)`. First, if we do allow it, what does it evaluate to? We have precedence with type aliases; `nameof(MyAliasForList)` will evaluate to the string `"MyAliasForList"`, not `"List"`, but there will no doubt be at least a few users who expect it to evaluate to the actual IL name of the backing field. We're also a bit unsure about what the use case for `nameof(field)` would be in this case; it wouldn't be used for argument validation, since there is no argument. It also wouldn't be used in something like `MemberNotNull`, since it isn't accessible outside the property. We did discuss whether we should simply allow it to make `field` and `value` consistent, but given the lack of motivating scenarios, we feel that it would be better to start with a tighter restriction we can loosen later if we hear feedback. ##### Conclusion `nameof(value)` is legal, and will evaluate to `"value"`. `nameof(field)` will not be legal. #### Should `value` be a keyword in a property or indexer get? Should `field` be a keyword in an indexer? The question at the heart of this is "should these be keywords where they will never work"? We quickly and unanimously said "no", even before finding an [example](https://github.com/dotnet/runtime/blob/19467dccf4e6786296eecd8007f90e6dafe01818/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XAttribute.cs#L158-L172) of where such a change would be unnecessarily breaking. ##### Conclusion `value` will not be a keyword in property or indexer `get`s. `field` will not be a keyword in indexers. #### Should `field` and `value` be considered keywords in lambdas and local functions within property accessors? We have prior art here, both in `await` inside an `async` function, and in LINQ query syntax within a nested lambda. In both of those cases, the context isn't further in interpreted; it's inside an `async` method or a LINQ query, so the keywords exist. We think that these keywords should behave exactly the same way; that both simplifies the language, and also feels like the logical conclusion. ##### Conclusion Yes, `field` and `value` will be contextual keywords within nested contexts when they exist. #### Should `field` and `value` be keywords in property or accessor signatures? What about `nameof` in those spaces? We split this question into two parts: First, the property signature itself. Conceptually `field` could be in scope here, but `value` definitely wouldn't be; it isn't today, and we don't think it should be tomorrow. For `field` in this position, we don't see a reason to enable it, so it also won't be visible in the property declaration. Second, inside accessor declarations, we have prior art of what we allow with `value` today. As an example: ```cs public class C(string s) : Attribute { public int P { get; [param: C(nameof(value))] set; } } ``` This is existing, legal C# code, where `value` is permitted in the attribute list of a property setter. Further complicating our existing rules is this example: ```cs class value(string type) : Attribute { value I { get; [param: value(nameof(value))] set; } } ``` In this case, the `value` type shadows the parameter name, including for the `nameof(value)`. This scenario is, we hope, non-existant; our search for types named `value` or `field` encountered nothing for the former, and only a test case in the mono/mono codebase for the latter. We therefore don't think we need to be concerned about breaking the behavior in this case; we therefore settled on `field` and `value` being contextual keywords in the signatures of accessors where they exist, including in attributes. ##### Conclusion `field` is a keyword in property `get` and `set` accessors. `value` is a keyword in property and indexer `set` accessors. Neither is a keyword in the main property or indexer signature. ### Dictionary expressions Proposal: https://github.com/dotnet/csharplang/issues/7822 Related: [KeyValuePair Correspondence](./LDM-2024-05-15-KeyValuePairCorrespondence.md) Today we looked at an outline of how `KeyValuePair` (`KVP`) corresponds to `Tuple`, and principles of how much we should let the latter shape our direction for the former. While we didn't get down to any hard decisions today, we did have a few comments during the overview to record: * This proposal for making `KVP` transparent doesn't address how recursive to make it. For example, `KVP>`. * Proposal 4, where `KVP` is fully transparent everywhere in the language, is a potential breaking change. * The meaning of adding a `KVP` changes when you consider target-typing a dictionary-like type vs a list-like type; the former will either overwrite or throw, and the latter will simply append. ================================================ FILE: meetings/2024/LDM-2024-06-03.md ================================================ # C# Language Design Meeting for June 3rd, 2024 ## Agenda - [Params collections and dynamic](#params-collections-and-dynamic) - [Allow ref and unsafe in iterators and async](#allow-ref-and-unsafe-in-iterators-and-async) ## Discussion ### Params collections and dynamic Issue: https://github.com/dotnet/roslyn/issues/73643 The issue describes an existing scenario where we started issuing warnings although we didn't use to. The scenario seems mainline: It's in Razor, and it combines the use of dynamic and params, both of which are common in the context. This seems an easy situation to get into. The warning is about one or more candidates not being able to be understood by the runtime binder, which will therefore ignore them. The warning means that new overloads, in this case params collection overloads, can "poison the pot", even for cases where the runtime binder could have still handled things just fine, only with a possibly different outcome than the compile-time binder. We do have some existing warnings of the same type, but they were grandfathered in. The problem here is that the combination of a new language feature that the runtime binder can't handle plus new overloads that use that feature cause *new* warnings in existing code. This seems like the first of potentially several situations like this going into the future. What should our general philosophy be going forward? If we are certain that something is not going to work out at runtime, we can give compile-time *errors*. But is there ever a case where a *warning* is the right thing for dynamic? After all, use of the feature in and of itself signals a willingness to risk things going awry at runtime. #### Conclusion In the particular case in question, the warning does not serve a useful purpose and is not worth the break in behavior that it causes. Furthermore, we feel in general that warnings are not useful with dynamic. They are a signal that something *might* not work, and with dynamic people are already bought in to that outcome. All else equal we should avoid adding such warnings in the future also. We are fine with compile-time errors when we're certain things don't work out at runtime. We're even fine adding *new* compile-time errors for *existing* scenarios that break at runtime, since people are already broken, and getting the error earlier is helpful. ### Allow ref and unsafe in iterators and async Proposal: https://github.com/dotnet/csharplang/blob/main/proposals/ref-unsafe-in-iterators-async.md The proposal loosens the restrictions on having ref variables and unsafe blocks in iterators and async methods. The reason for earlier restrictions is that ref variables and unsafe code need their state on the stack, and don't mix well with `yield` and `await` which need to squirrel away state in the heap. However, there is no problem with ref and unsafe being used *between* `yield`s and `await`s, as long as the ref or unsafe state does not need to be persisted across them. This is what the proposal allows. The feature is already implemented - this is a review of the speclet to verify that we remain in agreement with all the decisions. Some specific points of discussion: #### Lock statements in iterators Currently, `yield` statements are allowed inside `lock` statements in iterators. This keeps a lock while the iterator is suspended, which is highly dubious, and the proposal calls for a new warning on this pattern of code. Additionally, there is a new variant of the `lock` statement (https://github.com/dotnet/csharplang/blob/main/proposals/lock-object.md) specifically for the `System.Threading.Lock` type. Since this type is a ref struct, such a lock statement constitutes a use of a ref across a `yield` and is therefore an error. #### Breaking change because of Roslyn bug: Up until now, local functions inside unsafe async methods were erroneously allowed to have unsafe behavior, even though the top-level method body was safe due to other rules. The proposal calls for breaking this with an error going forward. Such an error is already issued for unsafe operations inside of lambda expressions - we just missed implementing it for local functions when we added those to the language. #### Conclusion We like this. Each design point seems well argued and balanced. We're provisionally ok with the breaking change on the local functions. We'll keep an eye on fallout and discuss if consequences turn out worse than we're able to anticipate. ================================================ FILE: meetings/2024/LDM-2024-06-10.md ================================================ # C# Language Design Meeting for June 10th, 2024 ## Agenda - [`ref struct`s implementing interfaces and in generics](#ref-structs-implementing-interfaces-and-in-generics) ## Quote of the Day - No particularly amusing quotes occurred today. Sorry. ## Discussion ### `ref struct`s implementing interfaces and in generics Champion Issue: https://github.com/dotnet/csharplang/issues/7608 Proposal: https://github.com/dotnet/csharplang/blob/f2db071d1ccd4f69edf9c3186e13c4331630fa7b/proposals/ref-struct-interfaces.md Today we went through the current version of the `ref struct` improvements specification to catch up LDM on all the implementation decisions that have been made by team during development, as well as any wrinkles that the core libraries team ran into while absorbing the feature. Most of the changes were approved without issue; * We confirmed that `allows` is the syntax we will go with for the feature, over `allow`. * We discussed the implications of default interface members not being supported on `ref struct`s; it's an unfortunate limitation, but we can't think of any way around it. The boxing of the receiver is done by the runtime, so we can't try and allow it for specific DIMs that don't violate rules. We also don't see much of a use case for a DIM that doesn't call an instance member on the type immediately anyways. * One of the marquee original intentions for `ref struct`s implementing interfaces was for `Span` and `ReadOnlySpan` to implement `IEnumerable`. This would help solve a number of betterness issues with adding new APIs, but because `IEnumerable.GetEnumerator()` returns an `IEnumerator`, we can't implement it in an allocation-free manner. That would cause any `IEnumerable` API to become a performance trap for `Span`, which is extremely undesirable. Looking further at this last point, we realize that this means that we don't have any major use-cases for `ref struct`s implementing `interface`s at present. The core libraries teams do not currently have plans to use the functionality; they do intend to make heavy use of `allows ref struct`, but this will mainly be on "transparent" types, such as `Action` or `Func`. The implementation of these types don't require specialization based on functionality, but instead serve as abstractions for user code that may want to abstract over `ref struct`s as well. Given that we don't have example use cases for interface implementation to validate our rules against, we are a bit concerned about shipping the feature in release without the ability to ensure that we are actually shipping a useful feature; it would not be good to ship something that consumers eventually come back and say is unusable because we missed some critical design flaw. Given this, we're currently considering holding back that half of the feature in preview until we have scenarios to validate against. We'd like to hear from the community here as well; what are your use cases for `ref struct`s implementing `interface`? #### Conclusion Rules for `ref struct`s in generics are approved. Rules for `ref struct`s implementing interfaces generally look good, but need validation against real world scenarios before we allow this part of the feature to ship in anything more than preview. ================================================ FILE: meetings/2024/LDM-2024-06-12.md ================================================ # C# Language Design Meeting for June 12th, 2024 ## Agenda - [`params Span` breaks](#params-span-breaks) - [Extensions](#extensions) ## Quote(s) of the Day - "When I share on Teams I lose my mouse." "You're a VIM user, you shouldn't be using your mouse anyway." - "I was expecting this to tear us apart." <2 people post gifs of The Room> "Someone got the reference. He's my totem actor." ## Discussion ### `params Span` breaks Champion Issue: https://github.com/dotnet/csharplang/issues/7700 Issue: https://github.com/dotnet/roslyn/issues/73743 We started today with a shorter discussion on a potential upgrade trap with `params` collections in C# 13. Because `params Span` is preferred over `params T[]`, it will mean that, on upgrade, call sites start preferring newly-introduced `Span` overloads; these overloads cannot be used in expression trees, however, so code that worked fine previously will now start failing. To work around this, consumers will have to explicitly create an array with `new[] { }`, instead of letting `params` work as expected. While unfortunate, this is much the same flavor of breaking change we have encountered in other instances where we introduce new conversions; we think it would be more unfortunate to try and handle this specially, and then later need to make still more changes if we update expression trees. Given that, we think the best approach is to simply document the potential breaking change and leave this as is. We'll let this sit over the weekend and think on it, and come back on Monday to make a final decision. #### Conclusion No conclusion today. ### Extensions Champion Issue: https://github.com/dotnet/csharplang/issues/5497 Related: https://github.com/dotnet/csharplang/pull/8121 Finally, we're back on extension codegen, this time with unfortunate news; our codegen strategy for extensions, and in particular using `Unsafe.As`, was rejected by the .NET runtime architects as fundamentally unsafe. There are potential aliasing issues that could cause parts of the JIT to become confused, particularly in rarer optimization scenarios, and they cannot guarantee that it will always be safely handled given our intended usage patterns; they would need to teach the runtime to handle these scenarios. Given that one of the goals of the current emit strategy is that it would need no runtime participation until a later date when we look at interface implementation, this is unfortunate; it would even mean that we couldn't ship any form of extensions in the .NET 9 timeframe, not even in preview. This is a long-lead feature and will need bake time with real users before we're fully confident that we have the right design, so we wanted to head back to the drawing board for emit. We see two main approaches: 1. Fully commit to runtime support. If we wanted to go the runtime support route, we wouldn't just rely on `Unsafe.As`; we'd update the runtime to just directly allow ref assignment of an underlying type to an extension type. This would be safer at a runtime level, as it gives it a primitive it can verify. It may also give us a better starting point for later support of interface members. However, we also think that it's unfortunate that we wouldn't be able to get any previewing during the .NET 9 timeframe. While we don't want to be afraid of not shipping features that were promised if they're not ready, this would likely mean that extensions wouldn't be shipped in a stable form until at least .NET 11. This is pretty unpalatable to the LDM. 2. Fall back to treating extensions as sugar over static methods on extension types, much like standard extension methods today. There's increased difficulty with the compiler representation here; the public model and internal emit models of these types would have to sugar and desugar to convert between them. It's certainly something that we know how to do today, as we do it with extension methods. That experience tells us that further expanding the sugar is possible, but will have some complicated edges. It will also require more rewrites of member bodies in order to mimic the semantics of instance methods, particularly for nested closures. It is possible, though, and has a couple of advantages: 1. A version of this could actually ship in the .NET 9 timeframe, at least in preview. That will let developers get their hands on it to validate our designs. 2. Existing extension methods may actually be convertible to the new extension form in a binary-compatible fashion. This could help resolve a concern the LDM has had for a while, that new types would need to be introduced to allow the new style of extension method. It wouldn't be perfect; in particular, any holder of extension methods that has extensions for multiple types likely couldn't work. We also don't think the runtime work to support extensions implementing interfaces for these types would be prohibitively more expensive than option 1; to the runtime, a static method that takes an object as its first parameter is very similar to an instance method on that object. While it wouldn't be as immediately straightforward as an instance member on an extension type, we also don't think it will be too challenging, especially since we expect to need runtime support for the interface implementation part anyways. Ultimately, we settled on option 2 here. The binary-compat story is something we want to investigate more with this approach, and the approach still allows for the original goals of the rejected codegen proposal. #### Conclusion We change the codegen strategy for extensions to be based on static methods, much like current extension methods, rather than `struct` types and `Unsafe.As`. ================================================ FILE: meetings/2024/LDM-2024-06-17.md ================================================ # C# Language Design Meeting for June 17th, 2024 ## Agenda - [`params` Span breaks](#params-span-breaks) - [Overload resolution priority questions](#overload-resolution-priority-questions) - [Application error or warning on `override`s](#application-error-or-warning-on-overrides) - [Implicit interface implementation](#implicit-interface-implementation) - [Inline arrays as `record struct`s](#inline-arrays-as-record-structs) ## Quote of the Day - "I don't want to jinx it, but this is the last open question" ## Discussion ### `params` Span breaks Champion Issue: https://github.com/dotnet/csharplang/issues/7700 Issue: https://github.com/dotnet/roslyn/issues/73743 Today, we followed up from [last time](LDM-2024-06-12.md#params-span-breaks), now that the LDM has had some time to consider options. To recap, we left the last meeting feeling that our best course of action would be to not attempt to mitigate this in the compiler, but instead document the breaking change. We still think this is the case; attempting to mitigate this, either by changing how the compiler binds for expression trees, or using some kind of attribute, will just lead to us eventually needing to figure out how to undo the change later if we ever are able to modernize expression trees. We do think we should invest a bit more in the error experience; having the compiler detect when such an overload is used in an expression tree and issue a specific diagnostic, and having the IDE offer to add an explicit array creation will go a long way to making the experience understandable. But ultimately, we will not be trying to ensure the code will compile exactly as is, unchanged. #### Conclusion Do not change the compiler. Improve the error reporting experience. ### Overload resolution priority questions Champion issue: https://github.com/dotnet/csharplang/issues/7706 Spec: https://github.com/dotnet/csharplang/blob/a50814bc5d5dacc9bc9b45db4d29c97ce91d2f1c/proposals/overload-resolution-priority.md#open-questions Next, we went through the 2 open questions in overload resolution priority. #### Application error or warning on `override`s Following up from the [last time](LDM-2024-05-13.md#overload-resolution-priority-questions), we need to decide what do when a user puts an `OverloadResolutionPriorityAttribute` on an overriding method. These attributes are always ignored on overrides, so while it isn't necessarily an error in and of itself, such an application has no effect. We don't necessarily think that there's a specific future scenario we want to preserve here, but we still think that such an application is a sign of a user misunderstanding the feature. Given that, we want to hard error in scenarios where a user applies an `OverloadResolutionPriorityAttribute` that will be ignored by the compiler. ##### Conclusion It is an error to put `OverloadResolutionPriorityAttribute` in a location that would be ignored by the compiler, such as method overrides. #### Implicit interface implementation Next, we looked at a related open question: should we try and have implicit interface implementations inherit priorities automatically? We don't think this is a good idea; concrete methods can actually implement multiple interface members, with potentially different priorities. How they implement various interfaces can also be quite complicated, especially in the presence of `modopt`s on interface members; the compiler sometimes needs to emit bridge methods that explicitly implement interface members and then forward to the class implementation, should those get the attribute automatically as well? We think it's ultimately simpler and more understandable for everyone if we continue the existing C# precedent that non-signature components, such as parameter names, `params`, and attributes, are not inherited by implementations. ##### Conclusion We will not inherit `OverloadResolutionPriorityAttribute` from interface definitions. ### Inline arrays as `record struct`s Champion issue: https://github.com/dotnet/csharplang/issues/7431 Issue: https://github.com/dotnet/roslyn/issues/73504 Finally today, we looked at an overlooked scenario from C# 12; applying `InlineArrayAttribute` to a `record struct` type. We never addressed this scenario, so we generate default codegen for `record struct`s that are actually inline arrays. This means that for all the code we generate, such as `Equals`, `GetHashCode`, and `ToString`, we don't do any enumeration of all the array elements. Instead, we just look at the first element. While this is likely something that we could update the C# compiler to handle correctly, we also question the value of doing so; inline array types have very specific requirements, and `record struct` doesn't bring a ton of advantages for them. There's not a potentially changing list of fields to keep up to date, as inline arrays can only have a single field. Thus, we think the right solution is actually to just make it an error to apply `InlineArrayAttribute` on a `record struct`. If there is demand after making this change, we can always revisit at a later date, but until then, we will leave this as an error. #### Conclusion Applying `InlineArrayAttribute` to a `record struct` will be an error. ================================================ FILE: meetings/2024/LDM-2024-06-24.md ================================================ # C# Language Design Meeting for June 24th, 2024 ## Agenda - [First-Class Spans](#first-class-spans) - [`field` questions](#field-questions) - [Mixing auto-accessors](#mixing-auto-accessors) - [Nullability](#nullability) ## Quote of the Day - Nothing particularly amusing was said today, sorry. ## Discussion ### First-Class Spans Champion issue: https://github.com/dotnet/csharplang/issues/7905 Spec link: https://github.com/dotnet/csharplang/blob/dd326f7fb0c282825ed1b2ffbe8180b6c54afa1c/proposals/first-class-span-types.md#conversion-from-type-vs-from-expression We started today by looking at an open question in the first-class span feature. The question at hand is about a minor difficulty that the compiler team has run into while implementing the feature; by making the conversion a conversion from type, rather from an expression, we've actually run into a novel scenario in the compiler. Currently, all the special-cased types that participate in conversions from the core library itself (ie, the assembly that defines `System.Object`), not from other assemblies. This fact has led to some of the structure of the compiler around conversions. `(ReadOnly)Span`, on the other hand, may not come from core library, but may instead come from `System.Memory.dll`, or the user may define it themselves in source. This means we need to decide how the compiler finds the `(ReadOnly)Span` type that considered for the conversions defined by this feature. We have a few options for this: 1. Require that the `System.Span` and `System.ReadOnlySpan` considered for the feature come from the core library, like all other types that have special conversion rules. 2. Restructure the compiler to plumb our existing logic for finding these types into the conversion logic. 3. Simply match by the full name of the type. Option 2 is the most consistent with all the rest of the compiler's handling around `(ReadOnly)Span`, but we're also concerned about the investment cost here. It's potentially a big restructure; not impossible by any means, but it has a fairly low return-on-investment in the long term. Option 1 is the easiest to implement, but we're somewhat concerned about the inconsistencies, particularly with other features that will allow the user to override `System.Span` with a type defined in source. Finally, option 3 is easier to implement than 2, and likely to be the most consistent with it. It's not perfect; in particular, it will potentially apply to _all_ definitions of `System.(ReadOnly)Span`, regardless of what assembly they come from, while things like collection expression rules only apply to the canonical definition (regardless of whether that canonical definition comes from source, corelib, or `System.Memory.dll`). We think that, for 99.9% of users, this will result in identical behavior to 2. Given that, we think option 3 is the best balance here. #### Conclusion We will go with option 3, match `System.Span` and `System.ReadOnlySpan` by full name. ### `field` questions Champion issue: https://github.com/dotnet/csharplang/issues/140 Questions: https://github.com/dotnet/csharplang/blob/dd326f7fb0c282825ed1b2ffbe8180b6c54afa1c/proposals/semi-auto-properties.md#open-ldm-questions #### Mixing auto-accessors The first question up is a simple confirmation question. While the proposal has long included the ability to have one half of a semi-auto property be a `set;` or `get;`, we've never explicitly confirmed it in LDM. After a brief discussion, we confirmed that we do indeed want to be able to leave one half of a semi-auto property as an auto accessor. ##### Conclusion Confirmed. #### Nullability The next issue took up the rest of our time today, without reaching a final conclusion. Nullability of the backing field here is very tricky: we do almost no inter-procedural analysis of methods today (except for local functions), and it very much seems like we will need to do this type of analysis to get the nullability correct. There's also a lot of edge cases to consider, particularly around lazy initialization. One thing that became very clear was that the proposed `[field: NotNull]` approach wasn't an acceptable tradeoff to the LDM. Nullability does make a lot of pragmatic cuts where it is possible to observe `null` values, such as arrays. However, we think that this isn't a place where we could accept that type of tradeoff; arrays are a local and obvious source of nulls. Lazy initialization through multiple constructors or methods is hard to get right and much subtler to debug. We are somewhat concerned about having a magic solution though, especially given our experience with `var`. While there are no safety holes introduced from our treatment of `var` as allowing null assignments, we do know that a non-zero percentage of our users are confused by the behavior; even if it's completely safe, there's a perception that the guardrail is too loose, and it causes these users to lose their trust of the feature. If we did fancy cross-method analysis for `field`, and allowed `null` assignments to the backing field even when it's provably perfectly safe, is that ok? Or will that confuse users and cause them to distrust the feature? We want to kick these questions back to a small group to noodle on, so we will leave these for now and revisit in the near future. ================================================ FILE: meetings/2024/LDM-2024-06-26.md ================================================ # C# Language Design Meeting for June 26th, 2024 ## Agenda - [Extensions](#extensions) ## Quote of the Day - "You only get one first impression, unless you're like me and you forget things all the time." ## Discussion ### Extensions Champion Issue: https://github.com/dotnet/csharplang/issues/5497 Format proposal: https://github.com/dotnet/csharplang/pull/8242 We spent today looking over the proposed format for static member translation for extension types. The format discussion revolves around how much compatibility we want to maintain with existing extension methods, and whether we want to force other languages to do explicit work to support them. There are two real extremes here: 1. Completely require that non-C# languages must do work to support new extension types. This means putting `modreq`s on the types, or using `CompilerFeatureRequired`, to force compilers that don't understand to pretend those members don't exist. This would ensure that we don't have to support any amount of back-compat work with new extensions, but also means that we'd rule out a migration story entirely. 2. Fully support usage in other compilers in static form with no changes. This would possibly mean that C# would need to support calling these extensions in static form as well; if an older C# compiler could see and understand these static members, and emit calls to them, then upgrading and not supporting calling in static form would be a breaking change. The advantage of this form would be compatibility with some older extension methods, and permit upgrading older static classes to extension types, which is an attractive ability. After some discussion, we ended up pulling the extremes into separate parts; instance method extensions, and everything else. For everything else, LDM is strongly against allowing them to be called in static form by other languages unless they do the work to explicitly support that form. For instance methods (ie, `static void M(this int i)` methods today), we're very interested in allowing back-compat. This would mean a few concrete things for the proposal to go chase down: 1. We would have to support calling these instance methods in both `instance.M()` and `E.M(instance)` form, since the latter is valid today for existing extension methods defined on a `static` type. 2. There are some areas of extension type resolution that do not behave the same as extension method resolution today. Those areas will need to be overhauled. 3. Signature uniqueness still needs to be explored. How would the `E.M(instance)` format behave when `E` has both `void M()` and `static void M(Instance i)` defined on it? We want to take these questions back and look at how complicated they will be to solve before making a final decision on the migration story. #### Conclusion We will block consumption in other languages without explicit support for extension types for all extension members except instance members. For instance members, we will explore making the emit binary-compatible, and how much we will have to overhaul to get that to work, then come back and make a final decision on whether to block consumption. ================================================ FILE: meetings/2024/LDM-2024-07-15-usage-data.md ================================================ Building a large internal repo with `this.field/value` style of breaking changes had 100+ errors. Hit every variation of the diagnostic: - `field` as field - `value` as field - `this.that.value` in accessor Example: ```csharp public int Value { get { return this.value; } set { this.value = value; this.valuePtr = &value; } } ``` Random repos found with GH queries - [this.value](https://github.com/search?q=%22this.value%22+language%3AC%23&type=repositories&l=C%23) 209K results, 20% hit rate of real bug through 2 pages - https://github.com/DotNetAnalyzers/StyleCopAnalyzers - https://github.com/VodeoGames/VodeoECS - https://github.com/NeoforceControls/Neoforce-Mono - https://github.com/askeladdk/aiedit - https://github.com/YuhangGe/DLX-System - https://github.com/GameDiffs/TheForest - https://github.com/alexsteb/GuitarPro-to-Midi - [this.that.value](https://github.com/search?q=%2Fthis%5C.%5Ba-z%5D%2B%5C.value%2F+language%3AC%23&type=code) 106K results, 8% hit rate of real bug through 4 pages - https://github.com/Real-Serious-Games/Unity-Editor-UI - https://github.com/VeeamHub/SuperEdit - https://github.com/moto2002/mobahero_src - [this.field](https://github.com/search?q=%22this.field%22+language%3AC%23&type=code) 41K results, 8% hit rate of real bugs through 3 pages - https://github.com/HaloMods/Halo1AnimationEditor - https://github.com/Team-COCONUT/Minotaur - https://github.com/moto2002/mobahero_src - https://github.com/Toskyuu/TPW - https://github.com/graehu/SON I only dug in the first 4-5 pages for each query as I felt it was representative at that point. Possible I'm wrong but I doubt the numbers would change if we dug any deeper. More GH queries: - [\_field](https://github.com/search?q=%2F%28%5E%7C%5CW%29_field%28%24%7C%5CW%29%2F+language%3AC%23&type=code) as a field name. 6K results, near 100% hit rate - [\_value](https://github.com/search?q=%2F%28%5E%7C%5CW%29_value%28%24%7C%5CW%29%2F+language%3AC%23&type=code) as a field name. 137K results, near 100% hit rate Largest intentional breaking change we've in last ~5 years was lambda inference and overload changes: - Had a very long lead and got plenty of preview feedback about it. - In total we had ~20-30 bugs filed against it. - That is number of customers who did not self correct. Let's assume for sake of argument that total number of users that hit this problem is 10x reported. So 200-300 My conclusions from data: 1. Generated code is a challenge for our fixer 2. `field` and `_field` are uncommon but not rare field names - The use of `field/_field` is a style decision - Breaking change is about the style in which it is declared and accessed: `this.field =` vs. `field =` 3. `value` and `_value` are common field names - The use of `value/_value` is a style decision - Breaking change is about the style in which it is declared and accessed: `this.value` vs. `value =` 4. Ratio of field:value virtually every query is 1:8-1:12 5. Breaks on `this.value` or `this.field` are not viable - In the range of 20K and 10K impacted GH samples - That is roughly 2 orders of magnitude greater than the upper bound of our highest breaking change 6. It is very hard to quantify what the breaks on naked field / value would be. - Hard to construct a GH query to narrow down to uses of field / value without keywords. - Metadata queries can't distinguish between this.field and simply field in an accessor ================================================ FILE: meetings/2024/LDM-2024-07-15.md ================================================ # C# Language Design Meeting for July 15th, 2024 ## Agenda - [`field` keyword](#field-keyword) - [First-Class Spans Open Question](#first-class-spans-open-question) ## Quote of the Day - "Obviously what the data means is that after we make `field` a keyword, usage of it as the name of a field will skyrocket, because that's what correlation means, right?" ## Discussion ### `field` keyword Champion issue: https://github.com/dotnet/csharplang/issues/140 Specification: https://github.com/dotnet/csharplang/blob/e44ebf7095e462ef5f9bbd869386a70cbd85e6d0/proposals/field-keyword.md#syntax-locations-for-keywords [Usage data](LDM-2024-07-15-usage-data.md) We started today by looking at real world usage data for `field` and `value` as keywords, to allow us to evaluate how breaking of a change we are looking at. Our current strategy, making both `field` and `value` keywords within property accessors, even when used as a nested expression, turns out to be extremely breaking. Internal repos show hundreds of errors, with more waiting further in the build process; the errors stopped the build in root projects, which would have to be fixed up to then allow the build to progress further. Surprisingly (to the LDM, at least), `value` is actually _more_ breaking than `field` is, and by a pretty big order of magnitude: our analyses find that, across various sources, `value` is used in a way that would break with this proposal 8-12 times more than `field` is. We're pretty concerned by the level of break here, even with fixers, for a couple of reasons: * A decent amount of the break was in generated (either through a separate tool or through a Roslyn source generator) code. Even with a fixer, that generated code would likely just be overwritten the next time the generator runs. * Our original supposition that, because `value` already has special meaning in property bodies it would be fairly simple to unify with `field`, turns out to be completely false. Given these issues, we have a few proposals on how to deal with this, scaling back the break to have less of an impact: 1. Accept the break, exactly as shown. 2. Scale back the keyword recognition to only consider _primary\_expressions_. For `value`, this fixes a lot of the errors; in Roslyn at least, it only results in a single build error, where `value` was used in a lambda inside the getter body. 3. 2, but separate out `value` entirely, and leave it alone. Just make the breaking change for `field`, and only in the _primary\_expression_ scenario. To tackle this question, we started by looking at the two separate parts: should we scope the break down, and should we remove any breaks on `value`? Our first pass here was maximally breaking, as we wanted to see how bad the reality actually would be. It turns out to be unacceptably breaking, so we are in strong agreement to scope down the break to just _primary\_expressions_. Next, we took a second look at `value`. While scoping down the break would eliminate a very large percentage of the real-world breaks that we see here, it wouldn't eliminate them; Roslyn itself would break, just as the very first example. Our breaking change philosophy was that any breaking changes need to be well-motivated, and looking at the actual data in comparison to our original suppositions, we now think that we don't have enough motivation to take a change to `value`. Even though changing `value` would make it more consistent with `field`, we don't think we have enough justification to make it worth it. Therefore, we go with option 3. #### Conclusion `field` will only be recognized as a keyword when used as a _primary\_expression_ within an accessor body. Any changes to `value` will be removed from this proposal and its behavior will continue unaffected. ### First-Class Spans Open Question Champion issue: https://github.com/dotnet/csharplang/issues/7905 Specification: https://github.com/dotnet/csharplang/blob/4578db732a7d4aece52a07d1c822846b381f40b2/proposals/first-class-span-types.md#delegate-extension-receiver-break Finally today, we took a look at a potential breaking change that could be caused first-class spans, and the proposed mitigation for it. This is a somewhat complicated case: the main issue is that because method group resolution of an extension method called in extension form can actually apply a conversion to the receiver, that conversion could potentially be something that can't be boxed. This isn't really a problem today, because the only conversions allowed here are identity, reference, or boxing conversions; these are all things that are already on the heap, so capturing the receiver into the method closure is perfectly fine. `Span`s, though, cannot be captured into the method group closure, so if there is ever a method group converted to a delegate type, and it uses a span conversion on the receiver, that _will_ be an error. Since these conversions can be prioritized, it means that currently valid code will become an error, particularly as the BCL adds new `Span`-taking overloads. Given the 100% error rate of the scenario, we are fine with adding a special rule to exclude `Span` conversions when doing method group overload resolution on an extension method called in extension form. #### Conclusion Mitigation is approved. ================================================ FILE: meetings/2024/LDM-2024-07-17.md ================================================ # C# Language Design Meeting for July 17th, 2024 ## Agenda - [Overload resolution priority open questions](#overload-resolution-priority-open-questions) - [Better conversion from collection expression with `ReadOnlySpan` overloads](#better-conversion-from-collection-expression-with-readonlyspant-overloads) ## Quote of the Day - "I don't know what shortstop is" "It's Abbott and Costello!" (proceeds to play Who's on First for the LDM during break) ## Discussion ### Overload resolution priority open questions Champion issue: https://github.com/dotnet/csharplang/issues/7706 PR: https://github.com/dotnet/csharplang/pull/8296 We started today by looking at a couple of open questions for overload resolution priority, to confirm and/or revise implementation decisions made by the feature. First, we looked at whether to disallow the attribute on a few more locations. We'd [previously](LDM-2024-06-17.md#application-error-or-warning-on-overrides) decided that the attribute shouldn't be allowed in locations where it would be an error to apply it. A few more locations came up during implementation. The only one of these that had mild discussion was local functions. However, ultimately, we decided that the simple and pragmatic solution is to just disallow the attribute on local functions; even though they go through overload resolution, they do not allow overloads, so the attribute will have no impact. Next, we looked at the desired behavior for language version. The proposal suggests 3 options for handling: 1. Don't have the attribute do anything when `LangVersion` is set to < 12. 2. Issue an error when the attribute impacts overload resolution behavior when `LangVersion` is set to < 12. 3. Silently honor the attribute when `LangVersion` is set to < 12. Option 2 was discarded as a non-starter basically immediately, as it would block the runtime from adopting the feature. This leaves options 1 and 3. Option 1 better favors those who encounter the new feature as they scout out new versions of VS for their coworkers, as it will ensure that new tooling versions continue to build their existing code exactly the same as it was. Option 3 better favors those that upgrade to .NET 9, but keep themselves on C# 12 for the time being. After some discussion, we think that option 1 better serves the purpose of langversion, and will proceed with that approach. #### Conclusion We will error on all 5 locations: conversion operators, lambdas, destructors, static constructors, and local functions. We will ignore the attribute when the compiler is set to language version 12 or less. ### Better conversion from collection expression with `ReadOnlySpan` overloads Champion issue: https://github.com/dotnet/csharplang/issues/8297 Related issue: https://github.com/dotnet/roslyn/issues/73857 Finally today, we took a look at what we could do around collection expression conversions to handle some issues that the BCL will be running into when they ship .NET 9. New overloads of `string.Concat` can break collection expressions because we don't have a betterness tiebreaker around `ReadOnlySpan` vs `ReadOnlySpan`, and first-class spans can't serve this gap because collection expressions have a different preferred span type than the rest of the language. Discussion in LDM showed a general reluctance to go with the narrow proposal, and we much preferred the version that applied better conversion from element recursively to solve this problem. That version gets us closer to unification with `params` collections and is more complete. While we do think this will be a problem in .NET 9, we're hopeful that we can use `OverloadResolutionPriorityAttribute` to solve the BCL issues. We'll therefore take this back and verify that `OverloadResolutionPriorityAttribute` can solve this in a way that is acceptable; if it can't, then we can pick this back up and make a decision on what to do here in the short term, or whether we should pull the methods from the BCL for .NET 9. #### Conclusion We will look at `OverloadResolutionPriorityAttribute` to solve this for .NET 9, and then look at the more comprehensive better conversion approach for a future release. ================================================ FILE: meetings/2024/LDM-2024-07-22-ref-struct-interface-examples.md ================================================ # ref struct interfaces [Proposal](https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md) ## Questions 1. Move forward with ref struct interfaces out of preview 2. Do we want to introduce a warning in the DIM case? 3. Do we want to limit the constraint language? ## Concrete Usages ### Needed for `ITensor` The tensor team wants to ship the following interface that is meant ```csharp interface ITensor { [UnscopedRef] ReadOnlySpan Lengths { get; } } ``` ### Comparer Interfaces This is now used by the runtime in the [comparer][comparer] interfaces. ```csharp public static int BinarySearch(this System.ReadOnlySpan span, T value, TComparer comparer) where TComparer : System.Collections.Generic.IComparer, allows ref struct; public static int BinarySearch(this System.ReadOnlySpan span, TComparable comparable) where TComparable : System.IComparable, allows ref struct; ``` [comparer]: https://github.com/dotnet/runtime/pull/103604 ### Enumerator Runtime wants to have existing `ref struct` based enumerators inherit `IEnumerator` ### Math interfaces Runtime is considering them for the math related interfaces: - `IAdditionOperators` - `IParsable` - `ISpanParsable` - `IUtf8SpanParsable` ### Customer Scenarios - [U8String project](https://github.com/dotnet/csharplang/discussions/8211#discussioncomment-9883809) alows for unification - [Asset Ripper](https://github.com/AssetRipper/AssetRipper.Text.Html) makes [heavy use](https://github.com/AssetRipper/AssetRipper.Text.Html/pull/1/files) of this already ## Warn on DIM case? The runtime doesn't, and never will, support calling a default implemented member (DIM) when the receiver type is a `ref struct`. The compiler will require that a `ref struct` implement all members. Could also choose to warn at the point the code invites this problem if we wanted to. ```csharp interface I1 { // Virtual method with default implementation void M() { } } // Invocation of a virtual instance method with default implementation in a generic method that has the `allows ref struct` // anti-constraint void M(T p) where T : allows ref struct, I1 { p.M(); // Warn? } ``` ## Limit Constraint Language? The language allows for us to do this today ```csharp void M(T t) where T : IDisposable, allows ref struct { } ``` That is strange if there is no way `T` can be `ref struct` but not also implement interfaces ================================================ FILE: meetings/2024/LDM-2024-07-22.md ================================================ # C# Language Design Meeting for July 22n, 2024 ## Agenda - [Extensions](#extensions) - [Ref structs implementing interfaces](#ref-structs-implementing-interfaces) ## Quote of the Day - "A catscade of errors" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/5497 Spec change: https://github.com/dotnet/csharplang/pull/8305 We started today by looking at the latest proposed changes for extensions. There was no real pushback on the string-based encoding erasure form, but we did bring up some concerns that may need to be addressed: * How will the format work for local functions? * We don't think that function pointers are necessary to support right now; extensions already have limited interactions with pointer types in general. If we can come up with specific scenarios, we could look at them at that point. * We talked briefly about simply upgrading the `Type` format to allow encoding type parameters. However, that runs into the issue that the extension types would not satisfy type constraints. Any solution that involves concrete `Type` instances would likely break reflection in some way. * We also briefly discussed whether we need to invest in a way to shrink the amount of metadata we emit. For now, we don't think we need to. The concern with nullable was that every member across an entire project would implicitly get new attributes. We're nowhere near that level here. Overall, the encoding format here is generally approved. ### Ref structs implementing interfaces Champion issue: https://github.com/dotnet/csharplang/issues/7608 Related: [Ref struct interfaces examples](LDM-2024-07-22-ref-struct-interface-examples.md) Finally today, we are following up on a [previous meeting](LDM-2024-06-10.md#ref-structs-implementing-interfaces-and-in-generics) where we asked for examples of using `ref struct`s in interfaces that we could use as validation that the feature was designed and working as we hoped. We received a number of examples, and we'd like to thank all the readers of these notes who responded. There were a few examples in particular that proved especially helpful, and they're called out in the linked examples file. Given these examples, we're happy with the feature at this point, and are ready to ship it for C# 13. We then took another look at the DIM scenario for `ref struct`s, and whether we should consider a warning at the call site. We don't think that this is generally beneficial; the consumer doesn't really have an option for avoiding it, and we can't catch all instances where this would happen. Instead, we think the onus is on the person who is implementing an interface on a `ref struct`; if the `interface` is updated, the implementor needs to go recompile with the new interface member and publish a new version. #### Conclusion We are comfortable with shipping `ref struct`s implementing interfaces, and we will not warn on calling an interface member that has a DIM. ================================================ FILE: meetings/2024/LDM-2024-07-24.md ================================================ # C# Language Design Meeting for July 24th, 2024 ## Agenda - [Discriminated Unions](#discriminated-unions) - [Better conversion from collection expression with `ReadOnlySpan` overloads](#better-conversion-from-collection-expression-with-readonlyspant-overloads) ## Quote of the Day - "I put that there so people will complain" ## Discussion ### Discriminated Unions Champion issue: https://github.com/dotnet/csharplang/issues/113 Proposal: https://github.com/dotnet/csharplang/blob/18a527bcc1f0bdaf542d8b9a189c50068615b439/proposals/TypeUnions.md First up today, the discriminated unions working group presented the proposal they've been working on for a while to the broader LDM. This was a broad overview session, rather than a deep dive into nitty-gritty questions; there are still plenty of little details that will need to be filled in, but we're cautiously optimistic about this proposal and moving forward with it. There was some concern about some of the ternary behavior, but we can dig more into that as we bring this proposal back for detailed follow ups in the future. ### Better conversion from collection expression with `ReadOnlySpan` overloads Champion issue: https://github.com/dotnet/csharplang/issues/8297 Related issue: https://github.com/dotnet/roslyn/issues/73857 We followed up from [last Wednesday](LDM-2024-07-17.md#better-conversion-from-collection-expression-with-readonlyspant-overloads), revisiting an even narrower proposal; just looking at implicit reference conversions, rather than all implicit conversions except numeric conversions. However, LDM still does not prefer the narrow fix; it has edge cases and isn't generalizable. There is some concern that rules around `OverloadResolutionPriority` might not work here; `string.Concat` has 15 overloads in .NET 9, and this isn't the type of break-glass scenario `OverloadResolutionPriority` was designed for. Given this, we reaffirm that we want to look into doing the recursive approach, ie better conversion from element. #### Conclusion Continue looking into option 3, better conversion from element. ================================================ FILE: meetings/2024/LDM-2024-08-14.md ================================================ # C# Language Design Meeting for August 14th, 2024 ## Agenda - [`field` keyword](#field-keyword) - [`field` nullability](#field-nullability) - [`field` in event accessor](#field-in-event-accessor) - [Scenarios similar to `set;`](#scenarios-similar-to-set) ## Quote of the Day - "That feels a bit like letting the wolf into the hen house." ## Discussion ### `field` keyword Champion issue: https://github.com/dotnet/csharplang/issues/140 Spec: https://github.com/dotnet/csharplang/blob/d80d82e87e26412c2f5f3ef55c5253f474ad5049/proposals/field-keyword.md #### `field` nullability Nullability proposal: https://github.com/dotnet/csharplang/issues/8360 Today, we discussed a proposal for how nullable reference types could be handled for `field`. This proposal has a decent amount of magic in it, defining a new concept of "null-resilient getters", and using that to determine whether the backing field should be considered nullable if it is not a value type. We have somewhat mixed feelings on this. The user code is ultimately something we think is reasonable: `string Prop { get => field ??= ""; }` is a perfectly reasonable property definition. However, this is effectively a narrow case of the different backing field question, proposal https://github.com/dotnet/csharplang/issues/133. The LDM has also expressed interest in that proposal, so the question we need to resolve is: does the nullability proposal move the cliff far enough to warrant including the language? It certainly can't handle anything related to nullable value types, or anything related to `Lazy` initialization. It will handle lazy initialization using `??=` or `Interlocked.CompareExchange`, but the latter pattern is certainly not common outside of a few specific codebases. After discussion on this point, we have a slight lean towards nullability as proposed in 8360, but this is a smaller LDM session and we don't feel that we have the unity to make a final call today. We'll let this ruminate in our brains for the weekend and then come back next week to make a final decision. ##### Conclusion No conclusion today. Will revisit next week. #### `field` in event accessor Question: https://github.com/dotnet/csharplang/blob/d80d82e87e26412c2f5f3ef55c5253f474ad5049/proposals/field-keyword.md#field-in-event-accessor We next looked at whether we can scope `field` down to not being usable in `event`s. We don't see a use case; generally speaking, either the standard field-like events are sufficient, or the user will want some kind of separate backing field, like a `List`, to store all the delegates so they can do something with them, rather than using `MulticastDelegate`. Absent scenarios, we will not allow this. ##### Conclusion Disallowed. #### Scenarios similar to `set;` https://github.com/dotnet/csharplang/blob/d80d82e87e26412c2f5f3ef55c5253f474ad5049/proposals/field-keyword.md#scenarios-similar-to--set- Finally today, we considered whether to disallow scenarios that are _similar_ to, but not exactly, `{ set; }`. After some brief discussion, we think that we should keep with the status quo, and only disallow `set;`-only properties. We don't want to try and get into the business of recognizing when a property does or does not have side-effects in the general case; it's either `set;`, which is trivially vacuous, or it's something else, and we'll let the user do what they want. ##### Conclusion We will keep the status quo: auto-`set;`-only properties are disallowed. Anything else is fair game. ================================================ FILE: meetings/2024/LDM-2024-08-19.md ================================================ # C# Language Design Meeting for August 19th, 2024 ## Agenda - [Better conversion from collection expression](#better-conversion-from-collection-expression) ## Quote of the Day - "So you're saying that element type is more equal than collection type?" ## Discussion ### Better conversion from collection expression Champion issue: https://github.com/dotnet/csharplang/issues/8374 Specification: https://github.com/dotnet/csharplang/blob/915dce8cd2a87a390904044ddada2d0794a15a24/proposals/collection-expressions-better-conversion.md Today, we followed up from the [last time](LDM-2024-07-24.md#better-conversion-from-collection-expression-with-readonlyspant-overloads) we discussed collection expressions. This is a matter with some urgency, as we want to try and solve the `ReadOnlySpan` vs `ReadOnlySpan` issue before .NET 9 ships. To do this, we looked at a proposal for introducing a new algorithm for better conversion from expression, as it applies to collection expressions. The proposal tries to stick close to one of our [previous principles](../2023/LDM-2023-09-20.md#overload-resolution-fallbacks) around collection expressions, which is that we didn't want to get pulled in opposite directions from the collection type and the element type. To do this, and still enable disambiguation between `List` and `List`, or other similar scenarios, we had to introduce a new concept around collection type comparison, without type arguments. This concept is new to the language, and there is some amount of spec work that needs to be done on it. However, further discussion led us to question something larger: the entire principle we previously stated, where we said we wanted scenarios like `IEnumerable` and `List` to produce an ambiguity error. This principle was based around the idea that with collection expressions, both the type of the collection itself, as well as the type of the elements, are equal in priority. On further reflection, we're not sure this is actually true. The collection expression doesn't have a type, and even when we eventually add a natural type, target-typing will always win, just like it does with switch expressions. On the other hand, the elements _do_ have natural types, and those natural types can say a lot. Given `[1, 2, 3]`, we've come around to the idea that `IEnumerable` is better than `List`, based purely on element type. This radically changes our thoughts on the direction this proposal needs to move; we'll come back next LDM with an updated proposal that takes this changed principle in mind, prioritizing collection element type betterness over the collection type itself. #### Conclusion We will revisit an adjusted proposal that prioritizes element type over collection type betterness. ================================================ FILE: meetings/2024/LDM-2024-08-21.md ================================================ # C# Language Design Meeting for August 21st, 2024 ## Agenda - [Better conversion from collection expression](#better-conversion-from-collection-expression) - [`field` keyword nullability](#field-keyword-nullability) ## Quote of the Day - "We can get rid of that whole T' business" "But you like tea!" "No, we still have T, we just don't have T'" ## Discussion ### Better conversion from collection expression Champion issue: https://github.com/dotnet/csharplang/issues/8374 Specification: https://github.com/dotnet/csharplang/blob/24aac29f7af589f5aa6242629642821df4cea422/proposals/collection-expressions-better-conversion.md Following up from [last time](LDM-2024-08-19.md#better-conversion-from-collection-expression), we brought back the proposal with the requested tweaks for review by the LDM. The overall proposal is what we expected to see, and we will move forward with it. With that out of the way, we then looked at the open question around how much we should prefer span types over other types. The existing wording of the rule, as shipped in C# 12, also makes `ReadOnlySpan`/`Span` vs `List` ambiguous, which is strong motivation for us to not solve that here; we have heard lots of feedback around the various aspects of collection expressions that we needed to adjust, and this ambiguity has not come up among them. Further, given that this is ambiguity, it is an area that we will able to adjust in the future if we hear more feedback around it, as we've done when we've adjusted other ambiguity rules in the past with betterness, better betterness, and bestest betterness. Additionally, API authors that have such an ambiguity can make their APIs usable via `OverloadResolutionPriorityAttribute` in C# 13, by prioritizing the `ReadOnlySpan`/`Span` variant. Given this, we feel comfortable with the specification as written. #### Conclusion Specification is accepted as written. ### `field` keyword nullability Champion issue: https://github.com/dotnet/csharplang/issues/140 Spec: https://github.com/dotnet/csharplang/blob/d80d82e87e26412c2f5f3ef55c5253f474ad5049/proposals/field-keyword.md Nullability proposal: https://github.com/dotnet/csharplang/issues/8360 Finally today, we took another look at the proposal for making the `field` keyword handle nullability automatically. Last time, we were leaning towards adopting the proposal, but hadn't quite developed the unity we need to adopt the proposal, so we spent today talking through our reasoning more. The arguments are very similar to last time: is there too much magic going on here, or is this something that users will expect to just work? One important argument that came up during this was a thought experiment to reorder `field` and nullable-reference types as features: if we presume that `field` had existed before we did NRT, it would be nearly certain that this code would have existed. One goal of NRT was to allow perfectly safe and idiomatic code to continue existing exactly as it had before with a minimal amount of changes. After more discussion on this point, we decided to move forward with the proposal, but will still need to dig into some of the details. In particular, the tradeoff between "getters with no nullability warnings" or "getters that have _more_ nullability warnings than when `field` starts as null"; this is the difference between having a `string unrelated = null;` in the getter or not. We'll need to think through how complex we want to make our rules here; simpler rules are easier to explain, but they might also still cause some confusion. We'll consider these details again in a future LDM. #### Conclusion General proposal is adopted. Specific behavior still needs more review. ================================================ FILE: meetings/2024/LDM-2024-08-26.md ================================================ # C# Language Design Meeting for August 26th, 2024 ## Agenda - [`field` keyword open questions](#field-keyword-open-questions) - [`field` in property initializers](#field-in-property-initializers) - [Interaction with partial properties](#interaction-with-partial-properties) - [`readonly` field](#readonly-field) ## Quote of the Day - "Whenever I want to thumbs up, I see the open hand and I click it" ## Discussion ### `field` keyword open questions Champion issue: https://github.com/dotnet/csharplang/issues/140 Spec: https://github.com/dotnet/csharplang/blob/ce7bd40ee26432ba085c5ba9d8ad3be6d4f7fa9a/proposals/field-keyword.md#open-ldm-questions #### `field` in property initializers https://github.com/dotnet/csharplang/blob/ce7bd40ee26432ba085c5ba9d8ad3be6d4f7fa9a/proposals/field-keyword.md#field-in-property-initializer We started by looking at whether `field` should be visible in the property initializer. This would diverge in behavior from how `this` is generally inaccessible in accessors, and most of our debate centered not around whether we should disallow this, but whether we should have a specific error if the user does try to do this. Ultimately, we decided to let the compiler implementation decide whether to have a specific error, but to otherwise not adjust the behavior. If there is an actual class field named `field`, then it will be visible in the initializer (and potentially be an error to reference); in all cases, the synthesized backing field of the property will not be visible. ##### Conclusion We will bind the initializer as in previous versions of C#. We won't put the backing field in scope, nor will we prevent referencing other members named `field`. #### Interaction with partial properties https://github.com/dotnet/csharplang/blob/ce7bd40ee26432ba085c5ba9d8ad3be6d4f7fa9a/proposals/field-keyword.md#interaction-with-partial-properties Next, we thought about how partial properties and `field` should interact. There's two main interactions to consider: where to allow initializers, and whether to allow auto-implemented accessors in the implementation side of the property. For the first question, we considered the use cases for partial `field`-backed properties. We foresee most of these properties being used by source generators, in one of two possible ways: 1. A hint to a source generator (likely combined with an attribute) that the generator should implement the body of this property. 2. A hint from a source generator to the user that the machinery of the generator requires the user to write a property; using a partial property here would give the user a better error than "undefined member `Name`", and convey to the user the shape of what they need to implement. In both scenarios, it is useful for the user to be able to put an initializer on the property to indicate what the backing field should be initialized to. We are also in agreement that we don't want to try and allow putting the initializer in both locations, even if it is semantically identical; attempting to determine whether the initializers are identical is difficult for both the compiler and a human reading the code. Thus, we conclude that initializers will be allowed on either the declaration or the implementation, but not both at the same time. For the second question, we again thought about our example scenarios; the scenarios are generally locations where one or both of the accessors can't just be a simple auto-accessor. They need to have some kind of side effect, such as triggering an INPC notification, logging, etc. However, we _do_ think that the "only one accessor is complex" scenario is common enough to allow one of the implementing accessors to be an auto-implemented accessor. Allowing both accessors to be automatically implemented is not currently on the table, as it's complex for the compiler to implement and we don't have any scenarios to drive the design. ##### Conclusion Either declaring or implementing property locations can use an initializer, but not both at the same time. At least one implementing accessor must be manually implemented, but the other accessor can be automatically implemented. #### `readonly` field https://github.com/dotnet/csharplang/blob/ce7bd40ee26432ba085c5ba9d8ad3be6d4f7fa9a/proposals/field-keyword.md#readonly-field Our final question today is around the `readonly`ness of the backing field. This is mostly unobservable; reflection can see it, but it doesn't have a real impact on user code. Given the lack of observability, we think the right decision is to rely on the containing context; if the containing type or property is `readonly`, then the backing field will be too. Practically, this means that when the containing type is a `struct`, and either it or the property is marked `readonly`, then the synthesized backing field will also be `readonly`. ##### Conclusion When the containing type is a `struct`, and either it or the property is marked `readonly`, then the synthesized backing field will also be `readonly`. ================================================ FILE: meetings/2024/LDM-2024-08-28.md ================================================ # C# Language Design Meeting for August 28th, 2024 ## Agenda - [Nullable in `ref` ternaries](#nullable-in-ref-ternaries) - [Block-bodied switch expression arms](#block-bodied-switch-expression-arms) ## Quote of the Day - "I want to go meta meta for a second" ## Discussion ### Nullable in `ref` ternaries Issue: https://github.com/dotnet/csharplang/issues/8379 First up today, we looked at an issue that was originally raised as a Roslyn bug; nullable suppression of `ref` ternaries didn't work as expected. In fixing this bug, our testing showed that the experience was not very polished overall, and should be revisited to be more consistent. One important note about this is that the original bug _is_ a compiler bug; a nullable warning was being reported, but it couldn't be suppressed. However, we think this is a good opportunity to align `ref` ternaries with both regular ternaries, and method argument behavior. Given that, we decided to go with option 1: use best common type and type inference. #### Conclusion Go with proposed option 1, using best common type and type inference. ### Block-bodied switch expression arms Champion issue: https://github.com/dotnet/csharplang/issues/3037 Related issue: https://github.com/dotnet/csharplang/issues/3086 Finally today, we took a look at an older issue that occasionally comes up, to see if we wanted to make any more progress on it in the nearer term. Switch expression arms, and by extension any expression location, occasionally want to have side-effects. These often force users to fall back to much more verbose patterns; moving to a switch statement, rewriting an expression into a series of multiple statements, etc. While the specific issue we discussed today is only for switch expressions, it really is a backdoor to the whole space, as we need to design far enough ahead to know how the whole space would work. Otherwise, we'd risk ending up in a space where we have diverging statement-within-expression syntaxes, which is something we absolutely want to avoid. Ultimately, the LDM is very much in favor of continuing to explore this space, but we're far less unified on the specifics. We discussed a few different possible syntaxes for the "produce a value from this expression block": * `break value` - as in the proposal. This has some advantages in not being legal syntax today, but some members are concerned about the confusion it could bring if you mix `break`, `break value`, and `continue`. There's also some concern that it is not intuitive as to what is actually happening. * `return value` - an alternate option that would make `return` mean something different when in an expression block. Some members mentally model expression blocks as lambda expressions that are immediately invoked, and this interpretation naturally complements this. However, other members are concerned that it implies that the user will leave the method, not the containing expression block, and that it would also block the ability to actually do just that. * `out value` - like `break`, this has the advantage of not being legal syntax today in the locations you'd use it. But, like with break, there's concern about how understandable of a keyword it is. * No keyword - As in https://github.com/dotnet/csharplang/issues/3086, we could also just leave the last `;` off a statement and have that be what the block evaluates to. There's some compelling examples of this in the real world already, such as Rust or F# (F#'s `|> ignore` is the equivalent of a more obvious `;` in this case). But there's some concern about the subtlety of this, and whether it would force users to write expression blocks in a particular pattern; how might a user produce a value from the middle of a `foreach` if they found the value they're looking for, for example. Ultimately, we are far too fractured, and talking about too many hypotheticals, to make a decision today. We need to go back and do more research; look at other languages that have concepts like this and see what they do, how their solutions might apply to C#, and come up with sets of examples around it to inform our decision making. ================================================ FILE: meetings/2024/LDM-2024-09-04.md ================================================ # C# Language Design Meeting for September 4th, 2024 ## Agenda - [Triage (no milestone)](#triage-no-milestone) - [Type inference using method group natural type](#type-inference-using-method-group-natural-type) - [Collection Expressions Next (C#13 and beyond)](#collection-expressions-next-c13-and-beyond) - [Dictionary expressions](#dictionary-expressions) - [Extending patterns to "as"](#extending-patterns-to-as) - [`params in` parameters](#params-in-parameters) - [Triage (working set)](#triage-working-set) - [Only Allow Lexical Keywords in the Language](#only-allow-lexical-keywords-in-the-language) - [Permit variable declarations under disjunctive patterns](#permit-variable-declarations-under-disjunctive-patterns) - [CallerCharacterNumberAttribute](#callercharacternumberattribute) - [`Task` nullability covariance](#taskt-nullability-covariance) - [Nullable analysis of LINQ queries](#nullable-analysis-of-linq-queries) ## Quote of the Day - "The power I have is that I get to interpret" ## Discussion ### Triage (no milestone) Today we triaged our championed issues without milestones, and started going through our working set. #### Type inference using method group natural type Issue: https://github.com/dotnet/csharplang/issues/7687 We need to investigate and make sure we didn't intentionally not do this in C# 10. Once we figure that out, we think it can to into Any Time. Until then, Needs More Work. #### Collection Expressions Next (C#13 and beyond) Issue: https://github.com/dotnet/csharplang/issues/7913 This is an epic tracking multiple smaller improvements, and the champion is currently on vacation so we can't talk through all the individual things. We'll come back when the issue champion is back from vacation. #### Dictionary expressions Issue: https://github.com/dotnet/csharplang/issues/7822 We've actively worked on this in the past year and continue to think about it. Working set. #### Extending patterns to "as" Issue: https://github.com/dotnet/csharplang/issues/8210 We don't like that this would collapse the success and failure branches of an `is` into _null_ and _not null_, that would then need to be checked again to actually use the value; part of the point of `is` is to get rid of that type of code. We think the expanded versions of the examples are more readable. This proposal is rejected. #### `params in` parameters Issue: https://github.com/dotnet/csharplang/issues/8301 There's some interesting design work here around `ref struct`s that would push us to further expand our lifetime tracking, but we're not sure that this issue, by itself, would meet the bar of doing that work. We don't think that it's a bad idea to add, particularly if we start by blocking `ref struct`s from participating, so we'll put this in the working set. ### Triage (working set) Next, we started going through our working set issues to see if there's anything we should pull out. We didn't get through the whole thing; we stopped after 3590. The notes below only include issues that we decided to change something about. #### Only Allow Lexical Keywords in the Language Issue: https://github.com/dotnet/csharplang/issues/4460 This issue was a forcing function to get us to think about breaking changes. We've thought about breaking changes, and our approach to `field`, while breaking, is significantly less breaking than this issue proposes. We may pull it out of our back pocket in a future release where we're more comfortable with breaking changes in general, but until then, this is moved to the backlog. #### Permit variable declarations under disjunctive patterns Issue: https://github.com/dotnet/csharplang/issues/4018 We do still want this feature, but aren't doing any active design work on it right now. Therefore, we'll move it to the backlog. #### CallerCharacterNumberAttribute Issue: https://github.com/dotnet/csharplang/issues/3992 This was a proposal for how we might implement interceptors. We don't plan on doing this, this proposal is rejected. #### `Task` nullability covariance Issue: https://github.com/dotnet/csharplang/issues/3950 We do still want this feature, but aren't doing any active design work on it right now. Therefore, we'll move it to the backlog. #### Nullable analysis of LINQ queries Issue: https://github.com/dotnet/csharplang/issues/3951 We do still want this feature, but aren't doing any active design work on it right now. Therefore, we'll move it to the backlog. ================================================ FILE: meetings/2024/LDM-2024-09-11.md ================================================ # C# Language Design Meeting for September 11th, 2024 ## Agenda - [Better conversion from collection expression and `params` collections](#better-conversion-from-collection-expression-and-params-collections) - [First-class span types](#first-class-span-types) - [`Reverse`](#reverse) - [New ambiguities](#new-ambiguities) - [Covariant arrays](#covariant-arrays) ## Quote of the Day - "I think I have the record for how many times my brain stumbled on this today." ## Discussion ### Better conversion from collection expression and `params` collections Champion issues: https://github.com/dotnet/csharplang/issues/8374, https://github.com/dotnet/csharplang/issues/7700 Spec change: https://github.com/dotnet/csharplang/pull/8393 We started today by looking at an explicit consequence of our better conversion from collection expression changes and ensuring that we were ok with it. In our view of element type being the most important thing, this is a natural consequence and we're ok with it. We're also ok with how it aligns more with how `params` arrays has always worked; for example, `params int[]` has always been ambiguous with `params int?[]` when called with 0 elements, and this lines up with that behavior. We also looked at the proposed change to `params` collections. This is also in the name of consistency; we want to make the rules for the new feature consistent with the new rules for the old feature. All together, these changes bring everything together with near-complete consistency, which we strongly like. #### Conclusion Proposed rules are adopted. ### First-class span types Champion issue: https://github.com/dotnet/csharplang/issues/7905 Finally today, we went over some possible breaking changes that will be caused by first-class spans. We knew going into this that there would be breaking changes, so that we have them isn't a surprise, but in the name of due diligence we want to go through them and make sure that we're comfortable with them. #### `Reverse` https://github.com/dotnet/csharplang/blob/38c28b88c3e9ea9fb076b39c8d204f2b189b6796/proposals/first-class-span-types.md#calling-reverse-on-an-array The word `Reverse` is unfortunately too useful; it's both in `LINQ`, as "Please give me a stream that is the reverse of my input stream", as well as in `MemoryExtensions` as "Please in-place reverse this `Span`". Currently, only the former thing is applicable. After this feature, both will be applicable, and the `Span` version will win. There is a workaround for the BCL (add `Reverse(array)` to `Enumerable`), which we expect them to do to address this break. Ultimately, we're ok with this break; it's the way that we'd have wanted it to be in the first place, and the overall likelihood that there are identically named methods with different semantics is pretty low, as that usually breaks the guidance in the API design guidelines: https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/member-overloading. #### New ambiguities https://github.com/dotnet/csharplang/blob/38c28b88c3e9ea9fb076b39c8d204f2b189b6796/proposals/first-class-span-types.md#ambiguities For this case (which arises in assert methods like `Assert.Equals` in xUnit, NUnit, and MSTest), it's a bit unfortunate, but there are also several options available to API authors. In many ways, this is simply a historical design artifact of the lack of `ReadOnlySpan` and the lack of this conversion in the first place: if these APIs were to be rewritten just with this feature, then they could just have exposed `ReadOnlySpan` overloads and been done with it. We see two possible avenues for these APIs: * Apply `OverloadResolutionPriority(-1)` to their array-based overloads, so everything goes through `ReadOnlySpan`. * Expose an `Assert.Equals(ReadOnlySpan expected, T[] actual)`, which will be preferred for `Assert.Equals([1, 2], somethingThatIsAnArray)`. #### Covariant arrays https://github.com/dotnet/csharplang/blob/38c28b88c3e9ea9fb076b39c8d204f2b189b6796/proposals/first-class-span-types.md#covariant-arrays Covariant arrays, our favorite feature, strikes again. However, we're unsure that this is a real case that users will hit. We'll certainly document it, but this particular code example came from our unit tests, which aren't always reflective of real things users will write. Given this, we're also ok with this break. #### Conclusion We accept all of these breaks. ================================================ FILE: meetings/2024/LDM-2024-09-18.md ================================================ # C# Language Design Meeting for September 18th, 2024 ## Agenda - [Nullability in `field`](#nullability-in-field) - [Extensions naming](#extensions-naming) ## Quote of the Day - "Rename roles to frijoles" ## Discussion ### Nullability in `field` Champion issue: https://github.com/dotnet/csharplang/issues/140 Discussed proposal: https://github.com/dotnet/csharplang/issues/8425 We started today by looking at how we're going to deliver nullability rules for the `field` preview that ships in November with .NET 9. While we still want a full inference system, we don't think that we reasonably have time to implement it before the first preview goes out. Therefore, we need to plan what our initial preview will ship with for options. We have: 1. No special support. To suppress nullability warnings, `= null!;` will be required. 2. Support for the nullability attributes with the `field` attribute target, allowing a user to mark the backing field as `[field: MaybeNull, AllowNull]`. 3. Leave the backing field oblivious, and do not report nullability warnings on it for now. After some discussion, we like option 2. Both option 1 and 2 do leave some cruft for the user to clean up after we have inference, but option 1 leaves the code in a potentially unsafe state; option 2 is planned to be supported in the inference scenario anyways, and it leaves the user's code in a safe state. Option 3 doesn't leave any cruft, but it does mean that users will be operating in a null-unsafe state before they get the new warnings. #### Conclusion We will attempt to ship option 2, support for `field`-targeted nullability attributes. If we do not have enough runway however, we will end up shipping 1 by pure virtue of it requiring no additional compiler work. ### Extensions naming Champion issue: https://github.com/dotnet/csharplang/issues/5497 Rename proposal: https://github.com/dotnet/csharplang/blob/1e8255a438517bc3ad067c726c28cfa20cb60f1e/meetings/working-groups/extensions/rename-to-roles-and-extensions.md Finally today, we are reopening the topic of naming for the feature that has alternately been known as "shapes", "roles & extensions", and now "extensions". When we [previously settled](../2023/LDM-2023-02-22.md#extensions) on the `implicit extension` and `explicit extension`, we felt that we'd reached a breakthrough; this was a name and category system that jibed well with the LDT, and we felt it added clarity and would help explain this feature to customers. However, now that we've had a year and a half to use this name with the broader C# community, we no longer feel that this connection adds clarity to the feature; most of us feel that some kind of different noun for `explicit extension` would be better. One example where some of us find issue: naming conventions for these types. Many existing `static class`es that are used for extension methods are named something like `ArrayExtensions`, and while such a convention works just fine for `implicit extension` types, we're not convinced that would work well for `explicit extension`s; we think of those as something like `Person`, not `JsonObjectPersonExtension` or the like. And by tying these features to the same noun, we then imply that these naming conventions _should_ be related. These types of ties continue throughout the feature, and a read of the room after this discussion was particularly telling; the vast majority of the LDT thought that separate nouns was the correct path. A couple of members were unsure, and no one was convinced that a single noun + modifier was the correct path forward. Given this, we will plan on going back to separate nouns. Of course, that then immediately opened the floor for which noun to use. We're not ready to make decisions on nouns today, but we did brainstorm a list to do some initial preferencing: * `role` - The original proposal. We like that it's clear that the underlying object is assuming a "role"; it's all it was before, but more specific. Some members do have a gut feeling that there's something wrong with it, but haven't yet been able to put it into words. * `adapter` - Lots of initial dislike for this one. Adapter as a pattern implies that maybe you could use it to adapt one class to be another, which this feature will not allow. * `alias` - This doesn't imply anything about the additive nature of the feature, which feels like a bit of a misstep. * `view` - This one is also decently well-liked, but there's some concern that the word `view` is already too overloaded, even within the .NET ecosystem. Database views, our various UI frameworks, etc; it gives us pause that we'd be able to explain this well, which is the concern that brought us back here in the first place. * `shape` - This one has a lot of previous baggage around implicit implementation, and we don't want to imply that we're introducing generalized structural typing to C#. * `extension type` - Very wordy. It does relate to what was previously known as `implicit extension`s, which is now just `extension`s, but as discussed previously, we're not sure that's a benefit. * `layer` - Wasn't discussed in depth, no one had strong feelings on this one. * `facade` - Wasn't discussed in depth, no one had strong feelings on this one. * `abstraction` - Wasn't discussed in depth, no one had strong feelings on this one. * `augmentation`/`augment` - This is what F# calls their extension methods, though they don't use it as a keyword. We're a bit wary of conflating that. * `intent` - This makes some people think of Android first, rather than a type system thing. Of these, we had 2 clear standouts during initial preferencing: `role`, with most of the LDM liking it, and `view`, with a majority at least being ok with it, but not as many liking it as `role`. We'll let this one sit for a couple of weeks, and then come back with more thoughts. #### Conclusion The general idea is approved. `implicit extension` is now just `extension`. We don't yet have a name for `explicit extension`, but `role` is a standout leader, with `view` being another possible contender. ================================================ FILE: meetings/2024/LDM-2024-09-30.md ================================================ # C# Language Design Meeting for September 30th, 2024 ## Agenda - [Extensions](#extensions) ## Quote of the Day - "Are you converting the topic?" "Yes, I'm explicitly converting it, so it's kinda dangerous" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/5497 Related: https://github.com/dotnet/csharplang/blob/1e8255a438517bc3ad067c726c28cfa20cb60f1e/meetings/working-groups/extensions/extensions-as-static-types.md Today, we looked at a proposal for scoping the work involved in extensions to something more manageable for initial previews. Extensions being able to be used as types adds many complexities to the design, and by forbidding this, we may be able to get previews out sooner for earlier feedback. Extensions depend on this their typeness in two major cases: * The type of `this` inside the extension * Their disambiguation syntax The latter is something that we've had more general proposals around before, particularly around calling DIMs from implementing types. Such a syntax could serve us here as well, and if we go with the restriction, is something that we want to seriously investigate. While a majority of the LDM is ok with allowing extensions to ship without an explicit syntax for disambiguation, a plurality is not ok with this. We prefer to reach consensus, rather than simply doing majority rules, so we'll definitely be investigating this and bringing back results to the LDM for further discussion. The type of `this` is a bit harder, particularly if we ever want to re-expand back to allowing `extension`s to be able to be local types. For example: ```cs extension E for object { public void M() { Console.WriteLine(Identity(this)); string Identity(T t) => typeof(T).Name; } } ``` If we change the type of `this` at a later date, then this goes from printing `object` to printing `E`. While we may end up being ok with such a breaking change, we still need to acknowledge that it is a breaking change. Another part of making `this` be the underlying type is that it means that, when shadowing a base type member, it's very difficult to call the shadowing member. Consider: ```cs extension E for string { public int Length => 10; public void M() { Console.WriteLine(this.Length); // This calls `string.Length`, not `E.Length` } } ``` We should consider warning in these scenarios, to let the user know that they're doing something that is almost certainly useless and not what they intended to do (or at the very least, likely will not work the way they expected it to). Ultimately, we think we're ok with continuing to explore this restricted space. We want to be able to get previews out into our user's hands sooner rather than later, and this seems like a viable approach to managing the large complexity of the feature. #### Conclusion Restricting `extension`s from being instance types is tentatively approved. We will also investigate disambiguation syntax for calling specific members, a more general feature than just for `extension`s. ================================================ FILE: meetings/2024/LDM-2024-10-02.md ================================================ # C# Language Design Meeting for October 2nd, 2024 ## Agenda - [Open questions in field](#open-questions-in-field) - [`readonly` contexts and `set`](#readonly-contexts-and-set) - [`Conditional` code](#conditional-code) - [Interface properties and auto-accessors](#interface-properties-and-auto-accessors) - [Extensions](#extensions) ## Quote of the Day - "Attempting to find my way to this room was like navigating the Severance basement" ## Discussion ### Open questions in field Champion issue: https://github.com/dotnet/csharplang/issues/140 Questions: https://github.com/dotnet/csharplang/blob/3cca17a650e40d787629e52a8d46e59459cb2b74/proposals/field-keyword.md#open-ldm-questions #### `readonly` contexts and `set` There could be scenarios for this where you're implementing a `set` accessor on a `readonly` struct and either passing it through, or throwing. Recommendation is accepted, we will allow this. #### `Conditional` code `Conditional` code can have effects on non-conditional code, such as `Debug.Assert` changing nullability. It would be strange if `field` didn't have similar impacts. It is also unlikely to come up in most code, so we'll do the simple thing and accept the recommendation. #### Interface properties and auto-accessors Standardizing around the instance field itself being the cause of the error is consistent with partial properties in classes, and we like that outcome. The recommendation is accepted. ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/5497 Related: https://github.com/dotnet/csharplang/blob/1e8255a438517bc3ad067c726c28cfa20cb60f1e/meetings/working-groups/extensions/extensions-as-static-types.md, https://github.com/dotnet/csharplang/pull/8472 Today, we continued our thinking around extensions from [last time](./LDM-2024-09-30.md#extensions), and around how we want to approach the complex issues standing in the way of us getting previews out. Unfortunately for us, this ended up raising far more questions than answers. The focus today was around aligning with method type inference for existing extension methods. As an example: ```cs public class C { public void M(I i, out object o) { i.M(out o); // infers E.M i.M2(out o); // error CS1503: Argument 1: cannot convert from 'out object' to 'out string' } } public static class E { public static void M(this I i, out T t) { t = default; } } public static extension E2 for I { public static void M2(out T t) { t = default; } } public interface I { } ``` As currently envisioned, extension types have a 2-stage type inference approach. The first stage deduces the type parameter for the extension type, based on the receiver, and the second deduces any type parameters on the method (if there are any). This is very different to how today's extension methods work; since all type parameters are on the method, they're all inferred at the same time, and the receiver is just one of the parameters. This is an example of a behavior difference that we'd incur between old and new extension methods, and we want to see if we can solve it. One possibility is a single pass, that pretends the type parameters from the extension type are part of the method signature. Then we do the same single pass as in extension methods today, and it all behaves the same. This comes with its own downsides, however, which is breaking a _different_ expectation from C# users around how type parameters work. There is no place in C# today where type parameters from different definitions are smashed together and inferred at the same time. A single-stage inference pass will look and feel different to how the syntax implies that it would work. Some members feel that the two-stage approach is the more logical approach in general, and actually appreciate that VB did this with classic extension methods. We then further got into discussions around compat, and how/if existing extensions methods could move to the new form. As an example, could `LINQ` be written in the new form? We're somewhat concerned that it couldn't be, at least not as designed today; there's an `Enumerable` class with a large number of extensions in it, many on differing types. Adopting the new form would of course at least break binary compat, since we can't define all those methods in a single extension type. We'd then further be concerned that this would block the BCL from using the new form in conjunction with the old form, due to concerns about the differing inference methodology, as well as other behavioral differences between the forms. This has finally brought us squarely back to the questions on syntax. If the current syntax implies a particular behavior, and that behavior would block adoption, perhaps a different form is needed. We could consider a more member-centric approach, rather than a type-centric approach. We're definitely not ready to make any calls on this today, but we shall examine these ideas further in the next LDM. ================================================ FILE: meetings/2024/LDM-2024-10-07-extension-compat.md ================================================ # Extension compat 1. To what extent can new extension methods replace old ones without a change in behavior? 2. What are your options when they can't? ## Kinds of compat 1. Binary compat - code compiled against old extension methods works the same way against new ones 2. Source compat - code that compiled against old extension methods compiles and works the same way against new ones a. When used as extension methods b. When used as static methods ## Priority between old and new extension methods We have three options for how to handle lookup between competing old and new extension methods in a given scope, giving preference to either the former, the latter or neither. ### Neither is preferred This is only an option if we can somehow get old and new extension methods into the same candidate set. If we do this, old extension methods must be removed as soon as new replacements are made - coexistence would result in ambiguities. Compat breaks would arise when the new method doesn't apply in the same circumstances, or behaves differently when it does. It is possible that we could mitigate this with an attribute like `[OverloadResolutionPriority]`, deferring to one only when the other doesn't apply. ### New extensions are preferred This would allow old extension methods to stay around as backup methods. Compat breaks would arise whenever a new extension method does not - or cannot - behave exactly as the old one. It is possible for migrated extension methods to start silently shadowing unrelated old ones that would have previously been the best fit. ### Old extensions are preferred This would allow new versions of extension methods to be declared alongside other extension members in the new style, even as old ones would be preferred for compat reasons, if kept around. This might allow a mitigation period while compat breaks are identified and addressed: old ones could be brought into scope in a given file until compat issues have been dealt with. ## Preference/disambiguation syntax New extension methods will likely have a different disambiguation approach that the old ones, where you just call the static method. Any such calls would possibly be broken when using the new declaration syntax, both from a source and binary compat point of view. It's possible that we could promise to generate static methods with exactly the same signature from new extension methods, at least in a large and well-defined number of cases. Alternatively, the old extension methods could be kept around as ordinary static methods for this purpose. ## Type inference The current design of new extensions splits generic type inference into two parts: one for a generic underlying type (which needs to be improved somewhat) and one for a generic extension method. This leads to different results in corner cases, including whether the extension method applies to a given receiver, and which type arguments are inferred when it does. Those situations constitute source compat breaks. There is a proposal to rejoin the two phases into one in the new approach. Chances are that if we did, few people would notice a difference. For any non-method extension members there probably wouldn't *be* a difference, since only the underlying type would be potentially generic anyway. So this would be a possible mitigation. ## Name of enclosing class Old extension methods of different receiver types can be grouped together in one static class. The current design for extension types requires a different extension type declaration for each underlying type. In such situations, when porting old extension methods, some will end up in a generated class with a different name than before. This compromises binary compat of callsites, as well as source compat when used directly as static methods. It is possible that attributes could be used to force generated extension methods onto a different class than the one they are declared in. Or extension members could have a syntax to override the underlying type specified by the extension type. ## Refness of receiver Old extension methods allow receivers to be `ref` or `in` parameters only on value types. New extensions automatically manage ref-ness so that it matches the behavior of `this` in type declarations: In reference types it is a value parameter, and value types it is a `ref` parameter. This is the case even when the underlying type is an unconstrained type parameter - callsites are generated to manage this at runtime! The generated method itself in these cases takes a ref parameter as the receiver, and the callsite will take a copy when the type argument is a reference type. This means that there is no compatible way to port an extension method that has a value-parameter receiver of a value type or unconstrained generic type, and unconstrained generic extension methods would be generated with a ref instead of a value parameter. We could potentially mitigate this with an attribute on the member to prevent ref generation, or as part of a feature to allow extension members to "override" their underlying type. ## Nullability of receiver Old extension methods can - and very occasionally do - have nullable reference types as receiver types, whereas the current design for new extension methods does not allow for that. Even if `for C?` syntax was allowed for underlying reference types, it's unlikely that you would want the underlying type to be nullable for every extension member on that type. We could mitigate this through a memberwise attribute, or as part of a feature to allow extension members to "override" their underlying type. ================================================ FILE: meetings/2024/LDM-2024-10-07.md ================================================ # C# Language Design Meeting for October 7th, 2024 ## Agenda - [Extensions](#extensions) ## Quote(s) of the Day - "If I had a penny `foreach` code-based dad joke..." - "If we can have our cake and eat it too, I love it. I like cake, and I like eating." ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/5497 Related: https://github.com/dotnet/csharplang/blob/1e8255a438517bc3ad067c726c28cfa20cb60f1e/meetings/working-groups/extensions/extensions-as-static-types.md, https://github.com/dotnet/csharplang/pull/8472 Continuing our discussion from [last time](LDM-2024-10-02.md#extensions), we started to explore the pros and cons of moving to a per-member syntax, instead of per-type. Per-member would naturally lead to a single-pass generic inference algorithm, since everything is in the same signature, but brings its own complexities. Today, properties and operators cannot introduce type parameters; in order to extend generic types, a per-member syntax would need to come up with a way to introduce that; it seems unlikely that we would want to require that any extension properties or operators must be on substituted generic types (to put it mildly). However, we're concerned that per-member syntax could be worse overall; it doesn't improve on how a series of extension methods today are required to repeat the same type over and over. It also looks closer to what an actual member of the original type looks like. This, of course, then leads to its own set of problems, such as the inability to specify whether `this` is `ref` or `scoped` for structs. And the compat story becomes, as previously discussed, more complex. One thing we clarified during discussion is that, even though we've arrived at the idea that extension types aren't actually types and can't be the type of an instance variable today, we haven't ruled out that extension types could implement interfaces in the future, allowing the user to implicitly treat the underlying type as having implemented that interface without having to do any casts. To try and help drive conversation, we also went through some discussion on what compat means in the context of extensions, reviewing [this document](LDM-2024-10-07-extension-compat.md). Compat can mean "keep your existing methods as they are, unchanged, and put new stuff in a new extension type", or it could mean "everything can move forward to the new form". Along the latter line, we also started thinking about whether there's a unifying `for` clause that can applied to members as well as on types, and then have some sort of grow-up story between them. The story would likely still be complex; the generic inference question is still gnarly. But we think that this is the next area to explore, and will come back next meeting with proposals on it to consider. ================================================ FILE: meetings/2024/LDM-2024-10-09.md ================================================ # C# Language Design Meeting for October 9th, 2024 ## Agenda - [Extensions](#extensions) ## Quote of the Day - "And then I had a final point that is evading me right now and I was trying to stretch my words to see if it pops in there, but it hasn't quite yet, so I'm going to give it over to " ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/5497 Related: https://github.com/dotnet/csharplang/blob/d5b8e7808998024e4aa6b380acdccac30aa03b60/proposals/extensions_v2.md, https://github.com/dotnet/csharplang/blob/1e8255a438517bc3ad067c726c28cfa20cb60f1e/meetings/working-groups/extensions/Compatibility%20through%20coexistence%20between%20extension%20types%20and%20extension%20methods.md Coming back from [last time](LDM-2024-10-07.md#extensions), we wanted to explore different compatibility stories for extensions, and look at what a member-based syntax would look like. We spent the first half of the meeting listening to presentations about the 2 different explorations that came from this; the first exploration is maximally binary compatible, using a member-based `for` syntax that enables all current extension methods to move to a newer form while still being 100% binary compatible with their existing form. The second proposal we went over was how interop would work in a world where we don't do this, and instead say that existing extension methods are here to stay, forever; the new way is the new way, and if a user wants perfect binary compat, the only way they get it is through keeping their existing methods as they are. We came up with a few concerns for each proposal through discussion in the second half. * Even if there's a fully compatible new syntax, we can't eliminate old style extension methods from the world. Old codebases will continue to exist, and many users will simply not move forward for a variety of reasons. * The fully compatible proposal will also leave us in a world with 3 different types of extensions: old style, new member style, and eventually, new type style. We will likely still need to have the discussion on how generic inference will work in the new type style extensions, as all our previous arguments around looking more like types than like members will still apply. * The least compatible proposal ends up exposing each decision through refactorings. There will undoubtedly be fixers from both the Roslyn team and other environments to help users move forward to the new form wherever possible, and anywhere the fixers can't run because the meaning changes exposes design decisions. * It's possible that the second proposal is somewhat of a subset of the first; is it possible that we could do the second, get that into preview, then wait for feedback on whether a fully compatible form is necessary? * On the other hand, it's possible that we may want to avoid a type syntax entirely for extensions if we went with the member approach, as it would muddy understanding of what the feature is: a fancy place to put a parameter, that's it. * Finally, if we go with the member approach, we need to discuss whether we want to support generic properties and operators in general, since it's an obvious next question if we allow extension operators and properties to be generic. We didn't come to any conclusions today, so we'll be back again with more extensions in the future. ================================================ FILE: meetings/2024/LDM-2024-10-14-Enumerable-extension.cs ================================================ namespace System.Linq; public extension Enumerable for IEnumerable { public T Aggregate(Func func); public TAccumulate Aggregate(TAccumulate seed, Func func); public TResult Aggregate(TAccumulate seed, Func func, Func resultSelector); public IEnumerable> AggregateBy(Func keySelector, TAccumulate seed, Func func, IEqualityComparer? keyComparer = null) where TKey : notnull; public IEnumerable> AggregateBy(Func keySelector, Func seedSelector, Func func, IEqualityComparer? keyComparer = null) where TKey : notnull; public bool All(Func predicate); public bool Any(); public bool Any(Func predicate); public IEnumerable Append(T element); public IEnumerable AsEnumerable(); public float? Average(Func selector); public double? Average(Func selector); public double? Average(Func selector); public double? Average(Func selector); public decimal? Average(Func selector); public double Average(Func selector); public double Average(Func selector); public double Average(Func selector); public decimal Average(Func selector); public float Average(Func selector); public IEnumerable Chunk(int size); public IEnumerable Concat(IEnumerable other); public bool Contains(T value); public bool Contains(T value, IEqualityComparer? comparer); public int Count(); public int Count(Func predicate); public IEnumerable> CountBy(Func keySelector, IEqualityComparer? keyComparer = null) where TKey : notnull; public IEnumerable DefaultIfEmpty(); public IEnumerable DefaultIfEmpty(T defaultValue); public IEnumerable Distinct(); public IEnumerable Distinct(IEqualityComparer? comparer); public IEnumerable DistinctBy(Func keySelector); public IEnumerable DistinctBy(Func keySelector, IEqualityComparer? comparer); public T ElementAt(Index index); public T ElementAt(int index); public T? ElementAtOrDefault(Index index); public T? ElementAtOrDefault(int index); public IEnumerable Except(IEnumerable other); public IEnumerable Except(IEnumerable other, IEqualityComparer? comparer); public IEnumerable ExceptBy(IEnumerable other, Func keySelector); public IEnumerable ExceptBy(IEnumerable other, Func keySelector, IEqualityComparer? comparer); public T First(Func predicate); public T First(); public T FirstOrDefault(Func predicate, T defaultValue); public T FirstOrDefault(T defaultValue); public T? FirstOrDefault(); public T? FirstOrDefault(Func predicate); public IEnumerable GroupBy(Func keySelector, Func, TResult> resultSelector, IEqualityComparer? comparer); public IEnumerable GroupBy(Func keySelector, Func elementSelector, Func, TResult> resultSelector, IEqualityComparer? comparer); public IEnumerable GroupBy(Func keySelector, Func elementSelector, Func, TResult> resultSelector); public IEnumerable GroupBy(Func keySelector, Func, TResult> resultSelector); public IEnumerable> GroupBy(Func keySelector, IEqualityComparer? comparer); public IEnumerable> GroupBy(Func keySelector, Func elementSelector); public IEnumerable> GroupBy(Func keySelector, Func elementSelector, IEqualityComparer? comparer); public IEnumerable> GroupBy(Func keySelector); public IEnumerable GroupJoin(IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector); public IEnumerable GroupJoin(IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector, IEqualityComparer? comparer); public IEnumerable<(int Index, T Item)> Index(); public IEnumerable Intersect(IEnumerable other); public IEnumerable Intersect(IEnumerable other, IEqualityComparer? comparer); public IEnumerable IntersectBy(IEnumerable other, Func keySelector); public IEnumerable IntersectBy(IEnumerable other, Func keySelector, IEqualityComparer? comparer); public IEnumerable Join(IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector); public IEnumerable Join(IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer? comparer); public T Last(Func predicate); public T Last(); public T LastOrDefault(Func predicate, T defaultValue); public T LastOrDefault(T defaultValue); public T? LastOrDefault(); public T? LastOrDefault(Func predicate); public long LongCount(); public long LongCount(Func predicate); public decimal Max(Func selector); public double Max(Func selector); public TResult? Max(Func selector); public int Max(Func selector); public long Max(Func selector); public int? Max(Func selector); public double? Max(Func selector); public long? Max(Func selector); public float? Max(Func selector); public float Max(Func selector); public decimal? Max(Func selector); public T? Max(IComparer? comparer); public T? Max(); public T? MaxBy(Func keySelector, IComparer? comparer); public T? MaxBy(Func keySelector); public double Min(Func selector); public int Min(Func selector); public long Min(Func selector); public decimal? Min(Func selector); public double? Min(Func selector); public decimal Min(Func selector); public long? Min(Func selector); public float? Min(Func selector); public float Min(Func selector); public TResult? Min(Func selector); public int? Min(Func selector); public T? Min(IComparer? comparer); public T? Min(); public T? MinBy(Func keySelector, IComparer? comparer); public T? MinBy(Func keySelector); public IOrderedEnumerable Order(IComparer? comparer); public IOrderedEnumerable Order(); public IOrderedEnumerable OrderBy(Func keySelector); public IOrderedEnumerable OrderBy(Func keySelector, IComparer? comparer); public IOrderedEnumerable OrderByDescending(Func keySelector); public IOrderedEnumerable OrderByDescending(Func keySelector, IComparer? comparer); public IOrderedEnumerable OrderDescending(Comparer? comparer); public IOrderedEnumerable OrderDescending(); public IEnumerable Prepend(T element); public IEnumerable Reverse(); public IEnumerable Select(Func selector); public IEnumerable Select(Func selector); public IEnumerable SelectMany(Func> collectionSelector, Func resultSelector); public IEnumerable SelectMany(Func> selector); public IEnumerable SelectMany(Func> selector); public IEnumerable SelectMany(Func> collectionSelector, Func resultSelector); public bool SequenceEqual(IEnumerable other); public bool SequenceEqual(IEnumerable other, IEqualityComparer? comparer); public T Single(); public T Single(Func predicate); public T? SingleOrDefault(); public T SingleOrDefault(T defaultValue); public T? SingleOrDefault(Func predicate); public T SingleOrDefault(Func predicate, T defaultValue); public IEnumerable Skip(int count); public IEnumerable SkipLast(int count); public IEnumerable SkipWhile(Func predicate); public IEnumerable SkipWhile(Func predicate); public float Sum(Func selector); public int Sum(Func selector); public long Sum(Func selector); public decimal? Sum(Func selector); public double Sum(Func selector); public int? Sum(Func selector); public long? Sum(Func selector); public float? Sum(Func selector); public double? Sum(Func selector); public decimal Sum(Func selector); public IEnumerable Take(Range range); public IEnumerable Take(int count); public IEnumerable TakeLast(int count); public IEnumerable TakeWhile(Func predicate); public IEnumerable TakeWhile(Func predicate); public T[] ToArray(); public Dictionary ToDictionary(Func keySelector, Func elementSelector, IEqualityComparer? comparer) where TKey : notnull; public Dictionary ToDictionary(Func keySelector, Func elementSelector) where TKey : notnull; public Dictionary ToDictionary(Func keySelector, IEqualityComparer? comparer) where TKey : notnull; public Dictionary ToDictionary(Func keySelector) where TKey : notnull; public HashSet ToHashSet(); public HashSet ToHashSet(IEqualityComparer? comparer); public List ToList(); public ILookup ToLookup(Func keySelector, Func elementSelector); public ILookup ToLookup(Func keySelector, Func elementSelector, IEqualityComparer? comparer); public ILookup ToLookup(Func keySelector); public ILookup ToLookup(Func keySelector, IEqualityComparer? comparer); public bool TryGetNonEnumeratedCount(out int count); public IEnumerable Union(IEnumerable other); public IEnumerable Union(IEnumerable other, IEqualityComparer? comparer); public IEnumerable UnionBy(IEnumerable other, Func keySelector); public IEnumerable UnionBy(IEnumerable other, Func keySelector, IEqualityComparer? comparer); public IEnumerable Where(Func predicate); public IEnumerable Where(Func predicate); public IEnumerable<(T First, TSecond Second)> Zip(IEnumerable second); public IEnumerable Zip(IEnumerable second, Func resultSelector); public IEnumerable<(T First, TSecond Second, TThird Third)> Zip(IEnumerable second, IEnumerable third); } public extension Enumerable for IEnumerable { public IEnumerable Cast(); public IEnumerable OfType(); // Non-extension static methods - can they live here too? public abstract static IEnumerable Empty(); public abstract static IEnumerable Range(int start, int count); public abstract static IEnumerable Repeat(TResult element, int count); } public extension OrderedEnumerable for IOrderedEnumerable { public IOrderedEnumerable ThenBy(Func keySelector); public IOrderedEnumerable ThenBy(Func keySelector, IComparer? comparer); public IOrderedEnumerable ThenByDescending(Func keySelector); public IOrderedEnumerable ThenByDescending(Func keySelector, IComparer? comparer); } public extension KeyValuePairEnumerable for IEnumerable> where TKey : notnull { public Dictionary ToDictionary(); public Dictionary ToDictionary(IEqualityComparer? comparer); } public extension KeyValueTupleEnumerable for IEnumerable<(TKey, TValue)> where TKey : notnull { public Dictionary ToDictionary(); public Dictionary ToDictionary(IEqualityComparer? comparer); } public extension IntEnumerable for IEnumerable { public double Average(); public int Max(); public int Min(); public int Sum(); } public extension LongEnumerable for IEnumerable { public double Average(); public long Max(); public long Min(); public long Sum(); } public extension FloatEnumerable for IEnumerable { public float Average(); public float Max(); public float Min(); public float Sum(); } public extension DoubleEnumerable for IEnumerable { public double Average(); public double Max(); public double Min(); public double Sum(); } public extension DecimalEnumerable for IEnumerable { public decimal Average(); public decimal Max(); public decimal Min(); public decimal Sum(); } public extension NullableIntEnumerable for IEnumerable { public double? Average(); public int? Max(); public int? Min(); public int? Sum(); } public extension NullableLongEnumerable for IEnumerable { public double? Average(); public long? Max(); public long? Min(); public long? Sum(); } public extension NullableFloatEnumerable for IEnumerable { public float? Average(); public float? Max(); public float? Min(); public float? Sum(); } public extension NullableDoubleEnumerable for IEnumerable { public double? Average(); public double? Max(); public double? Min(); public double? Sum(); } public extension NullableDecimalEnumerable for IEnumerable { public decimal? Average(); public decimal? Max(); public decimal? Min(); public decimal? Sum(); } ================================================ FILE: meetings/2024/LDM-2024-10-14-Enumerable-extensions.cs ================================================ namespace System.Linq; extension Enumerable { public TSource Aggregate(Func func) for IEnumerable source; public TAccumulate Aggregate(TAccumulate seed, Func func) for IEnumerable source; public TResult Aggregate(TAccumulate seed, Func func, Func resultSelector) for IEnumerable source; public IEnumerable> AggregateBy(Func keySelector, TAccumulate seed, Func func, IEqualityComparer? keyComparer = null) for IEnumerable source where TKey : notnull; public IEnumerable> AggregateBy(Func keySelector, Func seedSelector, Func func, IEqualityComparer? keyComparer = null) for IEnumerable source where TKey : notnull; public bool All(Func predicate) for IEnumerable source; public bool Any() for IEnumerable source; public bool Any(Func predicate) for IEnumerable source; public IEnumerable Append(TSource element) for IEnumerable source; public IEnumerable AsEnumerable() for IEnumerable source; public float? Average(Func selector) for IEnumerable source; public double? Average(Func selector) for IEnumerable source; public double? Average(Func selector) for IEnumerable source; public double? Average(Func selector) for IEnumerable source; public decimal? Average(Func selector) for IEnumerable source; public double Average(Func selector) for IEnumerable source; public double Average(Func selector) for IEnumerable source; public double Average(Func selector) for IEnumerable source; public decimal Average(Func selector) for IEnumerable source; public double? Average() for IEnumerable source; public float? Average() for IEnumerable source; public double? Average() for IEnumerable source; public double? Average() for IEnumerable source; public float Average(Func selector) for IEnumerable source; public decimal? Average() for IEnumerable source; public double Average() for IEnumerable source; public double Average() for IEnumerable source; public double Average() for IEnumerable source; public decimal Average() for IEnumerable source; public float Average() for IEnumerable source; public IEnumerable Cast(this IEnumerable source); public IEnumerable Chunk(int size) for IEnumerable source; public IEnumerable Concat(IEnumerable second) for IEnumerable first; public bool Contains(TSource value) for IEnumerable source; public bool Contains(TSource value, IEqualityComparer? comparer) for IEnumerable source; public int Count() for IEnumerable source; public int Count(Func predicate) for IEnumerable source; public IEnumerable> CountBy(Func keySelector, IEqualityComparer? keyComparer = null) for IEnumerable source where TKey : notnull; public IEnumerable DefaultIfEmpty() for IEnumerable source; public IEnumerable DefaultIfEmpty(TSource defaultValue) for IEnumerable source; public IEnumerable Distinct() for IEnumerable source; public IEnumerable Distinct(IEqualityComparer? comparer) for IEnumerable source; public IEnumerable DistinctBy(Func keySelector) for IEnumerable source; public IEnumerable DistinctBy(Func keySelector, IEqualityComparer? comparer) for IEnumerable source; public TSource ElementAt(Index index) for IEnumerable source; public TSource ElementAt(int index) for IEnumerable source; public TSource? ElementAtOrDefault(Index index) for IEnumerable source; public TSource? ElementAtOrDefault(int index) for IEnumerable source; public IEnumerable Empty(); public IEnumerable Except(IEnumerable second) for IEnumerable first; public IEnumerable Except(IEnumerable second, IEqualityComparer? comparer) for IEnumerable first; public IEnumerable ExceptBy(IEnumerable second, Func keySelector) for IEnumerable first; public IEnumerable ExceptBy(IEnumerable second, Func keySelector, IEqualityComparer? comparer) for IEnumerable first; public TSource First(Func predicate) for IEnumerable source; public TSource First() for IEnumerable source; public TSource FirstOrDefault(Func predicate, TSource defaultValue) for IEnumerable source; public TSource FirstOrDefault(TSource defaultValue) for IEnumerable source; public TSource? FirstOrDefault() for IEnumerable source; public TSource? FirstOrDefault(Func predicate) for IEnumerable source; public IEnumerable GroupBy(Func keySelector, Func, TResult> resultSelector, IEqualityComparer? comparer) for IEnumerable source; public IEnumerable GroupBy(Func keySelector, Func elementSelector, Func, TResult> resultSelector, IEqualityComparer? comparer) for IEnumerable source; public IEnumerable GroupBy(Func keySelector, Func elementSelector, Func, TResult> resultSelector) for IEnumerable source; public IEnumerable GroupBy(Func keySelector, Func, TResult> resultSelector) for IEnumerable source; public IEnumerable> GroupBy(Func keySelector, IEqualityComparer? comparer) for IEnumerable source; public IEnumerable> GroupBy(Func keySelector, Func elementSelector) for IEnumerable source; public IEnumerable> GroupBy(Func keySelector, Func elementSelector, IEqualityComparer? comparer) for IEnumerable source; public IEnumerable> GroupBy(Func keySelector) for IEnumerable source; public IEnumerable GroupJoin(IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector) for IEnumerable outer; public IEnumerable GroupJoin(IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector, IEqualityComparer? comparer) for IEnumerable outer; public IEnumerable<(int Index, TSource Item)> Index() for IEnumerable source; public IEnumerable Intersect(IEnumerable second) for IEnumerable first; public IEnumerable Intersect(IEnumerable second, IEqualityComparer? comparer) for IEnumerable first; public IEnumerable IntersectBy(IEnumerable second, Func keySelector) for IEnumerable first; public IEnumerable IntersectBy(IEnumerable second, Func keySelector, IEqualityComparer? comparer) for IEnumerable first; public IEnumerable Join(IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) for IEnumerable outer; public IEnumerable Join(IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer? comparer) for IEnumerable outer; public TSource Last(Func predicate) for IEnumerable source; public TSource Last() for IEnumerable source; public TSource LastOrDefault(Func predicate, TSource defaultValue) for IEnumerable source; public TSource LastOrDefault(TSource defaultValue) for IEnumerable source; public TSource? LastOrDefault() for IEnumerable source; public TSource? LastOrDefault(Func predicate) for IEnumerable source; public long LongCount() for IEnumerable source; public long LongCount(Func predicate) for IEnumerable source; public decimal Max(Func selector) for IEnumerable source; public double Max(Func selector) for IEnumerable source; public TResult? Max(Func selector) for IEnumerable source; public int Max(Func selector) for IEnumerable source; public long Max(Func selector) for IEnumerable source; public int? Max(Func selector) for IEnumerable source; public double? Max(Func selector) for IEnumerable source; public long? Max(Func selector) for IEnumerable source; public float? Max(Func selector) for IEnumerable source; public float Max(Func selector) for IEnumerable source; public decimal? Max(Func selector) for IEnumerable source; public TSource? Max(IComparer? comparer) for IEnumerable source; public float? Max() for IEnumerable source; public int Max() for IEnumerable source; public TSource? Max() for IEnumerable source; public float Max() for IEnumerable source; public long? Max() for IEnumerable source; public int? Max() for IEnumerable source; public double? Max() for IEnumerable source; public decimal? Max() for IEnumerable source; public long Max() for IEnumerable source; public decimal Max() for IEnumerable source; public double Max() for IEnumerable source; public TSource? MaxBy(Func keySelector, IComparer? comparer) for IEnumerable source; public TSource? MaxBy(Func keySelector) for IEnumerable source; public double Min(Func selector) for IEnumerable source; public int Min(Func selector) for IEnumerable source; public long Min(Func selector) for IEnumerable source; public decimal? Min(Func selector) for IEnumerable source; public double? Min(Func selector) for IEnumerable source; public decimal Min(Func selector) for IEnumerable source; public long? Min(Func selector) for IEnumerable source; public float? Min(Func selector) for IEnumerable source; public float Min(Func selector) for IEnumerable source; public TResult? Min(Func selector) for IEnumerable source; public int? Min(Func selector) for IEnumerable source; public TSource? Min(IComparer? comparer) for IEnumerable source; public decimal Min() for IEnumerable source; public long Min() for IEnumerable source; public TSource? Min() for IEnumerable source; public float Min() for IEnumerable source; public float? Min() for IEnumerable source; public long? Min() for IEnumerable source; public int? Min() for IEnumerable source; public double? Min() for IEnumerable source; public decimal? Min() for IEnumerable source; public double Min() for IEnumerable source; public int Min() for IEnumerable source; public TSource? MinBy(Func keySelector, IComparer? comparer) for IEnumerable source; public TSource? MinBy(Func keySelector) for IEnumerable source; public IEnumerable OfType(this IEnumerable source); public IOrderedEnumerable Order(IComparer? comparer) for IEnumerable source; public IOrderedEnumerable Order() for IEnumerable source; public IOrderedEnumerable OrderBy(Func keySelector) for IEnumerable source; public IOrderedEnumerable OrderBy(Func keySelector, IComparer? comparer) for IEnumerable source; public IOrderedEnumerable OrderByDescending(Func keySelector) for IEnumerable source; public IOrderedEnumerable OrderByDescending(Func keySelector, IComparer? comparer) for IEnumerable source; public IOrderedEnumerable OrderDescending(IComparer? comparer) for IEnumerable source; public IOrderedEnumerable OrderDescending() for IEnumerable source; public IEnumerable Prepend(TSource element) for IEnumerable source; public static IEnumerable Range(int start, int count); public IEnumerable Repeat(TResult element, int count); public IEnumerable Reverse() for IEnumerable source; public IEnumerable Select(Func selector) for IEnumerable source; public IEnumerable Select(Func selector) for IEnumerable source; public IEnumerable SelectMany(Func> collectionSelector, Func resultSelector) for IEnumerable source; public IEnumerable SelectMany(Func> selector) for IEnumerable source; public IEnumerable SelectMany(Func> selector) for IEnumerable source; public IEnumerable SelectMany(Func> collectionSelector, Func resultSelector) for IEnumerable source; public bool SequenceEqual(IEnumerable second) for IEnumerable first; public bool SequenceEqual(IEnumerable second, IEqualityComparer? comparer) for IEnumerable first; public TSource Single() for IEnumerable source; public TSource Single(Func predicate) for IEnumerable source; public TSource? SingleOrDefault() for IEnumerable source; public TSource SingleOrDefault(TSource defaultValue) for IEnumerable source; public TSource? SingleOrDefault(Func predicate) for IEnumerable source; public TSource SingleOrDefault(Func predicate, TSource defaultValue) for IEnumerable source; public IEnumerable Skip(int count) for IEnumerable source; public IEnumerable SkipLast(int count) for IEnumerable source; public IEnumerable SkipWhile(Func predicate) for IEnumerable source; public IEnumerable SkipWhile(Func predicate) for IEnumerable source; public float Sum(Func selector) for IEnumerable source; public int Sum(Func selector) for IEnumerable source; public long Sum(Func selector) for IEnumerable source; public decimal? Sum(Func selector) for IEnumerable source; public double Sum(Func selector) for IEnumerable source; public int? Sum(Func selector) for IEnumerable source; public long? Sum(Func selector) for IEnumerable source; public float? Sum(Func selector) for IEnumerable source; public double? Sum(Func selector) for IEnumerable source; public decimal Sum(Func selector) for IEnumerable source; public double? Sum() for IEnumerable source; public float? Sum() for IEnumerable source; public long? Sum() for IEnumerable source; public int? Sum() for IEnumerable source; public decimal? Sum() for IEnumerable source; public long Sum() for IEnumerable source; public int Sum() for IEnumerable source; public double Sum() for IEnumerable source; public decimal Sum() for IEnumerable source; public float Sum() for IEnumerable source; public IEnumerable Take(Range range) for IEnumerable source; public IEnumerable Take(int count) for IEnumerable source; public IEnumerable TakeLast(int count) for IEnumerable source; public IEnumerable TakeWhile(Func predicate) for IEnumerable source; public IEnumerable TakeWhile(Func predicate) for IEnumerable source; public IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector); public IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, IComparer? comparer); public IOrderedEnumerable ThenByDescending(this IOrderedEnumerable source, Func keySelector); public IOrderedEnumerable ThenByDescending(this IOrderedEnumerable source, Func keySelector, IComparer? comparer); public TSource[] ToArray() for IEnumerable source; public Dictionary ToDictionary(Func keySelector, Func elementSelector, IEqualityComparer? comparer) for IEnumerable source where TKey : notnull; public Dictionary ToDictionary(Func keySelector, Func elementSelector) for IEnumerable source where TKey : notnull; public Dictionary ToDictionary(Func keySelector, IEqualityComparer? comparer) for IEnumerable source where TKey : notnull; public Dictionary ToDictionary(Func keySelector) for IEnumerable source where TKey : notnull; public Dictionary ToDictionary([TupleElementNames(new[] { "Key", "Value" })] this IEnumerable<(TKey Key, TValue Value)> source) where TKey : notnull; public Dictionary ToDictionary(this IEnumerable> source, IEqualityComparer? comparer) where TKey : notnull; public Dictionary ToDictionary(this IEnumerable> source) where TKey : notnull; public Dictionary ToDictionary([TupleElementNames(new[] { "Key", "Value" })] this IEnumerable<(TKey Key, TValue Value)> source, IEqualityComparer? comparer) where TKey : notnull; public HashSet ToHashSet() for IEnumerable source; public HashSet ToHashSet(IEqualityComparer? comparer) for IEnumerable source; public List ToList() for IEnumerable source; public ILookup ToLookup(Func keySelector, Func elementSelector) for IEnumerable source; public ILookup ToLookup(Func keySelector, Func elementSelector, IEqualityComparer? comparer) for IEnumerable source; public ILookup ToLookup(Func keySelector) for IEnumerable source; public ILookup ToLookup(Func keySelector, IEqualityComparer? comparer) for IEnumerable source; public bool TryGetNonEnumeratedCount(out int count) for IEnumerable source; public IEnumerable Union(IEnumerable second) for IEnumerable first; public IEnumerable Union(IEnumerable second, IEqualityComparer? comparer) for IEnumerable first; public IEnumerable UnionBy(IEnumerable second, Func keySelector) for IEnumerable first; public IEnumerable UnionBy(IEnumerable second, Func keySelector, IEqualityComparer? comparer) for IEnumerable first; public IEnumerable Where(Func predicate) for IEnumerable source; public IEnumerable Where(Func predicate) for IEnumerable source; public IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip(IEnumerable second, IEnumerable third) for IEnumerable first; public IEnumerable<(TFirst First, TSecond Second)> Zip(IEnumerable second) for IEnumerable first; public IEnumerable Zip(IEnumerable second, Func resultSelector) for IEnumerable first; } ================================================ FILE: meetings/2024/LDM-2024-10-14.md ================================================ # C# Language Design Meeting for October 14th, 2024 ## Agenda - [Extensions](#extensions) ## Quote of the Day - "I know I might be jinxing myself by saying this" - "That is _not_ the quote of the day" - "I think it would be a little narcissistic if I was my favorite person." "Oh, I would totally say that though. That's why I shouldn't be anybody's favorite, because I'm my favorite person. I'm just a terrible narcissist." - "That is _also_ not the quote of the day" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/5497 Related: https://github.com/dotnet/csharplang/blob/d5b8e7808998024e4aa6b380acdccac30aa03b60/proposals/extensions_v2.md, https://github.com/dotnet/csharplang/blob/1e8255a438517bc3ad067c726c28cfa20cb60f1e/meetings/working-groups/extensions/Compatibility%20through%20coexistence%20between%20extension%20types%20and%20extension%20methods.md We started today by making sure that everyone has clarity on the different approaches here, devoting the first section of the meeting to that. To help with that, we did an exercise on "converting" LINQ to the respective new forms. The fully compatible version is [here](LDM-2024-10-14-Enumerable-extensions.cs), and the least compatible version is [here](LDM-2024-10-14-Enumerable-extension.cs). One thing that this revealed is that there's a difference in philosophy between the two approaches that can be expressed with one letter. The fully compatible version is `extensions`, where it's a collection of different extensions for various types. The least compatible version is `extension`, where the user is writing an extension to a given type, extending with a bunch of new members. One of the things that was brought up in response to this exploration is some concern that maybe the smaller, special cases for slightly different receivers aren't all that uncommon. In a type first approach, each of these small special cases would need its own container name; is it that uncommon that someone might want to have a series of extensions on a generic type, and then also one or two extensions on a specific substitution of that generic type? Should those latter two need their own container to be declared? A member first approach would not require this, but a type first approach would. We also discussed a bit about generic properties and operators. Adding generics in these locations would be new, and require its own set of designs; what if we avoided this by saying that, if a user wants to put a generic in a place it can't go today, would they need to use a type-centric approach? IE, this would ensure that if a user wanted to want an extension property on `List`, they would need to use a type extension, and couldn't use a member extension. This was pushed back on as a bad place to put the cliff; the underlying type being generic is a rather arbitrary place to put the line, and it would mean that LINQ couldn't use the member centric approach. We also looked at possible other forms of syntax. One possibility is a grouping syntax like: ```cs extension E { for IEnumerable where T { public void M1() { ... } } for IList where T { public void M2() { ... } } } ``` We're unsure what this would imply: would people think that it adds a local type parameter to the methods, or to the containing type, or to some other nebulous area? One of the reasons we're so cautious here is that C# has, to this point, not really messed with the basic metadata of signatures. Type parameters appear in IL exactly where they appear in syntax, parameters appear in IL where they appear in syntax, and so on. The only times we violate this are areas that cannot be part of any public API, such as lambdas and local functions. This would be a new type of modification, so we're wary to start down the path. Another point we thought about is human compatibility, not just source/binary compatibility. Our users know what modifiers on a parameter mean. They know what method type parameters are. They know how to say that a nullable value is accepted for a given parameter. A type based approach that still allowed adjusting these things would require a new set of syntax to do the same thing that users already know how to do, which isn't great for overall language cohesiveness. The LDM currently has a slight leaning towards the member based approach here, but plenty of us are still unsure and need more time to think about this. So once again, we will come back to this again in a future meeting. ================================================ FILE: meetings/2024/LDM-2024-10-16.md ================================================ # C# Language Design Meeting for October 16th, 2024 ## Agenda - [Simple lambda parameters](#simple-lambda-parameters) - [Unbound generic types in `nameof`](#unbound-generic-types-in-nameof) - [`typeof` string constants](#typeof-string-constants) ## Quote(s) of the Day - "Wacky Generics is the name of my new band focused on kids music." - "Today is apparently an 'everyone is in violent agreement' day." "And let me tell you how this solves extensions" - "Thanks for bringing more controversy. It was too easy, we needed something to rile people up." ## Discussion ### Simple lambda parameters Champion issue: https://github.com/dotnet/csharplang/issues/338 Questions: https://github.com/dotnet/csharplang/blob/53a58b04790ab20fadb7214827de3a91fe828121/proposals/simple-lambda-parameters-with-modifiers.md#open-ldm-questions We started today by looking at a simple proposal that we've previously approved and trying to answer a few open questions that came up during implementation. For the first question, on types named `scoped`, we're ok with breaking that in simple lambda parameters. We've previously done that in cases like `record` and `required`, and it makes sense to do so here as well. We also don't think the case is pathological, like `field`, and we already warn on types named with all lowercase characters anyway. For the second question, we're fine with accepting the recommendation of the implementation. The previous support for attributes on simple lambda parameters was intentional, and it would be weird to end up in another location where we'd fall off the cliff to full parameters, rather than fixing that too. #### Conclusion Both recommendations are accepted; we will now interpret `scoped` as a modifier for simple lambda parameters, not a type name, and we will allow attributes on simple lambda parameters. ### Unbound generic types in `nameof` Champion issue: https://github.com/dotnet/csharplang/issues/8480 We went through this new proposal. We're generally fine with it, but the implementation hasn't been rigorously reviewed yet. Given that, we're not 100% certain that all the semantic dark corners that were previously mentioned have been fully solved. We're fine with the spec as is if a rigorous review finds that it satisfies all of these corners, but if it doesn't, then we approve a more limited form in principle, where we cut off those dark corners and simply make them an error for our future selves to solve. #### Conclusion Approved in principle. If the implementation proves to be much more challenging than it currently appears, we will make those more challenging locations an error. ### `typeof` string constants Champion issue: https://github.com/dotnet/csharplang/issues/8505 Finally today, we did a bit of triage, and looked at this proposal on allowing a limited constant expression form for the fully qualified metadata name string. One immediate concern we have is that the format here is not documented; in fact, some constrained environments in the past have actually stripped out reflection APIs entirely. We're also not particularly sure about the scenarios that would require this to be a constant; attributes, for example, could just take a `Type` instead, rather than needing to take the full name of the type. That is far more robust, as it's an assembly-qualified name that then causes the runtime to probe for that exact type, while a string that lacks assembly qualification is ambiguous. Instead, we think that an easy option here is a source generator where the user puts a `[GenerateTypeName()]` attribute on a class, and a source generator fills in a partial part with a constant string representing the full type name. We're not unsympathetic to more general constant evaluation in C#, but this is too narrow of a usecase to drive it. #### Conclusion Rejected. ================================================ FILE: meetings/2024/LDM-2024-10-28.md ================================================ # C# Language Design Meeting for October 28th, 2024 ## Agenda - [Increment and Decrement Operators in null conditional access](#increment-and-decrement-operators-in-null-conditional-access) - [`yield` in `try` / `catch`](#yield-in-try--catch) ## Discussion ### Increment and Decrement Operators in null conditional access Champion issue: https://github.com/dotnet/csharplang/issues/6045 Spec: https://github.com/dotnet/csharplang/blob/28d016c4d9fc8cbbedfeb75c539e6bebbedc4292/proposals/null-conditional-assignment.md#incrementdecrement-operators An issue came up during implementation of null conditional access, which allows null conditionals as the target of an assignment. The question is whether to allow prefix/postfix increment/decrement operators, and the proposal is not to allow them. The alternative when increment or decrement is needed is to use compound assignments. Technically, the prefix operators would be difficult, and the outcome may not be what the programmer anticipated. Doing postfix, but not prefix operators may seem odd. #### Conclusion We decided to disallow these operators. If folks find scenarios where they need these operators, we might revisit this decision in the future. ### `yield` in `try` / `catch` Champion Issue: https://github.com/dotnet/csharplang/issues/8414 Spec: https://github.com/dotnet/csharplang/pull/8413 The proposal is to allow `yield` inside `try`/`catch` blocks in iterators, which is a longstanding user request. There are challenges, both technically and in matching programmer expectations. These particularly arise during `dispose`. The proposal is for only `finally` blocks, and not `catch` blocks to execute during dispose, even when an error is thrown in the `finally` block. We went through the details of the behavior, and there are concerns that the programmer may expect `catch` to be executed during dispose. We also discussed the behavior of `yield` during dispose. No conclusion was reached. We'll reconsider this in a future meeting after offline work and discussions. ================================================ FILE: meetings/2024/LDM-2024-10-30.md ================================================ # C# Language Design Meeting for October 30th, 2024 ## Agenda - [Extensions](#extensions) ## Quote of the Day - "Part of me wants to trust you , but part of me does not" "I would recommend not" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/5497 Documents: * https://github.com/dotnet/csharplang/blob/e84c2c9711c269243bb2084700512e3f002fec8b/meetings/working-groups/extensions/extensions-an-evolution-of-extension-methods.md * https://github.com/dotnet/csharplang/blob/e84c2c9711c269243bb2084700512e3f002fec8b/meetings/working-groups/extensions/compromise-design-for-extensions.md Today, we took a look at another set of proposals around moving forward on extension types; one taking a more member-centric approach, and the other taking a more type-centric approach. These proposals also tried to lay out visions for how we might balance tradeoffs. Most the meeting was simply spent reading over and clarifying these proposals. We also went and collected some statistics around extensions from a few popular libraries, using https://github.com/DustinCampbell/ExtensionMethodDumper. This data covers how many types are used as a `this` parameter in a given extension type. If we then assume that each of those static classes would need to be split up into extension types for each receiver, we get: * .NET libraries: 496 types goes to 1165 types. Ratio: 2.35 * ASP.NET: 536 types goes to 640: Ratio 1.19 * Roslyn: 782 types goes to 1753 types: Ratio 2.24 * FluentAssertions: 43 types goes to 170 types: Ratio 3.95 FluentAssertions is a particular outlier in our data here. More than half of that increase comes from a single type, `FluentAssertions.AssertionExtensions`, which has 85 receiver types on 97 methods. Another point that was brought up during the meeting is that we're trying to weigh several different types of "complexity", given our existing language. A type-based extension world would very likely require fewer overall concepts in the language, if we were starting from scratch. However, we're not starting from scratch; we're starting from C# as it exists. We already have member-based approaches in the language, and repurposing them may give us a future with a lower concept count given where we're starting. This gives us a lot more to think about, so we'll come back in a couple of weeks to discuss yet again. ================================================ FILE: meetings/2024/LDM-2024-11-04-patterns.md ================================================ Levi [pointed out](https://github.com/dotnet/roslyn/issues/75506) that many users misunderstand the precedence of `not` and `or` pattern combinators and write faulty code as a result. He found many instances on GitHub and internally. For example: `is not 42 or 43` or `is not null or Type`. In such cases, the `or` pattern is superfluous. The user most likely intended to write `is not (42 or 43)` or `is not (null or Type)` instead. We considered two possible solutions: 1. a syntax analyzer that would require parens on either the `not` or around the `or`, so that precedence is explicit 2. a compiler diagnostic when we can detect that the `or` was redundant in a `not ... or ...` pattern (the goal would be to catch the most common cases, not necessarily 100%) I'm pursuing the latter (PR). The diagnostic is implemented as a regular warning, so it introduces a compat break. For example: `is not 42 or 43 // warning: the pattern 43 is redundant. Did you mean to parenthesize the `or` pattern?` Note: we're okay with catching specific/known bad patterns, and do not require to catch every possible bad pattern. Few questions: 1. confirm we prefer this compiler-based detection over an analyzer 2. confirm that a regular warning (ie. compat break) is preferrable to a warning-wave warning or a LangVer-gated warning Note: here are some examples where the `or` pattern is not redundant, which Levi saw in the wild and internally: `is not BaseType or DerivedType` `is not bool or true` `is not object or { Prop: condition }` `is not { Prop1: condition } or { Prop2: condition }` `is not Type or Type { Prop: condition }` ================================================ FILE: meetings/2024/LDM-2024-11-04.md ================================================ # C# Language Design Meeting for November 4th, 2024 ## Agenda - [`not` and `or` patterns](#not-and-or-patterns) - [Notes on notes](#notes-on-notes) ## Quote of the Day - "Whoever fixed the team room, thanks for helping it see sharp." "[unamused] No" ## Discussion ### `not` and `or` patterns Issue: https://github.com/dotnet/roslyn/issues/75506 Document: [LDM-2024-11-04-patterns.md](LDM-2024-11-04-patterns.md) Today, we took a look at an issue with precedence in `not` patterns, when combined with `or` patterns. This is an area where unfortunate Englishisms get in the way of understanding syntax; when a native English-speaking person says "x is not 1 or 2", they usually mean that x is not 1, and it's also not 2. However, C# uses the same precedence as the standard unary operators for the pattern operators, so the same thing written in a pattern actually means `x is (not 1) or 2`. Our initial hope was that this would not be a major issue. However, we now have good evidence that it is; in internal scans, the error rate was nearly 90%. While we certainly can't go change the precedence from the past, we think that we can detect this and introduce a warning for the scenario. We will issue a warning when a `not` pattern causes a subsequent `or` pattern to be subsumed; while we wouldn't mind extending subsumption to an entire pattern, not just for this specific scenario, we think that will be significantly more complicated, and we don't want to delay fixing this. #### Conclusion We will add a warning for when a `not` pattern causes a subsequent `or` pattern to be subsumed. ### Notes on notes The outcome of this session was a restructure of how we approach record-keeping on csharplang. The results were documented in the process changes in [this PR](https://github.com/dotnet/csharplang/pull/8578), with the goal to improve the visibility of issue status on the repo. ================================================ FILE: meetings/2024/LDM-2024-11-13.md ================================================ # C# Language Design Meeting for November 13th, 2024 ## Agenda - Extensions ## Quote(s) of the Day - "These were my opinions, or at least they were a week ago" - "What's the entymology of rathole?" "It's etymology" "Oh right, entymology is the study of living trees" "You just caused my brain to segfault" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/5497 Documents: * https://github.com/dotnet/csharplang/blob/e84c2c9711c269243bb2084700512e3f002fec8b/meetings/working-groups/extensions/anonymous-extension-declarations.md * https://github.com/dotnet/csharplang/blob/e84c2c9711c269243bb2084700512e3f002fec8b/meetings/working-groups/extensions/extending-extensions-a-guide-to-relaxation.md We once again return to extensions, today with a compromise proposal to rule them all: a grouping approach that allows many of the advantages of the type-based approach (lack of repeating types, appears closer to a regular member definition) while also giving us the flexibility of the member-based approach, where a single `Extensions` type can contain all of a user's extensions. After going through the proposal, we were overall very positive on the approach, and think we have something specific that we can start to solve specific problems in and flesh out more. We noted a few things that need to be thought about more: * There's a bit of split-brain logic going on with the receiver parameter in the proposal. It's referred to as `this` in member bodies, but the attribute syntax uses `param`. It's also referable to with parameter modifiers in the `extension` block, which looks like a parameter declaration. Which is it; `this`, or a parameter? While there's definitely some amount of hybridization going on here, we think we want to lean towards the `param` side, rather than the `this` side, because parameter names an provide valuable context. For example, `source` and `destination` are likely clearer names for a mapping method than `this` and `destination`. * Along similar lines, how is the receiver documented in XMLDoc? * Not an issue, but we pointed out that this proposal does not permit new forms of generic specialization: it does not allow indexers to take generic type parameters, for example. After this discussion, we were able to come to a few key decisions that will help us flesh out this proposal further. #### Conclusions * We want to be able to generate compatible extension methods with this syntax. We're not committing to absolute 100% compatibility with existing extension methods, but we'd like to be able to express 99% of existing extension methods in the new format. * Along similar lines, we think we should generate visible methods from extension declarations. * Finally, we think that we should lean more towards treating the receiver as a parameter, rather than as `this`, for the next iteration of the proposal. ================================================ FILE: meetings/2024/LDM-2024-11-20.md ================================================ # C# Language Design Meeting for November 20th, 2024 ## Agenda - [Extensions](#extensions) - [Dictionary Expressions](#dictionary-expressions) ## Quote of the Day - "I don't think we should go boldly, I think we should boldly go" ## Discussion ### Extensions Champion Issue: https://github.com/dotnet/csharplang/issues/5497 Document: https://github.com/dotnet/csharplang/blob/a372f702e6f8f8fbbb0845611715cbfe4450cbd5/meetings/working-groups/extensions/extension-members-unified-proposal.md Today, we looked through the unified extension proposal to see whether the compiler team can move forward in this general direction. While we're not fully agreed on precise syntax here, we are agreed that this proposal, and the semantics that it implies, are the way forward for extensions in C#. There are a few things that definitely need to be directly addressed around the compat story. There was lots of uneasiness about using `this` as the indicator for backwards compatible generation. Nothing about the keyword implies compatibility, so we think that it's likely too "cute" and we need a different approach. In particular, there's some thought that we could just always emit in the legacy format, rather than forcing users to understand how or why they might want to do this. #### Conclusion General proposal is accepted. ### Dictionary Expressions Champion issue: https://github.com/dotnet/csharplang/issues/8659 Specification: https://github.com/dotnet/csharplang/blob/a372f702e6f8f8fbbb0845611715cbfe4450cbd5/proposals/dictionary-expressions.md Open question: https://github.com/dotnet/csharplang/blob/a372f702e6f8f8fbbb0845611715cbfe4450cbd5/proposals/dictionary-expressions.md#question-types-that-support-both-collection-and-dictionary-initialization Finally, we went over the proposal to add dictionary expressions to the language. Like extensions, we know that there's still plenty of design work to do here, but we want enough of a specification agreed on that the compiler team can start work and discover the more interesting questions. We spent quite a bit of time on key comparers, and there's clearly more design work that needs to happen before the LDM is ready to fully approve the direction. We are in general agreement that allowing users to provide a comparer to a dictionary expression is part of the "minimum required feature set" before dictionary expressions can be shipped; what we're not in agreement about is how we should go about doing that. We brainstormed several different syntaxes, such as `new(...) with [... elements]`, `[.. elements] with (Comparer)`, `[comparer; .. elements]`, `[comparer: comparerInstance, .. elements]`, and others. Nothing clearly stuck out as being a preference, so more design will be needed. Additionally, we're unsure whether we should just limit to a key comparer, or whether we should extend to general arguments. For example, `ImmutableDictionary` takes both key and value comparers, should we have a way to set both? What about something like `HashSet`, which isn't a "dictionary"; should there be a way to provide a comparer to that type? While we don't necessarily think such support is part of a minimum bar, we do want to make sure that we're keeping such support in mind and not designing something that can't be extended to support these scenarios. We also took a first look at the question on "hybrid" types, that support both the standard collection expressions and dictionary expression semantics. We are very unsure as an LDM at this point. We don't have good examples of where these patterns might exist, or a good understanding of _whether_ these patterns exist in practice. Therefore, we don't have a good feel for which is generally the more prominent part of such a type's design: collectionness, or dictionaryness. To give the compiler team a direction, we'll start with dictionaryness for now, as there's a very slightly leaning in that direction among LDM members, but we'll definitely need to revisit this in the future. #### Conclusion General proposal is accepted. More work will need to happen on dictionary construction arguments and hybrid type semantics. ================================================ FILE: meetings/2024/LDM-2024-12-04.md ================================================ # C# Language Design Meeting for December 4th, 2024 ## Agenda - [First-class span open questions](#first-class-span-open-questions) - [Preferring `ReadOnlySpan` over `Span` conversions](#preferring-readonlyspant-over-spant-conversions) - [Conversions in expression trees](#conversions-in-expression-trees) ## Quote of the Day - "Anything else for you?" ## Discussion ### First-class span open questions Champion issue: https://github.com/dotnet/csharplang/issues/8714 #### Preferring `ReadOnlySpan` over `Span` conversions Spec: https://github.com/dotnet/csharplang/blob/1754aaafffe270c19d8578af30a2eb768de3ee49/proposals/first-class-span-types.md#prefer-converting-from-t-to-readonlyspant-over-spant-in-overload-resolution The first issue we looked at today was around how array covariance can impact conversions to `Span`. Because .NET has array covariance, the conversion from an array to a `Span` has to check to make sure that the array is a `T[]`, not some subtype of `T[]`; if this is the case, then the conversion will throw. This is a footgun for usages of this implicit conversion, and one that was hit immediately on upgrade to C# 14 preview by Roslyn because of the triplet of `IEnumerable`, `Span`, and `ReadOnlySpan` in test assert APIs. It is fairly common for some expected data to be an array of some specific subtype, while the actual data being converted is a supertype. Previously this would have gone through `IEnumerable`, which was perfectly fine with covariant arrays. `Span` is now the preferred overload, and that isn't fine with covariant arrays. This issue, where an implicit conversion can throw, isn't entirely new to the language; after all, `dynamic` can be implicitly converted to any type, and that can throw. But it is likely more prominent than `dynamic`, as with that feature, the user usually intentionally started at `dynamic` and went to a specific type, rather than starting at array and then invisibly going to a `Span` where `IEnumerable` used to be fine. While we don't think that we have reason to entirely remove the array->`Span` conversion from the feature, we do think that this merits some work to make sure that when there is a safer conversion available that likely has the same intended meaning, we prefer that conversion instead. The proposal here is to make `ReadOnlySpan` preferred over `Span` for first-class span. From an immediate type theory perspective, this is an exception how most-specific type normally works. Since `Span` can be converted to `ReadOnlySpan`, but not the other way around, our standard most-specific type logic would indicate that `Span` should be preferrable. However, for these types of APIs that overloaded across `Span` and `ReadOnlySpan`, we think that the only reason they're overloaded is because we currently _don't_ have first-class spans as a language feature. Both overloads were added to make the API usable, but the only thing the API needs is read access, and the `Span` API usually just forwards to the `ReadOnlySpan` version. The only time this reasoning doesn't hold up is when we consider extension methods across multiple different types; such overloads could indeed have different behavior, where one overload really does need mutable access, while the other only reads from the array. Despite this, we think the overall feature is better served by preferring `ReadOnlySpan` over `Span`. We also considered the question of whether we should take a bigger step. Our proposed rules only impact when arrays are converted to span types. This is yet another element to a decoder ring, another special case that users need to know to understand how overload resolution will be applied to their APIs. Could we instead make a broader, simpler rule, where we just always prefer `ReadOnlySpan` over `Span`? We don't think we have enough data to make such a decision today. We don't know of any APIs that this would negatively affect, but more investigation needs to be done before making a call. Finally on this topic, we considered how restrictive to make the preference on type parameters. It seems a little suspicious to us to prefer `ReadOnlySpan` over `Span`; can we really make the assertions we said earlier about the expected behavior of the API for this type of shape? We'll investigate this as well, and see whether there are examples of this pattern in the wild. ##### Conclusion We adopt the rule preferring `ReadOnlySpan` over `Span` for array conversions. We will investigate broader rules as well, and their potential impact on the BCL. #### Conversions in expression trees Spec: https://github.com/dotnet/csharplang/blob/1754aaafffe270c19d8578af30a2eb768de3ee49/proposals/first-class-span-types.md#ignore-span-conversions-in-expression-trees Our last topic of the day was considering whether we should try and handle the conversion specially in expression trees, as this can be a breaking change. We had a [similar discussion](https://github.com/dotnet/csharplang/blob/1754aaafffe270c19d8578af30a2eb768de3ee49/meetings/2024/LDM-2024-06-17.md#params-span-breaks) with `params ReadOnlySpan`, and we continue to find our reasoning from that issue compelling. Therefore, we will not make any changes to how binding occurs in expression trees; they will potentially pick up new span-based overloads and users may have to insert explicit casts to get old behavior back. ##### Conclusion No special cases for expression trees. ================================================ FILE: meetings/2024/LDM-2024-12-09.md ================================================ # C# Language Design Meeting for December 9th, 2024 ## Agenda - [First-class span open questions](#first-class-span-open-questions) - [Simple Lambda Parameters](#simple-lambda-parameters) - [Collection expression arguments](#collection-expression-arguments) ## Quote of the Day - "Array covariance is like spice girls, no one admits to liking them but someone is buying the albums" ## Discussion ### First-class span open questions Champion issue: https://github.com/dotnet/csharplang/issues/8714 Spec diff: https://github.com/dotnet/csharplang/commit/7e964ef0ca8620eb4e7129396e867059d6c26be4#diff-e5dfbc21202c1a2f879f6221ecd9105efde1815d69cbe2db81189b1a667db328 First up today, we took a look at the specific proposed wording for our rule from [last time](LDM-2024-12-04.md#preferring-readonlyspant-over-spant-conversions). We're happy with this direction, though the specific wording needs to be reworded: by the proposed rule, multiple bullets could be true at the same time in opposite directions, leading to an ambiguity. The intent behind the rule, though is correct and agreed on. We continue to stand by our reasoning that `ReadOnlySpan` should be preferred over `Span`, and the intent of these rules is to enable precisely that. #### Conclusion Direction is approved, modulo a bit of wordsmithing to remove ambiguities. ### Simple Lambda Parameters Champion issue: https://github.com/dotnet/csharplang/issues/8637 Open questions: https://github.com/dotnet/csharplang/blob/9cca9e78ac7c35af896bf4643a5c3cd2aef828b2/proposals/simple-lambda-parameters-with-modifiers.md#open-questions Next, we took a look at a couple of open questions for simple lambda parameters. The first seemed reasonable; making the definition of `scoped` depend on whether there's a default value for the parameter seems much worse to us than a minor breaking change that we've already made in other locations. However, the second question is more thorny. After discussion, we realized that we don't have an intuition of what `params` and `scoped` will do in terms of overload resolution. Will it cause overloads to be thrown out? Or will it only have an impact after an overload has been chosen, potentially issuing an error because the modifier can't be applied to the parameter? We're not comfortable making a decision with an imprecise understanding of the consequences today. Therefore, we'll come back next time with a complete understanding of the rules that are being proposed and figure out if we need to cut any modifiers at that point. #### Conclusion `scoped` break is approved in theory, but we'll come back next time to determine if we will include `scoped` and `params` in this work. ### Collection expression arguments Champion issue: https://github.com/dotnet/csharplang/issues/8887 Spec: https://github.com/dotnet/csharplang/blob/9cca9e78ac7c35af896bf4643a5c3cd2aef828b2/proposals/collection-expression-arguments.md Finally, we took a look at a proposed extension to collection expressions, that will also be used for dictionary expressions. We didn't have enough time left to get into specifics of the proposal; the proposed syntax, in particular, didn't resonate with a lot of the LDM. In particular, we think this may be too indirect of a usage of `with`, as it's not really related to the current usage of `with` in the language. However, we think there's promise in the general idea, and want to move forward with the idea. We'll work on syntax at a later point. #### Conclusion General feature is approved to move forward, with the specifics to be discussed in the coming months. ================================================ FILE: meetings/2024/README.md ================================================ # C# Language Design Notes for 2024 ### Mon Dec 9, 2024 [C# Language Design Meeting for December 9th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-09.md) - First-class span open questions - Simple Lambda Parameters - Collection expression arguments ### Wed Dec 4, 2024 [C# Language Design Meeting for December 4th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md) - First-class span open questions - Preferring `ReadOnlySpan` over `Span` conversions - Conversions in expression trees ### Wed Nov 20, 2024 [C# Language Design Meeting for November 20th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-11-20.md) - Extensions - Dictionary Expressions ### Wed Nov 13, 2024 [C# Language Design Meeting for November 13th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-11-13.md) - Extensions ### Mon Nov 4, 2024 [C# Language Design Meeting for November 4th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-11-04.md) - `not` and `or` patterns - Notes on notes ### Wed Oct 30, 2024 [C# Language Design Meeting for October 30th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-30.md) - Extensions ### Mon Oct 28, 2024 [C# Language Design Meeting for October 28th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-28.md) - Increment and Decrement Operators in null conditional access - `yield` in `try` / `catch` ### Mon Oct 21, 2024 Canceled ### Wed Oct 16, 2024 [C# Language Design Meeting for October 16th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-16.md) - Simple lambda parameters - Unbound generic types in `nameof` - `typeof` string constants ### Mon Oct 14, 2024 [C# Language Design Meeting for October 14th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-14.md) - Extensions ### Wed Oct 9, 2024 [C# Language Design Meeting for October 9th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-09.md) - Extensions ### Mon Oct 7, 2024 [C# Language Design Meeting for October 7th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-07.md) - Extensions ### Wed Oct 2, 2024 [C# Language Design Meeting for October 2nd, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-02.md) - Open questions in field - `readonly` contexts and `set` - `Conditional` code - Interface properties and auto-accessors - Extensions ### Mon Sep 30, 2024 [C# Language Design Meeting for September 30th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-30.md) - Extensions ### Wed Sep 25, 2024 - C# 14 (no notes) ### Mon Sep 23, 2024 - *Design review* (no notes) ### Wed Sep 18, 2024 [C# Language Design Meeting for September 18th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-18.md) - Nullability in `field` - Extensions naming ### Wed Sep 11, 2024 [C# Language Design Meeting for September 11th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md) - Better conversion from collection expression and `params` collections - First-class span types - `Reverse` - New ambiguities - Covariant arrays ### Wed Sep 4, 2024 [C# Language Design Meeting for September 4th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-04.md) - Triage (no milestone) - Type inference using method group natural type - Collection Expressions Next (C#13 and beyond) - Dictionary expressions - Extending patterns to "as" - `params in` parameters - Triage (working set) - Only Allow Lexical Keywords in the Language - Permit variable declarations under disjunctive patterns - CallerCharacterNumberAttribute - `Task` nullability covariance - Nullable analysis of LINQ queries ### Wed Aug 28, 2024 [C# Language Design Meeting for August 28th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-08-28.md) - Nullable in `ref` ternaries - Block-bodied switch expression arms ### Mon Aug 26, 2024 [C# Language Design Meeting for August 26th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-08-26.md) - `field` keyword open questions - `field` in property initializers - Interaction with partial properties - `readonly` field ### Wed Aug 21, 2024 [C# Language Design Meeting for August 21st, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-08-21.md) - Better conversion from collection expression - `field` keyword nullability ### Mon Aug 19, 2024 [C# Language Design Meeting for August 19th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-08-19.md) - Better conversion from collection expression ### Wed Aug 14, 2024 [C# Language Design Meeting for August 14th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-08-14.md) - `field` keyword - `field` nullability - `field` in event accessor - Scenarios similar to `set;` ### Wed Jul 24, 2024 [C# Language Design Meeting for July 24th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-07-24.md) - Discriminated Unions - Better conversion from collection expression with `ReadOnlySpan` overloads ### Mon Jul 22, 2024 [C# Language Design Meeting for July 22nd, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-07-22.md) - Extensions - Ref structs implementing interfaces ### Wed Jul 17, 2024 [C# Language Design Meeting for July 17th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-07-17.md) - Overload resolution priority open questions - Better conversion from collection expression with `ReadOnlySpan` overloads ### Mon Jul 15, 2024 [C# Language Design Meeting for July 15th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-07-15.md) - `field` keyword - First-Class Spans Open Question ### Wed Jun 26, 2024 [C# Language Design Meeting for June 26th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-26.md) - Extensions ### Mon Jun 24, 2024 [C# Language Design Meeting for June 24th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-24.md) - First-Class Spans - `field` questions - Mixing auto-accessors - Nullability ### Mon Jun 17, 2024 [C# Language Design Meeting for June 17th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md) - `params` Span breaks - Overload resolution priority questions - Application error or warning on `override`s - Implicit interface implementation - Inline arrays as `record struct`s ### Wed Jun 12, 2024 [C# Language Design Meeting for June 12th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md) - `params Span` breaks - Extensions ### Mon Jun 10, 2024 [C# Language Design Meeting for June 10th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-10.md) - `ref struct`s implementing interfaces and in generics ### Mon Jun 3, 2024 [C# Language Design Meeting for June 3rd, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-03.md) - Params collections and dynamic - Allow ref and unsafe in iterators and async ### Wed May 15, 2024 [C# Language Design Meeting for May 15th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-15.md) - `field` and `value` as contextual keywords - Usage in `nameof` - Should `value` be a keyword in a property or indexer get? Should `field` be a keyword in an indexer? - Should `field` and `value` be considered keywords in lambdas and local functions within property accessors? - Should `field` and `value` be keywords in property or accessor signatures? What about `nameof` in those spaces? - Dictionary expressions ### Mon May 13, 2024 [C# Language Design Meeting for May 13th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-13.md) - First-class span types questions - Delegate conversions - Variant conversion existence - Overload resolution priority questions - Attribute shape and inheritance - Extension overload resolution ### Wed May 8, 2024 [C# Language Design Meeting for May 8th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-08.md) - `readonly` for primary constructor parameters ### Wed May 1, 2024 [C# Language Design Meeting for May 1st, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-01.md) - Adjust binding rules in the presence of a single candidate ### Wed Apr 24, 2024 [C# Language Design Meeting for Apr 24th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-24.md) - Adjust dynamic binding rules for a situation of a single applicable candidate ### Mon Apr 22, 2024 [C# Language Design Meeting for Apr 22nd, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-22.md) - Effect of language version on overload resolution in presence of `params` collections - Partial type inference: '_' in method and object creation type argument lists ### Wed Apr 17, 2024 [C# Language Design Meeting for Apr 17th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-17.md) - Relax `Add` requirement for collection expression conversions to types implementing `IEnumerable` - Extensions ### Mon Apr 15, 2024 [C# Language Design Meeting for Apr 15th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-15.md) - Non-enumerable collection types - Interceptors - Relax `Add` requirement for collection expression conversions to types implementing `IEnumerable` ### Mon Apr 8, 2024 [C# Language Design Meeting for Apr 8th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-08.md) - Implementation specific documentation ### Mon Apr 1, 2024 [C# Language Design Meeting for Apr 1st, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-01.md) - Async improvements (Async2) ### Wed Mar 27, 2024 [C# Language Design Meeting for Mar 27th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-03-27.md) - Discriminated Unions ### Mon Mar 11, 2024 [C# Language Design Meeting for Mar 11th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-03-11.md) - Dictionary expressions ### Mon Mar 4, 2024 [C# Language Design Meeting for Mar 4th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-03-04.md) - Breaking changes: making `field` and `value` contextual keywords - Overload resolution priority ### Wed Feb 28, 2024 [C# Language Design Meeting for Feb 28th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-28.md) - Extensions ### Mon Feb 26, 2024 [C# Language Design Meeting for Feb 26th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-26.md) - `ref struct`s in generics - Collection expressions ### Wed Feb 21, 2024 [C# Language Design Meeting for Feb 21st, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md) - Declaration of ref/out parameters in lambdas without typename - `params` collections - Metadata format - `params` and `scoped` across `override`s - `required` members and `params` parameters ### Wed Feb 7, 2024 [C# Language Design Meeting for Feb 7th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-07.md) - Partial type inference - Breaking change warnings ### Mon Feb 5, 2024 [C# Language Design Meeting for Feb 5th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-05.md) - First-class span types - Collection expressions: inline collections ### Wed Jan 31, 2024 [C# Language Design Meeting for January 31st, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-31.md) - Relax "enumerable" requirement for collection expressions - `params` collections evaluation orders ### Mon Jan 29, 2024 [C# Language Design Meeting for January 29th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-29.md) - `params` collections - Better function member changes - `dynamic` support - `dynamic` and `ref` local function bugfixing ### Mon Jan 22, 2024 [C# Language Design Meeting for January 22nd, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-22.md) ### Wed Jan 10, 2024 [C# Language Design Meeting for January 10th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-10.md) - Collection expressions: conversion vs construction ### Mon Jan 8, 2024 [C# Language Design Meeting for January 8th, 2024](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-08.md) - Collection expressions - Iteration type of `CollectionBuilderAttribute` collections - Iteration type in conversions ================================================ FILE: meetings/2025/LDM-2025-01-06.md ================================================ # C# Language Design Meeting for January 6th, 2025 ## Agenda - [Ignoring `ref struct`s in `Expression`s](#ignoring-ref-structs-in-expressions) - [Extensions](#extensions) ## Quote of the Day - "I'm also of many minds on this, maybe more than 2" ## Discussion ### Ignoring `ref struct`s in `Expression`s Champion issue: https://github.com/dotnet/csharplang/issues/8714 Proposal: https://github.com/dotnet/csharplang/blob/1393601a2a4c45222f027983a248a08996d8ff05/meetings/working-groups/ref-improvements/ignore-overloads-in-expressions.md Today, we looked at a proposal to change how overload resolution works in expression trees. This isn't a step we want to take lightly; our previous positions here, such as for `params ReadOnlySpan`, has been to let them exist, and then error if they couldn't be used. Unfortunately in this case, there's a very good chance that instead of being a compile-time error, it will end up being a runtime error instead. This has happened in the past, but as we increase our usage of `ref struct`s in C#, it keeps coming up; the latest example is first-class spans, which causes breaks in the EF query provider when unexpected versions of query methods get called. There are three options: 1. We could make overload resolution in expression trees in expression trees ignore `ref struct`s, as this proposal suggests. 2. We could more strongly enforce the restriction on `ref struct`s in expression trees, and error when one is used implicitly, like in these scenarios. 3. Do nothing and have EF's query provider understand the new methods. Of these options, the only one we're ok with is option 3. For option 1, it will break scenarios that work perfectly fine today; indirect usage of `ref struct`s can often work in `Expression`s that are compiled on CoreCLR. For option 2, it will force all of our users to update their code to handle the new breaking change. For 3, it does mean that some users who are on older versions of EF will face runtime errors when they update to C# 14, but don't update their reference to EF. However, those users will face such issues in general, not just because of increased `ref struct` issues. For example, EF is adding support for new types of joins, expressed through new query methods, and older query providers cannot handle those new methods just as they cannot handle these `ref struct` overloads. We do not think that it is burdensome to ask EF to handle these new overloads, and thus that is our plan. #### Conclusion Proposed change is rejected. We will make no changes to how overload resolution in expression trees occurs. ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Proposal: https://github.com/dotnet/csharplang/blob/dc2672a9fb93dc2c5d782b3c5600eea469a9c581/meetings/working-groups/extensions/extension-member-disambiguation.md Finally today, we looked at a proposal for how we might introduce a generalized disambiguation syntax. While this is useful for extensions, we think it may also be more generally useful, such as allowing calling default interface members from class members. While LDM likes the general direction, there was a lot of mixed feedback on the specific syntax forms shown here. Two potential alternative directions were brought up: * Use `in` instead of `as`, `at`, or `@`. This may more clearly define that we are looking for a specific member as implemented _in_ a specific type. * Change the paradigm to instead qualify the member, rather than the receiver. For example, `e.(MyExtension.Prop)` or `e1 (MyExtension.+) e2`. This may better qualify that the member is being bound in a specific manner, rather than the receiver. A number of LDT members liked this second direction, but it will introduce some challenges for things such as constructors, which don't have a syntax that can be referred to after a `.` today. `MyExtension.new` is a possibility, but we'll have to consider more. Ultimately, we didn't come to any conclusions today, and will revisit again in a future meeting. #### Conclusion No conclusion here today. ================================================ FILE: meetings/2025/LDM-2025-01-13.md ================================================ # C# Language Design Meeting for January 13th, 2025 ## Agenda - [Discriminated Unions](#discriminated-unions) ## Quote of the Day - "I'm apparently nothing" ## Discussion ### Discriminated Unions Champion issue: https://github.com/dotnet/csharplang/issues/8928 Proposal: https://github.com/dotnet/csharplang/blob/b6d1f4b3d132ce648299c75f3b9e2961151996b0/proposals/TypeUnions.md Working Group Status: https://github.com/dotnet/csharplang/blob/b6d1f4b3d132ce648299c75f3b9e2961151996b0/meetings/working-groups/discriminated-unions/Union%20implementation%20challenges.md Today we heard from the discriminated unions working group, which has been hard at work exploring the various tradeoffs of how different types of union structures can be implemented in C#. We started with a refresher of the various options, and worked in explanations of the tradeoffs the group has discovered. On the whole, we agree with the conclusions of the group; there is some difference in opinion on the severity of some of the pitfalls, such as the `OfType()` issue in adhoc unions, but we don't have any additional cases or examples to add to the group's conclusions. We are very much in favor of proceeding with a single part of the proposal; while we want to keep the other parts in mind during design to ensure that we can add them at a later date and have them still feel at home in the language, we view unions as a feature that we can and should deliver over time, rather than all at once, much as we have done with patterns. Therefore, we will proceed with deep design work on just class unions for now, drilling deep into just that section and working on a design that has the detail level necessary to be implemented in the language. We want to make sure that, even if v1 of the feature doesn't have a story to grow from a simple union declaration to full classes with the ability to declare members, implement interfaces, etc., that there is a plan for how to get that full functionality in the future. #### Conclusion We will proceed with in-depth design work on class unions. ================================================ FILE: meetings/2025/LDM-2025-01-15.md ================================================ # C# Language Design Meeting for January 15th, 2025 ## Agenda - [`fieldof`](#fieldof) - [Simple lambda parameters](#simple-lambda-parameters) - [Interpolated string handler method names](#interpolated-string-handler-method-names) ## Quote of the Day - "First mute of 2025. Made it to 1/15. New record." ## Discussion ### `fieldof` Champion issue: https://github.com/dotnet/csharplang/issues/9031 Specification: https://github.com/dotnet/csharplang/blob/7bf02aafb8fc623b5d21075514440b7e9fd4d94c/proposals/fieldof.md We started today by looking a proposal around backing field access outside of a property accessor. This scenario is being driven primarily through a few early adopters of the `field` feature, and the scenarios are interesting enough that we think exploration of the area is worth it. However, LDM is much more mixed on this specific solution; several members were unsure of how well `fieldof` fit in with `typeof` and `nameof`, and felt that it was too specialized to this specific problem, and not obvious where it should end. One other option we floated was the ability to specify both `init` and `set`, so a property could look like this: ```cs public int Prop { get; private init; set => SetAndNotifyIfChanged(ref field, value); } ``` There are definitely some issues to work out with such a proposal, but we like that it seems more generalizable and fits in better with property declarations today. We didn't conclude on this topic today, but will come back in a future LDM after exploring this second approach more. #### Conclusion This proposal will go into the working set to be actively considered. ### Simple lambda parameters Champion issue: https://github.com/dotnet/csharplang/issues/8637 Spec: https://github.com/dotnet/csharplang/blob/7bf02aafb8fc623b5d21075514440b7e9fd4d94c/proposals/simple-lambda-parameters-with-modifiers.md#open-questions We picked this up from the [last time](../2024/LDM-2024-12-09.md#simple-lambda-parameters) simple lambda parameters came up, around whether `scoped` or `params` should be allowed without a type. These modifiers are slightly different on their impact: `params` can only impact the call site, while `scoped` impacts both the call site and the body of the lambda. We're ok with `scoped`, because of that reason. However, we're not ok with `params`; it can never narrow overload resolution, and we'll have to do extra checks to make sure that `params` is allowed with the type that is inferred. Rather than having that extra check and potential for confusion, we think it's a better idea to simply disallow `params` without a type. If we ever hear from users that this would be useful, we can revisit at that point. #### Conclusion We allow `scoped` to be used without an explicit type. We forbid `params` without an explicit type. ### Interpolated string handler method names Champion issue: https://github.com/dotnet/csharplang/issues/9046 Specification: https://github.com/dotnet/csharplang/blob/a970d01597886d84d7498e1b6a9d8e8e8ebf02c1/proposals/interpolated-string-handler-method-names.md Finally today, we started triage for some newer issues. For this one, we think that we need a full hour in LDM to debate the approach and alternatives, but are interested in moving forward, so it will go into the `Working Set` milestone. ================================================ FILE: meetings/2025/LDM-2025-01-22.md ================================================ # C# Language Design Meeting for January 22nd, 2025 ## Agenda - [Partial events and constructors](#partial-events-and-constructors) - [Collection expression arguments](#collection-expression-arguments) - [Extensions disambiguation syntax](#extensions-disambiguation-syntax) ## Quote of the Day - Nothing particularly amusing or memorable today, sorry ## Discussion ### Partial events and constructors Champion issue: https://github.com/dotnet/csharplang/issues/9058 Specification: https://github.com/dotnet/csharplang/blob/305c5195ce36c9fe66e83235a15c5b9b885e77ab/proposals/partial-events-and-constructors.md First up today, we did a very quick check in on partial events and constructors. When we looked at partial properties in C# 13, we gave a brief consideration of the remaining member types, but decided to wait until we had use cases. We now have those use cases, and we agree that they're sufficient motivation and validation to move forward with. We'll review the complete specification at a later point, but this proposal is approved and moved into Working Set. #### Conclusion Approved, moved into Working Set ### Collection expression arguments Champion issue: https://github.com/dotnet/csharplang/issues/8887 Specification: https://github.com/dotnet/csharplang/blob/305c5195ce36c9fe66e83235a15c5b9b885e77ab/proposals/collection-expression-arguments.md#construction Next, we took a look at some of the semantics around collection expression arguments. We did not go over syntax today; that will be saved for a future LDM. Instead, we wanted to unblock the implementation's thornier semantic questions around overload resolution. We had no comments on the standard constructor scenario; it seemed fine. What was more controversial were the second and third scenarios. For the `Create` method scenario, there's a concern that some of the existing methods that the BCL would want to use are not in the form that the proposal requires: some of them use `params`, so their collections are the last argument to the `Create` method, not the first. While the BCL _could_ add another method that just reorders the parameters, we're not a fan of the idea; we may want to relax the requirements here to allow those methods. We then looked at interface implementations, and whether we want to expose "constructors" for collection expressions target-typed to these interface types. Unfortunately, one thing that came during discussion here is a discrepancy in the checked-in specification and the notes; the notes from [August 9th](../2023/LDM-2023-08-09.md#target-typing-of-collection-expressions-to-core-interfaces) indicate that we don't guarantee what the underlying type of these interfaces is, while the checked-in specification appears to reflect an older initial approach from the working group that guaranteed the concrete type to be `List`. We didn't fully debate the implications of this discrepancy on the current proposal, but the LDM did tend to lean either towards exposing the underlying constructors of the used type, or to simply stating that a few constructors were presumed to exist, regardless of the underlying type that ends up being used. For now, the group will proceed in that direction, and we'll need to bring this back again for further discussion to confirm. Finally, we thought briefly about `dynamic` arguments; we don't see any current use cases, and think that there are going to be some particularly gnarly challenges (for example, if we allow `Create` methods to have the collection either before or after the other arguments, how does that impact dynamic binding?). Given this, we will block it for now. #### Conclusion Constructor rules are approved. `Create` rules are approved, with some caveats that we may need to bring back a looser version after we do more investigation in the BCL for `Create` methods we want to approve. The implementation will proceed with interface constructors for now, but will need to come back to ratify the decision and determine exactly how this works in the future. `dynamic` arguments are disallowed. ### Extensions disambiguation syntax Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/e145230405eabef04a460003a20825fecce7f4d5/proposals/extensions.md Syntax examples: https://github.com/dotnet/csharplang/blob/8e5e055737bf8f278adf79176fdd370708e23d23/meetings/working-groups/extensions/disambiguation-syntax-examples.md In our last topic for the day, we dived right into syntax options for disambiguating extension types. [Last time](LDM-2025-01-06.md#extensions) we talked about syntax, we decided we wanted to explore member-focused approaches, and see what that ended up looking like. Now we're back with the results of those explorations, and it runs into a few problems. Specifically, there are some types of members that just don't have existing ways of being referred to in C#. For example, indexers, constructors, operators, and casts cannot be captured into method references today, and we'd have to invent a new syntax to even permit them to be referenced. This causes the member-focused approach to be fairly heavy-weight; it's a big piece of new syntax for a scenario we don't expect will be used much. One possible view is that this syntax could be viewed more broadly; what if we were to use this syntax to allow taking references to members that cannot be referenced today? That would bring unification to the feature and help justify the weight of the new syntax forms. However, we're not certain that this is a road we want to go down, and are wary of even more scope creep on the extensions feature. We're pretty split on this approach. We then looked at the other example forms that were brought. The LDM is fairly split on the casting form; some think it's a natural extension, others think that it's too far removed from the existing meaning of casting, as it does not modify the receiver. Additionally, there is no real opportunity for a cast-like syntax to be useful to other disambiguation scenarios, such as calling a DIM or an explicit implementation on a `struct` without boxing. Invocation-like syntax was more positively received, as was `in` and `at`. `using` was pretty strongly disliked for introducing yet _another_ new meaning for `using`, which is already heavily overloaded. `@` was also concerning for what impacts it could have on other tooling ecosystems such as Razor. We did consider a couple more approaches that were not in the document as well: * Could we allow `using MyExtension { range.Where (i => i < 10); }`? This is not a _new_ meaning for `using`, it's just a new location that an existing import syntax can be used. This is still potentially quite confusing, however, so we're not enamored with it. * What if we just leaned into the static-method nature and just required disambiguation to call using the emitted name. For example, `range = MyExtension.op_Plus(range, 5);`. This has the advantage of requiring absolutely no new syntax, but does mean that we need to lean further into the "this is just sugar over static methods" approach. Ultimately, we did not make a final decision today. We've expressed some initial opinions, and will come back later with refinements. Ultimately, we do not think an initial preview of extensions is gated on this decision, so we are fine with not having a concrete syntax today. ================================================ FILE: meetings/2025/LDM-2025-02-12.md ================================================ # C# Language Design Meeting for February 12th, 2025 ## Agenda - [User-defined instance-based operators](#user-defined-instance-based-operators) - [Left-right join in query expressions](#left-right-join-in-query-expressions) ## Quote of the Day - "That's how I shall influence the LDM, with cats. Unless you're a dog person, in which case that's terrible." ## Discussion ### User-defined instance-based operators Champion issue: https://github.com/dotnet/csharplang/issues/9101 Specification: https://github.com/dotnet/csharplang/blob/5a627e44b33f6c8765f11f3007de379b526684a0/proposals/user-defined-compound-assignment.md We started today by reviewing the proposal for user-defined compound assignment, which we are looking at for the first time. As we went over the spec, we had a couple of concerns around corner cases with instance vs static operators: we don't want type authors to accidentally push their users into odd scenarios where a `+=` may work, but `+` won't (or vice versa) because one form declares extra operators than the other. Other than that, though, we think this is an excellent proposal, and will move forward with it. #### Conclusion Proposal is approved. ### Left-right join in query expressions Champion issue: https://github.com/dotnet/csharplang/issues/8947 Specification: https://github.com/dotnet/csharplang/blob/5a627e44b33f6c8765f11f3007de379b526684a0/proposals/left-right-join-in-query-expressions.md Finally today, we looked at a proposal for a new LINQ query-syntax operator. There's concern here, both in the LDM and in the discussion thread, about where the line for new features is. We can't really avoid query providers failing on these new joins; the methods will exist in the BCL regardless, and various query providers might fail on them no matter what. While query syntax support might widen that gap, it's very possible that lack of it will hurt users who need the new operator more than it would cause users to hit query provider errors, since users who need this will have to drop the entire query back to method syntax. Given this, we're cautiously optimistic about moving forward with this. We may want to expand further in the future; for example, C# does not support `distinct` in query syntax today, like VB does, but if increased `join` options are successful, that may be enough of a signal to add further operators; conversely, if this ends up causing significant increases in errors for users and lots of pain, we know that further changes here are harder than they appear at first glance. The LDM is unanimously in favor of moving forward with this, and a slight majority think we should try to get it in for C# 14, to coincide with the new methods being added to the BCL. #### Conclusion We will proceed with this proposal, hopefully in the C# 14 timeframe (barring any other time constraints). ================================================ FILE: meetings/2025/LDM-2025-02-19.md ================================================ # C# Language Design Meeting for February 19th, 2025 ## Agenda - [Extensions](#extensions) ## Quote of the Day - "But, SHOULD we throw the baby out with the bath water?" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/e25de8eaaa30470ecd1202c1b1ce840512b0ff0c/proposals/extensions.md Reviewed document: https://github.com/dotnet/csharplang/blob/e25de8eaaa30470ecd1202c1b1ce840512b0ff0c/meetings/working-groups/extensions/extensions-lookup.md Today, we went over continuing proposals for how to do extension method lookup. This topic is both persistent and difficult, particularly since how extension method lookup was originally specified is not how it was actually implemented. As originally specified, extension methods invoked in extension form would have been subject to a 2-phase lookup approach, where first the receiver is used to find compatible extension methods, and then overload resolution would be performed among those methods. As the LDM, we generally prefer this approach; it is the most consistent with how lookup works for actual instance members. It would also ensure that "weird" results, like the `out` examples shown in [extensions-lookup], behave as a user might naively expect, failing to unify because the extension is being called on `I`, not `I`. This potentially gets even more odd for static scenarios: ```cs var e = IEnumerable.M(out object o); // What type is e? Does this line compile? public static class Ext { extension(IEnumerable) { public static IEnumerable M(out T t) => ...; } } ``` However, in C# 3.0, this is _not_ what was implemented. The examples in [extensions-lookup] do not error, they compile, giving `IEnumerable` as the receiver type. Given the 20 years of history here, and how any change to lookup is nearly certain to break _someone_, we don't feel comfortable breaking existing extension methods, and may even want to simply update the specification to cover what was really implemented in C# 3.0, rather than what was originally intended. Further, every attempt we've made during the design process of new extensions at breaking compatibility has led to us scaling back the breaks. At this point, we're aware of 3 major categories of extension methods that cannot be ported to the new syntax form 1-1 given the current design: 1. Generic extensions that have a receiver that uses one or more of the type parameters and, at the call site, specify the type arguments. This is because they must specify _all_ the type arguments, including any for the receiver, and that's not possible with the current design, as the receiver type parameters cannot be specified. 2. Generic extensions that have a receiver that uses one or more of the type parameters and specify their type parameters in a non-idiomatic order. For example, a hypothetical `TReturn Select(this TThis t, Func selector)`. We are not aware of real-world examples of this pattern, and are more comfortable ignoring it. 3. The `out` inference issue that was brought up today. We are, at the very least, concerned about categories 1 and 3. Given this, we want the working group to investigate what a compatibility mechanism would look like that handles these cases, potentially by changing how lookup works for such members, and whether we can get to a place that needs no explicit compatibility switch, but instead bakes the compatibility into the language. [extensions-lookup]: https://github.com/dotnet/csharplang/blob/e25de8eaaa30470ecd1202c1b1ce840512b0ff0c/meetings/working-groups/extensions/extensions-lookup.md#aligning-with-implementation-of-classic-extension-methods ================================================ FILE: meetings/2025/LDM-2025-02-24.md ================================================ # C# Language Design Meeting for February 24th, 2025 ## Agenda - [Extensions](#extensions) - [Interpolated string handler method names](#interpolated-string-handler-method-names) ## Quote of the Day - Nothing particularly funny was said today, sorry. ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/3ee9dcaae3800cdbbf16bc50dffc483de16576af/proposals/extensions.md Reviewed document: https://github.com/dotnet/csharplang/blob/3ee9dcaae3800cdbbf16bc50dffc483de16576af/meetings/working-groups/extensions/Extension-API-docs.md Today, we took a break on extensions from talking about deep philosophical questions, to simply looking at what the docs team is proposing for how new extensions are going to be displayed. Unfortunately, a few of the major decisions that we still need to resolve around compat will likely impact what the docs team displays; can they just always use the new format, or will knowing whether an extension is new or old style be relevant to a user? That will hold up some of their work, but we otherwise don't have any particular notes on the current proposal. ### Interpolated string handler method names Champion issue: https://github.com/dotnet/csharplang/issues/9046 Specification: https://github.com/dotnet/csharplang/blob/a970d01597886d84d7498e1b6a9d8e8e8ebf02c1/proposals/interpolated-string-handler-method-names.md Finally, we took a look at this proposal around expanding interpolated string handler arguments to handle a logging scenario. There's consternation among the LDM around the relative lack of purity in this approach; after all, names aren't the real thing that the type author wants, it's the log level. And it would potentially limit reuse of handlers built with this approach, since users who want to reuse such handlers from the BCL would need to name their methods exactly as the BCL methods are named, otherwise it wouldn't work. On the other hand, some members of the LDM are concerned about the investment here, and whether we're going to reach our old standby of 100 points digging further into this problem. The existing workaround of copy/pasting the handler 7ish times isn't pretty, but these handlers are not a large maintenance burden and are not touched very often after creation, and this approach _would_ provide that reusability guarantee that the BCL needs. Given the divide on this issue, then, we'll put it into the "Needs Work" milestone, and if we have time in the future, we might consider revisiting it; at that point, we can compare what the more complex solution would look like with the simple approach in this proposal. #### Conclusion Needs work. If we find time to look at alternatives, we may bring them back in the future. ================================================ FILE: meetings/2025/LDM-2025-02-26.md ================================================ # C# Language Design Meeting for February 26th, 2025 ## Agenda - [Extensions](#extensions) ## Quote of the Day - "I tried to chase the pony... the lesson is that it's too hard." "I think that's the thing that scared me about this, we kept coming up with examples where the pony got hurt." ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/688ef1e9d1646597f2be9dd56b674a06b0f20c13/proposals/extensions.md Reviewed document: https://github.com/dotnet/csharplang/blob/688ef1e9d1646597f2be9dd56b674a06b0f20c13/meetings/working-groups/extensions/implicit-compatibility-for-ported-extension-methods.md [Last time](./LDM-2025-02-19.md#extensions), we asked the working group to go look at compatibility mechanisms for bridging old extension lookup and new extension lookup, and to come back with the implications of this. The working group has done so, and we are looking at the results of that investigation today. The first thing that we wanted to be clear about is that the potential breaks listed here aren't exhaustive: they're what we've found so far, some of them even as recently as the morning of the meeting. We do think that these breaks are likely to be edge cases in general, but we can't claim that for certain. We ultimately need to decide on what form of semantics we want to go with for extension lookup. We see 4 different possible options: 1. Use the 2-stage lookup process, and make no attempt to bridge semantics for compat. 2. Use the 2-stage lookup process, and have an explicit opt-in for users to get compat. 3. Use the 2-stage lookup process, and have an implicit fixup for compat. 4. Use the 1-stage lookup process, which comes with built-in compat because it's the same lookup process as existing extensions. We're particularly concerned about user experience in the future of C# here. We don't want to get to a world in 5 years where users need to understand the difference between old and new extensions, when to prefer the newer form over other, what overload resolution minutiae apply to either form, how to document the old form vs the new form, etc. We think any of the first two has that problem: authors will either need to know when to pick one or the other, or they will need to know when to opt-in to compat. Further, the users of libraries will need to know whether they are using the old or new extension form, which means figuring out a way to document this. Option 3 would be nice if we could figure it out, but we think we've sufficiently proven that this would be extremely difficult. 4 does mean that we abandon some niceties that we'd get with a new form. For example, it means that we couldn't let type parameters that come from the receiver of an extension method be inferred. However, we have a separate proposal (https://github.com/dotnet/csharplang/issues/8968) for allowing some type parameters to be dropped, and it would be more broadly applicable. Given all of this, we prefer option 4, and will proceed with having new instance extension methods use the same lookup algorithm that current instance extensions use. #### Conclusion Instance extension methods in `extension` blocks will use the same lookup mechanism as current extension methods do. ================================================ FILE: meetings/2025/LDM-2025-03-03.md ================================================ # C# Language Design Meeting for March 3rd, 2025 ## Agenda - [Extensions](#extensions) ## Quote(s) of the Day - "Ah ... you all think I'm a person and not a robot" "You've always passed the Turing test, as far as I can tell." "So can ChatGPT" - _deep conversation on when an extension is actually an extension_ _lemur yell in the room as a phone notification_ "What was that?! It sounded like a fire alarm!" _more pandemonium occurs_ ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/975ef97651519ccfdcb569c92c74d695afc054c1/proposals/extensions.md Today, we looked at more open issues in extensions. This time, we looked at a few specific questions from the proposal. #### Static method overloading Question: https://github.com/dotnet/csharplang/blob/975ef97651519ccfdcb569c92c74d695afc054c1/proposals/extensions.md#metadata > We should follow-up on "factory scenario" where multiple extension declarations have static factory methods with same parameter types but different return types. This scenario looks something like this: ```cs public static class Factory { extension(A) { public static A Create() => ...; } extension(B) { public static B Create() => ...; } } ``` By the existing rules, this would be blocked: C# methods cannot differ just by return type (though they can in IL). There may even be scenarios where two static methods on different underlying types may have exactly the same signature, with the same return type, which is definitely not expressible in IL without help, namely in the form of a `modopt` or `modreq` on the signature to differentiate them. If we wanted to try and allow this in the future via `modopt`/`modreq`s on the return type, we would have to start doing that now; it is a binary-breaking change to change the `modopt`/`modreq`s on a member, and we do not want to end up in a scenario where a user might break binary compat simply by adding an entirely unrelated member. There's also a concern that using `modopt`s would mean that you can't move from an extension static method to a regular static method (or vice versa) without a binary break. Making the static extension member a regular static member on the type (perhaps in response to a downstream library adding the method you were polyfilling) would become a binary breaking change, and we have some hesitance about doing that. Another option would be to mangle the names of these members somehow, however we think that these static extension members should be speakable for disambiguation purposes, and we don't want to have to expose some bespoke mapping from what appears to be a standard static method to a complex naming scheme. Part of what's driving this scenario is that `extension` blocks _feel_ like different scopes. They have braces, so we believe that it's natural to assume that you can overload across them without issue. This does break down somewhat when examined closely: you can have multiple extension blocks for the same type, so would that mean that each block can overload on the same extended type? However, this natural inclination seems somewhat reasonable to us, as does having a single static class devoted to having factory methods for a number of different underlying types. Given this, we want to explore the `modopt` approach. The working group will do so, and come back to us with the consequences of this decision. That being said, we do feel that we are leaning towards blocks not actually being different scopes. They do seem like it at first glance, but the decisions we've made around them so far are leaning towards them not being real scopes. #### Method and property resolution Question: https://github.com/dotnet/csharplang/blob/975ef97651519ccfdcb569c92c74d695afc054c1/meetings/working-groups/extensions/extensions-lookup.md#extension-methods-proposal Next, we turned our attention to member lookup, especially for `static` methods, and for when properties are combined with methods. We largely agree with the rules proposed, and our discussion from the first question and from [last time](./LDM-2025-02-26.md#extensions). We want these to be standard methods, so applying standard rules in other aspects makes sense and is consistent. We value that consistency argument, and so we want to use the same rules as much as possible. This means standard lookup for static extensions scenarios, and if lookup finds both properties and methods in the same set, that's an error. We can potentially look at improvements to the method vs property scenario later, if we find that it is a problem in practice, but we don't expect it to be. We'll have a formal specese version of these rules to review later, but for now, they are tentative accepted as proposed. ================================================ FILE: meetings/2025/LDM-2025-03-05.md ================================================ # C# Language Design Meeting for March 5th, 2025 ## Agenda - [Dictionary expressions](#dictionary-expressions) - [Target-typed static member lookup](#target-typed-static-member-lookup) ## Quote of the Day - "I'm sure there are some funky types out there like in old .NET" "I plead the 5th" - "Give the dot a shot" ## Discussion ### Dictionary expressions Champion issue: https://github.com/dotnet/csharplang/issues/8659 Specification: https://github.com/dotnet/csharplang/pull/9158/files#diff-342dab893eaa8c901a2fafe92e2c6c13aaee9e42adcbd9115fac92ac0d1b9a22 We reviewed the proposed conversion and construction rules for concrete dictionary types that don't use `CollectionBuilderAttribute`. __The conversion rules__ pertain to how we decide that a given type is a dictionary type. For the pertinent situation, where the type is not an interface and does not provide a `CollectionBuilderAttribute`, the proposal requires the type to have an iteration type of `KeyValuePair` for some `TKey` and `TValue`, as well as a matching get-set indexer. If the type explicitly implemented e.g. `IDictionary` we wouldn't recognize it as a dictionary - the type itself needs to have the right members. When a type is a dictionary type, collection expressions will be implemented using its indexer rather than `Add` methods. __The construction rules__ pertain to how we bind to the indexer on such a type. For _key value pair elements_ `e1:e2` the proposal invokes the setter of the best applicable indexer, based on the key and value expressions in each pair. This means that the invoked indexer is not necessarily the one that qualifies the type as a dictionary type in the first place, by the conversion rules above. An alternative would be to always use the indexer that matches the iteration type - the one that made the type a dictionary type. For collection expressions we needed more nuance, because there were existing expectations around what would work, based on collection initializers. However, for indexers there is not a corresponding existing initializer case to be consistent with. For _expression elements_ `e` and _spread elements_ `..e` the proposed rules require elements to implicitly convert to `KeyValuePair`, exactly matching the iteration type of the dictionary type. There was some concern that this is too restrictive. A more permissive alternative would be to allow elements to be `KeyValuePair<...>` ("`KVP`" for short) with different type arguments, as long as those type arguments are implicitly convertible to `TKey` and `TValue` respectively. This would be restrictive in a different way, since the elements would have to _be_ a KVP, not just implicitly convert to one. This could even lead to breaking changes, where ordinary collection expressions have worked for dictionary types in the past due to user-defined conversions to KVP. A combined alternative would look for any KVP types that the element is implicitly convertible to, and _if there is exactly one_ check that its type arguments have implicit conversions to `TKey` and `TValue`. This would mitigate the potential breaking change, but searching for a unique implicit conversion seems a bit dubious. #### Conclusion * The rules for when a type is considered a dictionary are approved as proposed. * The indexer used in dictionary expressions should be the one that qualifies the type as a dictionary type, not the best fit based on overload resolution. * In expression and spread elements we'll allow them to be KVPs with different key and value types, as long as those are in turn implicitly convertible to the key and value types of the dictionary. For now the elements have to _be_ KVPs, not just implicitly convertible to them. If that becomes a problem we are open to loosening the rules, but we don't have a good proposal for that yet. ### Target-typed static member lookup Champion issue: https://github.com/dotnet/csharplang/issues/9138 Specification: https://github.com/dotnet/csharplang/blob/e2e957687cf2a165fb1a0fb05f1d72ddfbe8f932/proposals/target-typed-static-member-lookup.md The feature allows the static members of a type to be accessed directly without qualification when the expression is target typed by that type: ``` c# type.GetMethod("Name", .Public | .Instance | .DeclaredOnly); // BindingFlags.Public | ... ``` The proposal meshes well with some proposals for discriminated unions, but is useful for today's classes or enums. It supports the common case where a type has static members _of_ that type. Enums are common, and nested derived types are a way to express DUs today. Also, special values or factories are often exposed this way. This is a place where a lot of redundancy and repetition exists today, especially when types are large and/or generic. Static usings aren't really an adequate response. They bring the members into scope _everywhere_, which can be very noisy, and for generic types they only allow specific instantiations to be included. Using a sigil (`.`) in front of the name helps avoid breaking changes or convoluted lookup rules. It helps narrow the field of possible completions and may improve understanding of the code. At the same time there's also a lot of resistance to having a sigil instead of just a name. It feels like users may have difficulty realizing _when_ they could choose to use this feature. Without the dot it would feel like the compiler just knows what you mean. Perhaps there is a path we can take that allows us to get feedback from the ecosystem on this question? Finally there's also an argument that the whole feature makes code harder to read, and the meaning of names too complex to understand, regardless of syntax. #### Conclusion Most of us feel the feature is valuable, and we want to continue investigating and debating. The syntax question is a stumbling block for sure. ================================================ FILE: meetings/2025/LDM-2025-03-10.md ================================================ # C# Language Design Meeting for March 10th, 2025 ## Agenda - [Extensions](#extensions) - [Property method calling](#property-method-calling) - [Scoping and Shadowing](#scoping-and-shadowing) - [Type parameter inferrability](#type-parameter-inferrability) - [Accessibility](#accessibility) ## Quote of the Day - "We're starting the boring part of the meeting now" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/82157ff229f6c1bd2954a96ec2acb47064a48bad/proposals/extensions.md We looked at a few questions in extensions today. #### Property method calling The first question we looked at was whether to allow extension property underlying methods to be called directly on an instance. For example: ```cs _ = new object().Prop; // Allowed _ = C.get_Prop(new object()); // Previously said this is allowed _ = new object().get_Prop(); // Should this be allowed? public static class C { extension (object o) { public int Prop { get => 1; } } } ``` We're not sure what the use case for this form is. We allow the `get_Prop` syntax on `C` itself to serve as a disambiguation syntax, but we don't know why `get_Prop` should be visible as a method on `object` itself. We don't do this for regular properties, so we don't have a reason to allow it for extension properties either. Given this, we will disallow it. ##### Conclusion `get_Prop` form is disallowed in extension method form. #### Scoping and Shadowing Next, we looked at a couple of questions around scoping and shadowing. First, should extension parameter names be in scope in static members, even if they'd be an error to reference, and even if they shadow a field? And second, should instance methods be able to shadow the extension parameter in their own parameter lists? As an example: ```cs public static class E { static string s; extension(string s) { public int M(int i) { return s.Length + i; } public static string P => s; // Does this error that the extension parameter s can't be used, or bind to E.s? public void M2(string s) { ... } // Should we allow this to shadow the extension parameter? public static void M3(string s) { ... } // Should we allow this to shadow the extension parameter, since there's no implicit s? public static void M4() { s.ToString(); } // Does this bind to the extension receiver parameter and error, or to E.s? } } ``` There are a couple of sets of rules we can look to for inspiration here: existing parameter rules, and primary constructor rules. After some thought, we think existing parameter rules are the right inspiration; as mentioned in the previous topic, we keep leaning further towards the extension block being just another parameter. This means it should follow the same rules for other parameters, and when we consider instance extension methods, `M2` isn't shadowing anything, it's literally declaring 2 parameters with the same name, which is just not permitted by C#'s rules. On the other hand, local functions or lambdas inside `M2` would be allowed to shadow the parameter `s`, just like they can with other parameters today. More interesting is the `static` question: should we allow the parameter list in a `static` member to shadow the extension parameter? The LDM is fairly split here between allowing or disallowing it. We therefore think we'll start most restrictive, disallowing the parameter list to shadow, and wait for feedback. There is a workaround for users who want to do this, as they can just declare a new parameterless extension block and declare the method in that block, so this isn't a hard blocker for code, but we'd rather start as more restrictive and loosen when we have real examples, rather than starting with a loose rule that we may regret later. ##### Conclusion Extension block parameters will be lexically in scope in all members declared within the block, and will be treated as if they were declared in the parameter list of each extension member for the purposes of naming collisions and shadowing. #### Type parameter inferrability Question: https://github.com/dotnet/csharplang/blob/82157ff229f6c1bd2954a96ec2acb47064a48bad/proposals/extensions.md#extension-declaration-validation (first bullet) We next looked at whether we should loosen the requirement that any type parameters in the extension block declaration had to be inferrable from the extension parameter. We don't think that we have the examples that would be necessary to lift this restriction today: we can think of a couple of hypotheticals, such as an out-of-order `TResult, TSource`, or a set of type parameters where `TSource` needed to be constrained based on `TResult`, but we don't have any concrete examples of this. Given that, we think we should start most restrictive, and wait for users to bring examples to us. ##### Conclusion We will not loosen the restriction, all type parameters in the extension block declaration must be inferrable from the extension parameter. #### Accessibility Question: https://github.com/dotnet/csharplang/blob/82157ff229f6c1bd2954a96ec2acb47064a48bad/proposals/extensions.md#accessibility Finally today, we took a brief look at accessibility to get an initial gut feeling. The expressed sentiment in the room leaned towards `private` being with respect to the entire static class, not towards an individual extension block, but we did not have time to deeply dive into this topic, so no conclusions were made. ================================================ FILE: meetings/2025/LDM-2025-03-12.md ================================================ # C# Language Design Meeting for March 12th, 2025 ## Agenda - [Optional and named parameters in expression trees](#optional-and-named-parameters-in-expression-trees) - [Collection builder method parameter order](#collection-builder-method-parameter-order) - [Ignored directives](#ignored-directives) ## Quote of the Day - "I'm here for 5 minutes of [redacted] before everyone gets back!" _no one speaks for the 5-minute bio break_ "That was an exciting 5 minutes." - "I think we should accept C#'s age of over 25 years and go with octothorpe" ## Discussion ### Optional and named parameters in expression trees Champion issue: https://github.com/dotnet/csharplang/issues/9246 Specification: https://github.com/dotnet/csharplang/blob/b200d0a075dbeaff5a5b2eaab7b529102d5483af/proposals/optional-and-named-parameters-in-expression-trees.md First up today, we took a look at a change around expression trees to support something that seems like it should just fall out. This isn't a broad change, nor is it any kind of new node: it's simply removing an error. Doing searches through the history of C#, we're unsure why this is even restricted in the first place; our notes from the C# 4 timeframe don't indicate that any consideration was put into this topic, and no one still in the LDM who was present at the time can remember any discussions about blocking this. Likely, it was simply just forgotten about. Given that, we have a few approaches we can take: 1. Do nothing and keep the status quo. 2. Allow optional arguments. These will be filled in by the compiler, and emitted as if they had been called by the user. 3. Allow named arguments in-order. These will have no impact on the shape of the tree. 4. Allow named arguments out-of-order. This would potentially have impacts on the shape of the tree. While we likely could avoid adding new nodes for this, it would mean the compiler would start emitting existing node types that were never emitted before, which would be a breaking change for linq providers. We think we're fine with 2, and with 3. 4 is more complicated; the compiler doesn't emit any `BlockExpression`s today, if we were to have the C# compiler support out-of-order named parameters, it would need to do so in order to replicate C# named argument behavior, by saving the out of order results and then passing them to the method call so side effects happen in the correct order. We don't like this, and don't think the potential risk is worth it. Therefore, we plan to proceed with options 2 and 3. We don't think 4 is entirely out of the question, but it would need to be a detailed investigation into where and how query providers would break or support this node. #### Conclusion We will allow optional parameters in expression trees, and we will allow named arguments, so long as the arguments are supplied in parameter order (a la the C# 7.2 feature rules for non-trailing named arguments). ### Collection builder method parameter order Champion issue: https://github.com/dotnet/csharplang/issues/8887 Specification: https://github.com/dotnet/csharplang/blob/0d5e1f2c0864e65c6163e4a06405720493ba018a/proposals/collection-expression-arguments.md#collection-builder-method-parameter-order Next, we looked at a small open question in collection expressions around the `Create` method, and where it will expect parameters to be located. We have a standard pattern in the BCL for such create methods, which is to have arguments such as the comparator first, and then the actual contents of the collection to be created. We'd prefer to stick with this pattern for now, absent examples that need some other pattern. If we do find examples that need a different order, we can consider some way for a type author to explicitly indicate which parameter is the "collection contents" parameter in the future, such as an attribute or other piece of metdata on the parameter. Until we have such examples, though, we'll go with the simple solution of the contents must be the last parameter. #### Conclusion The `ReadOnlySpan` of collection contents must be the last parameter of the `Create` method. ### Ignored directives Champion issue: https://github.com/dotnet/csharplang/issues/8617 Specification: https://github.com/dotnet/csharplang/blob/a71774147c58d45efb5c6515c41665267684c9c5/proposals/ignored-directives.md Finally today, we took a look at this proposal for ignored directives. This was a proposal opened a number of years ago in anticipation of `dotnet run file.cs` eventually becoming a thing, and now that that effort is moving forward, it's time to talk about how the C# file will interact with this system. We want the system to be able to iterate without needing direct input from the compiler for every change. Given that, we think the bespoke `#sdk`, `#package`, and other directives shouldn't exist. We also think that just using `#!` for everything is a bad idea; it would complicate parsing and set up scenarios where the first line of the file isn't actually a valid `#!` directive for running the file. For now, we like the `#:` proposal; use the `#!` directive specifically for shell communication as the first line in the file, and `#:` for SDK communication otherwise. We like the brevity of the directive for this purpose, but would potentially be open to other syntaxes based on feedback from initial usage. #### Conclusion We accept `#!` and `#:` as the directive syntaxes for this feature, and look forward to working with the SDK and ecosystem on driving this feature forward. ================================================ FILE: meetings/2025/LDM-2025-03-17.md ================================================ # C# Language Design Meeting for March 17th, 2025 ## Agenda - [Collection expression arguments](#collection-expression-arguments) - [`with` breaking change](#with-breaking-change) - [Collection expression arguments and conversions](#collection-expression-arguments-and-conversions) - [Dictionary expressions](#dictionary-expressions) - [Extensions](#extensions) - [Accessibility](#accessibility) - [Static factory scenarios](#static-factory-scenarios) ## Quote of the Day - "I hope no one was confused by me saying coversion instead of conversion" "Contraversion" ## Discussion ### Collection expression arguments Champion issue: https://github.com/dotnet/csharplang/issues/8887 Specification: https://github.com/dotnet/csharplang/blob/d139d200cfe8ffe45536cdcc8b17ee7f25b5757b/proposals/collection-expression-arguments.md #### `with` breaking change Question: https://github.com/dotnet/csharplang/blob/d139d200cfe8ffe45536cdcc8b17ee7f25b5757b/proposals/collection-expression-arguments.md#with-breaking-change We started today by looking at whether, and if so where, to take a breaking change around the meaning of `with()` in a collection expression. We don't want `[with(1), with(2)]` to have different meanings for what `with` binds to, so we're ok with a breaking change in general. However, we haven't yet had the in-depth debate on whether `with` is the final syntax; given this, we don't want to risk anything on lower language versions at this time. ##### Conclusion We will take the breaking change around `with` inside a collection expression, but only in language version preview for now. #### Collection expression arguments and conversions Question: https://github.com/dotnet/csharplang/blob/753119dee9eb5e5a4bd36072559b25d9064c63e6/proposals/collection-expression-arguments.md#should-arguments-affect-collection-expression-conversion We think that the precedent around `new()` is good here. It's possible that we could relax the language here in the future, but we think that if we do so, it should be done holistically, for both `new()` and collection expressions, rather than just for collection expressions. `new()` doesn't do this because of the concern that it would mean adding a new constructor to a type could become a source-breaking change, and we think that we'd have to delve into that concern for both constructs at the same time. ##### Conclusion Collection expression arguments will not be used as part of determining whether a collection expression conversion exists. ### Dictionary expressions Champion issue: https://github.com/dotnet/csharplang/issues/8659 Specification: https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md Question: https://github.com/dotnet/csharplang/blob/d139d200cfe8ffe45536cdcc8b17ee7f25b5757b/proposals/dictionary-expressions.md#conversion-from-expression-element-for-keyvaluepairk-v-collections Following up from [last time](./LDM-2025-03-05.md#dictionary-expressions), the working group brought a proposal around key/value pair conversions in dictionary expressions. We're a bit concerned about the broad applicability of the proposed rules: they block off user-defined conversions when the iteration type is a KVP, even if we're not converting to a dictionary expression. That seems wrong to us; the user indicated how to convert a given type to a KVP, so why would we ignore that information? Our desire for covariance in KVP does make this more complicated than a standard conversion though. For example, what if the type specifies 2 UDCs to different KVP instantiations? Which one would we prefer for a variance conversion to the iteration type? After some discussion, we think that such "multiple conversion path" types are a sufficiently small corner of a corner that we don't have to worry about it: either better conversion will return an exact conversion to the element type of the collection expression, or we'll get an ambiguity. We also briefly re-examined whether we actually want to support these variance scenarios outside of pure dictionary types, and our conclusion is that we do. There are thousands of examples of `IEnumerable` APIs on GitHub, for methods that take any form of dictionary or list of KVPs, and we would like to support simple syntax for such APIs. ##### Conclusion We will allow all standard element type conversions that we allow in normal collection expressions when the element type is a KVP. We will allow KVP covariance in all collection expressions. ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/b484275c4369638c2c5f9305716582c76a1c9335/proposals/extensions.md #### Accessibility Question: https://github.com/dotnet/csharplang/blob/b484275c4369638c2c5f9305716582c76a1c9335/proposals/extensions.md#accessibility First up today, we continued from [a previous LDM](./LDM-2025-03-10.md#accessibility) on the topic of accessibility. We think our initial gut feeling was correct; extension blocks are not real items. You can declare multiple extension blocks for a single type, would `private` be private just to a single block, or to all shared blocks? There are also other decisions that we'd need to revisit if we started giving blocks a stronger identity here. ##### Conclusion Accessibility in extension blocks is relative to the containing static class. #### Static factory scenarios Question: https://github.com/dotnet/csharplang/blob/b484275c4369638c2c5f9305716582c76a1c9335/proposals/extensions.md#static-factory-scenario The working group took a look at the [previous decision](./LDM-2025-03-03.md#static-method-overloading) around static method overloading, and has come to the conclusion that there are current technical limitations around `modopt`/`modreq`s in the compiler that would make using them to permit overloading here difficult; today, Roslyn does not support using anything but a named type in a `modopt` or `modreq`. This is a compiler restriction, not a runtime restriction, but would be a somewhat involved change to fix up. However, we're also questioning whether we should support this type of overloading at all in extensions: sure, we can figure out how to emit such methods, but what would the disambiguation syntax be? We've already walked back from adding new syntax in other places for this, and while we do think that the scenario where a user wants to define the same static method on several types isn't unreasonable, we also think that we might be able to start restrictive for a preview period, and then listen for feedback. ##### Conclusion We will enforce standard overloading rules across extension blocks, so static methods on different underlying types in the same static class will not be able to share the same name and parameter types. ================================================ FILE: meetings/2025/LDM-2025-03-19.md ================================================ # C# Language Design Meeting for March 19th, 2025 ## Agenda - [Readonly setters on non-variables](#readonly-setters-on-non-variables) ## Quote of the Day - "Sorry for the shorter meeting today" ## Discussion ### Readonly setters on non-variables Champion issue: https://github.com/dotnet/csharplang/issues/9174 Specification: https://github.com/dotnet/csharplang/blob/d139d200cfe8ffe45536cdcc8b17ee7f25b5757b/proposals/readonly-setter-calls-on-non-variables.md Today, we looked at a proposal to lift an error scenario around `readonly`. One interesting thing with this is that we're already in an inconsistent state; we permit `readonly` setters to be called on the result of a `ref`-returning method, for example. This is likely an unintended "bug" in the implementation of `ref`-readonly methods, but at initial glance we could just make that the explicitly-allowed behavior and standardize on that. There was some discussion on named indexers; this is one of the primary use cases given to enable this language feature, but is that motivation enough for this language feature? Or should we just do named indexers directly? However, as we previously noted, we want the compiler to get to a consistent state; there are a few other motivating scenarios that we can discuss extending this feature to as well in future design sessions. We also brought up some concerns that this could potentially enable new dropped value scenarios, where the struct is creating a new reference type under the hood and then being dropped. However, there's nothing unique to `struct`s here; the same type of issue as might happen when a user uses an expression-bodied property of `{ get; } = new();` instead. Given all of this, we think that we're fine with this proposal to move forward. We'll put it in "Any Time" for now, and revisit potential extensions to it at a later date. #### Conclusion Proposal is accepted, put into "Any Time". ================================================ FILE: meetings/2025/LDM-2025-03-24.md ================================================ # C# Language Design Meeting for March 24th, 2025 ## Agenda - [Dictionary Expressions](#dictionary-expressions) - [Support dictionary types as `params` type](#support-dictionary-types-as-params-type) - [Type inference for `KeyValuePair` collections](#type-inference-for-keyvaluepairk-v-collections) - [Overload resolution for `KeyValuePair` collections](#overload-resolution-for-keyvaluepairk-v-collections) - [Extensions](#extensions) - [Signature conflict rules](#signature-conflict-rules) - [`extension` vs `extensions`](#extension-vs-extensions) ## Quote(s) of the Day - "A syntax question with no hands? That's impossible" ... "I'm going to mis-spell whichever one we choose anyways" - "What is snoisnestringsxEg?" "Something happened in liveshare" "Oh, that's not the syntax suggestion?" ## Discussion ### Dictionary Expressions Champion issue: https://github.com/dotnet/csharplang/issues/8659 Specification: https://github.com/dotnet/csharplang/blob/bcc689fafe8c2d226b8a3034eb7f66f48495e3aa/proposals/dictionary-expressions.md #### Support dictionary types as `params` type Question: https://github.com/dotnet/csharplang/blob/bcc689fafe8c2d226b8a3034eb7f66f48495e3aa/proposals/dictionary-expressions.md#support-dictionary-types-as-params-type Our first topic today is about whether we should support `params` parameters of dictionary types. This is really a breaking change, as it means that we would potentially use an indexer for a dictionary-like type, where we currently use an `Add` method. That would likely result in a change from throwing semantics to overwrite semantics, so anyone depending on potential exception behavior will see a behavioral difference. However, that's nothing specific to `params`, as the same is true for any other collection expression targeted to a dictionary-like type, so we're not concerned about the potential for breaks in this specific corner. We like this change overall, as it keeps the language simpler, even if the compiler is more complex. The goal we had with expanding `params` was that anything you could use a collection expression for, you could `params`. Rather than re-adding the decoder ring of "what can be `params`'d vs collection expression'ed", we keep the correspondence simple, though at the expense of more compiler developer work. ##### Conclusion We will allow use of `params` on dictionary-like types that can be targeted with a collection expression, and constructing those types will prefer using indexers when available, just like when using a collection expression to create that same type. #### Type inference for `KeyValuePair` collections Question: https://github.com/dotnet/csharplang/blob/bcc689fafe8c2d226b8a3034eb7f66f48495e3aa/proposals/dictionary-expressions.md#type-inference-for-keyvaluepairk-v-collections We like these proposed rules, as they honor the goal of making KVP as transparent as tuples for these types of targets. There is a minor breaking change here as well, in that type inference can now succeed where it previously failed, meaning that overload resolution may find a nearer applicable method (for example, not going out to extensions because there's now an applicable instance method). We're ok with this type of break, so we'll accept these rules as written. ##### Conclusion Rules accepted as written. #### Overload resolution for `KeyValuePair` collections Question: https://github.com/dotnet/csharplang/blob/bcc689fafe8c2d226b8a3034eb7f66f48495e3aa/proposals/dictionary-expressions.md#overload-resolution-for-keyvaluepairk-v-collections Again, we like the analogy with tuple behavior that is happening here. If we ever run into a scenario where these rules behave differently than tuples would in this same area, we should bring that back for discussion. Otherwise, this is approved. ##### Conclusion Rules are accepted as written ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/bcc689fafe8c2d226b8a3034eb7f66f48495e3aa/proposals/extensions.md #### Signature conflict rules Question: https://github.com/dotnet/csharplang/blob/bcc689fafe8c2d226b8a3034eb7f66f48495e3aa/proposals/extensions.md#extension-declaration-validation > The current conflict rules are: > 1. check no conflict within similar extensions using class/struct rules, > 2. check no conflict between implementation methods across various extensions declarations. > > Do we stil need the first part of the rules? First up in extensions, we looked at whether we still need/want both parts of the extension validation rules. The 2 rules serve different purposes. The second rule covers our ability to actually emit method signatures; this is the rule that prevents two extension blocks on different types from having the same static method. The first rule is more about preserving class experience; this is the rule that prevents having a static and instance method with the same name and parameters on the same type. We're hesitant to remove this rule without further examples of where it would be useful, and more exploration of where it would be problematic. This rule also protects things like being unable to declare both `int Prop { get; }` and `void set_P(int i) { }` in the same type, and is intended to help prevent concerning consumption scenarios. For example: ```cs class C { static Color Color { get; } static void Main() { Color.M(); // What M is called? } } class Color { public static void M() {} public void M() {} } ``` We don't want to mess with validation of so-called `Color`/`Color` scenarios now, so absent further evidence, we will keep the rules as is. ##### Conclusion Both rules are retained. #### `extension` vs `extensions` Question: https://github.com/dotnet/csharplang/blob/bcc689fafe8c2d226b8a3034eb7f66f48495e3aa/proposals/extensions.md#open-issues Finally today, we have a syntax bikeshed; do we prefer `extension` or `extensions` as the keyword? After some debate, we boiled this down to whether we think of the entire block as a true group of items or not. Or, in other words, can we think of moving the parameter down to the individual methods involved and have everything still be exactly the same? We have repeatedly concluded that yes, we do not think of the block as a construct with true meaning; writing out 10 extension blocks on the same underlying type, or grouping them together into a single block, has the same meaning. Therefore, we will continue with the name `extension`. We also had a small discussion on what to name the grouping of extension methods itself; we'd like to have a consistent name to use in documentation, in compiler API names, etc. We spitballed a number of options: * extension declaration * extension block * extension body * extensions * extension group * extension container Only "extension group" and "extension block" attracted any support, and of them "extension block" was heavily preferred, and not disliked by anyone. Therefore, we will call this grouping an "extension block". ##### Conclusion The keyword is `extension`, and the term for the entire set of extension methods is an "extension block". ================================================ FILE: meetings/2025/LDM-2025-04-02.md ================================================ # C# Language Design Meeting for April 2nd, 2025 ## Agenda - [User Defined Compound Assignment Operators](#user-defined-compound-assignment-operators) - [Should readonly modifier be allowed in structures?](#should-readonly-modifier-be-allowed-in-structures) - [Should shadowing be allowed](#should-shadowing-be-allowed) - [Should we have any consistency enforcement between declared += and + operators?](#should-we-have-any-consistency-enforcement-between-declared--and--operators) - [Readonly setters](#readonly-setters) - [Expansions](#expansions) ## Quote(s) of the Day ```csharp var csharp14 = [improvement1, improvement2, improvement3] ``` * There isn't a deep need to rescue people here. ## Discussion ### User Defined Compound Assignment Operators Champion issue: https://github.com/dotnet/csharplang/issues/9101 Specification: [User Defined Compound Assignment Operators.](https://github.com/dotnet/csharplang/blob/f0c692748ee5067fd59ed0ece46d79a408fe50d0/proposals/user-defined-compound-assignment.md) Discussion: https://github.com/dotnet/csharplang/discussions/9100 #### Should readonly modifier be allowed in structures? Question: https://github.com/dotnet/csharplang/blob/f0c692748ee5067fd59ed0ece46d79a408fe50d0/proposals/user-defined-compound-assignment.md#should-readonly-modifier-be-allowed-in-structures Our first topic today is about whether we should allow a `readonly` modifier for compound assignments. The current specification requires the target of the operator to be a variable. In some cases, these could be beneficial - such as in wrapper classes that expose a subset of a type's surface. Also, there was concern about `readonly` structs needing to have all members `readonly`. Full support would require additional design work. ##### Conclusion We will allow `readonly` modifiers, but we will not relax the target requirements at this time. #### Should shadowing be allowed Question: https://github.com/dotnet/csharplang/blob/f0c692748ee5067fd59ed0ece46d79a408fe50d0/proposals/user-defined-compound-assignment.md#should-shadowing-be-allowed We discussed that the implementation of user-defined operators is via methods and that behavior that did not match methods may seem strange. ##### Conclusion Shadowing will be allowed with the same rules as methods. #### Should we have any consistency enforcement between declared += and + operators? Question: https://github.com/dotnet/csharplang/blob/f0c692748ee5067fd59ed0ece46d79a408fe50d0/proposals/user-defined-compound-assignment.md#should-we-have-any-consistency-enforcement-between-declared--and--operators This was a follow-up on concerns raised in [LDM-2025-02-12](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-02-12.md#user-defined-instance-based-operators). Authors may accidentally push their users into odd scenarios where a += may work, but + won't (or vice versa) because one form declares extra operators than the other. There were concerns about enforcing such a rule. ##### Conclusion Checks will _not_ be done on consistency between different forms of operators. ### Readonly setters Champion issue: https://github.com/dotnet/csharplang/issues/9174 Specification: [Readonly setter calls on non-variables](https://github.com/dotnet/csharplang/blob/e34ecf622058b53f0fb37705baa8597ea045d378/proposals/readonly-setter-calls-on-non-variables.md) Discussion: https://github.com/dotnet/csharplang/discussions/2068 #### Expansions Question: https://github.com/dotnet/csharplang/blob/e34ecf622058b53f0fb37705baa8597ea045d378/proposals/readonly-setter-calls-on-non-variables.md#expansions There are cases where it is appropriate to error if a non-readonly setter is called, that may be reasonable when a `readonly` setter is called. We explored two of those cases that currently error with _CS1918 Member of property ... cannot be assigned with an object initializer because it is of a value type._ Should we include allowing readonly setters in these cases as part of the broader feature. A change would be needed to the C# spec on object initializers ([§12.8.16.3](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#128163-object-initializers)), which will be added to this proposal. It was noted that expansions could be split off and done after the rest of the work in this proposal. ##### Conclusion We will include expansions in the _Readonly Setter calls on non-variables_ proposal ================================================ FILE: meetings/2025/LDM-2025-04-07.md ================================================ # C# Language Design Meeting for April 7th, 2025 ## Agenda - [Breaking change discussion: making partial members in interfaces virtual and/or public](#breaking-change-discussion-making-partial-members-in-interfaces-virtual-andor-public) - [Interpolated string handler argument values](#interpolated-string-handler-argument-values) ## Quote of the Day - "We can have multiple arguments about it, further down the road" ## Discussion ### Breaking change discussion: making partial members in interfaces virtual and/or public Breaking change: [Compiler breaking changes](https://github.com/dotnet/roslyn/blob/db643157d8c64d47beb0a7d627244629a5116cd1/docs/compilers/CSharp/Compiler%20Breaking%20Changes%20-%20DotNet%2010.md#extended-partial-members-are-now-implicitly-virtual-and-public) Pull request: [dotnet/roslyn#77379](https://github.com/dotnet/roslyn/pull/77379) First up today, we looked at a potential bugfix for a longstanding behavior, since enhanced partial members were introduced into the language. This behavior was initially noticed when implementing partial events, and further investigation has shown that it affects all partial members in interfaces. While we don't like this inconsistency, we also don't think this behavior is particularly affecting anyone; we were the ones to discover it, rather than a bug being reported, and the behavior in methods has been around for a long time. While we're fine with what is effectively a bug fix for partial properties, which have not been out for long, we're not of the opinion that changing the behavior for partial methods in interfaces is worth the effort or risk potential. Instead, we'd rather just leave that as is. #### Conclusion We will take this bugfix for partial properties and events, but not for methods. Since partial properties are so new, we will not tie it to language version unless divisional breaking change procedures require us to do so. ### Interpolated string handler argument values Champion issue: https://github.com/dotnet/csharplang/issues/9046 Specification: https://github.com/dotnet/csharplang/blob/4df926c14494c5990bd1c27380180eab90c56cb3/proposals/interpolated-string-handler-argument-value.md Following up from [February 24th](./LDM-2025-02-24.md#interpolated-string-handler-method-names), we have a new proposal around how to pass constant values to interpolated string handlers for parameterization. This version of the proposal allows passing a single constant value via the attribute, which will be added after anything passed via `InterpolatedStringHandlerArgumentAttribute`. We thought about whether we would want to support multiple values, but we currently don't have any scenarios that need such capabilities, and we don't want to design that without a use case to validate the design against. To ensure that we have future ability to design this, though, we will disallow arrays as arguments, in case that is the approach we decide to take in the future. #### Conclusion Proposal is approved. Arrays are disallowed as argument values to preserve design space. ================================================ FILE: meetings/2025/LDM-2025-04-09.md ================================================ # C# Language Design Meeting for April 9th, 2025 ## Agenda - [Dictionary expressions](#dictionary-expressions) - [Collection expression arguments](#collection-expression-arguments) ## Quote of the Day - "Because we test everything, to our credit and our horror" ## Discussion ### Dictionary expressions Champion issue: https://github.com/dotnet/csharplang/issues/8887 Specification: https://github.com/dotnet/csharplang/blob/9735444553fbfaacdff76e77ef6ef0c14f1fcccb/proposals/dictionary-expressions.md#concrete-type-for-ireadonlydictionaryk-v First up today, we reviewed a question on what the default type for dictionary expressions targeted at `IDictionary` and `IReadOnlyDictionary` types. For this, we needed to untangle our previous design work around collection expressions: in 2023, we'd [decided to not guarantee `List`](https://github.com/dotnet/csharplang/blob/9735444553fbfaacdff76e77ef6ef0c14f1fcccb/meetings/2023/LDM-2023-08-09.md#conclusion-1), but this is not what was specified. This was due to some misunderstandings between the working group and the broader LDM; we wanted to start by understanding the principles the working group used to decide on the current rule, and then apply that to dictionary expressions. There are two aspects: 1. For the readonly interfaces (namely, `IEnumerable`), we wanted users to be able to rely on that readonly-ness. It was important to the group that it wouldn't be possible to downcast and mutate the collection. 2. For the mutable interfaces, it wasn't clear what the benefit of not guaranteeing `List` would be. This is the BCL's flagship collection type, and the one that has the largest optimization effort throughout the BCL. Given these reasons, we accept that as the new motivating set of rules for collection expressions, and we have a clear guideline of how to apply the same rules for dictionary interfaces as well. For `IReadOnlyDictionary`, the compiler should guarantee readonly-ness. It is free to use any implementation it so desires, so long as it's a "conforming" implementation, much like the specification for collection expression talks about conforming implementations. The precise rules for conformance should be brought to LDM for approval, but they need to ensure that the dictionary is truly readonly and cannot be mutated, even if viewed through an interface that may optionally allow mutation (for example, returning `True` for `ICollection.IsReadOnly`, and throwing on `ICollection.Add`, if it does in fact implement `ICollection`). For the mutable dictionary interfaces, we don't see a reason to not just guarantee `Dictionary`; it is the flagship dictionary type of .NET, and the one that has the largest optimization effort throughout the BCL. If this changes at some point in the future, we can look at it then. #### Conclusion We will guarantee `Dictionary` for the mutable dictionary interfaces. We will allow the compiler to use any compliant BCL or synthesized type for the readonly dictionary interfaces, where compliant means that it must ensure that the dictionary cannot be mutated by downcasting or alternate interface views. We update the assumptions for collection expressions that constructing an `IList` or `ICollection` uses a `List`. ### Collection expression arguments Champion issue: https://github.com/dotnet/csharplang/issues/8887 Specification: https://github.com/dotnet/csharplang/blob/9735444553fbfaacdff76e77ef6ef0c14f1fcccb/proposals/collection-expression-arguments.md#arguments-for-interface-types We did not have enough time to do more than give an overview of this question today and hear some initial sentiment. However, the initial sentiment seemed positive for two reasons: 1. For the mutable dictionary types, the first question today specified that `Dictionary` is the concrete type used. Why would you not be able to use the constructors from that type then? 2. For the readonly dictionary types, the first question today specified that a "compliant" implementation must be used. Why couldn't we make "compliant" include a "must be able to be constructed with a comparer argument"? Again, no conclusions here today, but we've primed the discussion for next week. ================================================ FILE: meetings/2025/LDM-2025-04-14.md ================================================ # C# Language Design Meeting for April 14th, 2025 ## Agenda - [Dictionary expressions](#dictionary-expressions) - [Collection expression arguments](#collection-expression-arguments) ## Discussion ### Dictionary expressions Champion issue: https://github.com/dotnet/csharplang/issues/8887 Specification: https://github.com/dotnet/csharplang/blob/e2e62a49d32f0d659b8baf0f23ee211b69563c65/proposals/dictionary-expressions.md#open-questions #### Parsing ambiguity Should `[a ? [b] : c]` be parsed as `[a ? ([b]) : (c)]` or `[(a?[b]) : c]`? ##### Conclusion Parse as `[a ? ([b]) : (c)]`. If the user intends to use the conditional operator, they can use parentheses and `[(a?[b]) : c]`. #### Implement non-generic `IDictionary` when targeting `IReadOnlyDictionary<,>` The existing types we might use to implement `IReadOnlyDictionary` implement `IDictionary` and implementing the interface on future types is not onerous. ##### Conclusion The type used when target typing `IReadOnlyDictionary<,>` should implement IDictionary. ### Collection expression arguments Champion issue: https://github.com/dotnet/csharplang/issues/8887 Specification: https://github.com/dotnet/csharplang/blob/e2e62a49d32f0d659b8baf0f23ee211b69563c65/proposals/collection-expression-arguments.md#open-questions #### Target types where arguments are required Types that require at least one argument in all constructors and factory methods can't be used for `params` collections. We discussed whether conversions should be supported for collection expressions for these types. We also discussed whether to require the presence of `with` to allow these conversions. ##### Conclusion Yes, conversions will be supported. Yes, require `with` to allow these conversions. #### Arguments for interface types For concrete types, we support the arguments available on constructors or factory methods. But, should we support arguments for interface target types, and if so, what method signatures should be used binding the arguments. Using the available constructors may result in signatures that are not particularly helpful. For example, when including a collection in a collection expression, a spread operator seems more clear than a `with` parameter. And depending on the types involved, what's allowed may be unexpected: ```csharp List list1 = [with(otherList)]; // allowed. IList list2 = [with(otherList)]; // not allowed. IList list3 = (List)[with(otherList)]; // allowed. ``` There are a small number of constructor args that are very useful in collection expressions, such as: - Capacity and comparer for mutable (the capacity might change) - Comparer only for immutable (we know the capacity) Now that we understand the implications of a generalized solution for comparer and capacity, we are less certain that the generalized solution is the correct approach - so we will reconsider this. The team will explore what arguments are useful and return with a curated list and recommendation. We can add support for additional arguments as use cases arise. #### __arglist We see very little value in supporting `__arglist` in collection expression arguments. ##### Conclusion We will not support `__arglist` unless they are "free" to support. ================================================ FILE: meetings/2025/LDM-2025-04-16.md ================================================ # C# Language Design Meeting for April 16th, 2025 ## Agenda - [Extension-open issues](#extension-open-issues) - [Collection expression arguments](#collection-expression-arguments) ## Discussion ### Extensions-open issues Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/feb7a075739e81393def6c28ab12eb7b8fdc3bb6/proposals/extensions.md#open-issues #### Nullability attributes Nullability attributes there are some scenarios where the attributes can be placed on the receiver parameter but may feel odd or be inappropriate. For example, if an attribute refers to a method parameter name, like `NotNullIfNull`, `nameof` cannot be used. The name of the parameter has to be used. These attributes can be placed on extension methods with the traditional syntax. If the attribute includes a name, and the name does not exist on one of the extension methods in the block, it will be ignored. This is consistent with existing behavior for nullability attributes. ##### Conclusion We will copy these attributes to the lowered version of the extension method, in the same way we copy any other attribute. We will do no special work for these cases. This avoids breaking changes for people moving from the traditional syntax to the new syntax, except in the case where they used `nameof` on a parameter other than the receiver parameter. #### Should skeleton methods throw `NotSupportedException` or `null`? These methods should not be called, and are marked as special methods. However, `NotSupportedException` better explains the intent. ##### Conclusion `NotSupportedException` is preferred. #### Should we accept more than one parameter in marker method in metadata? A future version might add more parameters. ##### Conclusion This is an implementation detail the team can choose. We do not have scenarios where we would need a second parameter. #### Should the extension marker of speakable implementation methods br marked with special name flag? This is a discussion of the compiler special name. It's up to the compiler to decide what is appropriate. The implementation method also are currently marked with special name. This may not be appropriate since the implementation has switched to speakable names. ##### Conclusion The extension method will be marked with special name. The implementations method will not be marked with special name. #### How should ORPA apply to new extension methods? Overload resolution priority attribute (ORPA) is meant to differentiate overloads within the same type. For the new extension syntax, what does the notion of the containing type mean? Using the containing static class, rather than the extension block is compatible with traditional extension methods, and supports the case where extensions with the same receiver parameter appear in different extension blocks. Given: ```csharp public static class Extensions { extension(Type1) { [OverloadResolutionPriority(1)] public void Overload(...) } extension(Type2) { public void Overload(...) } // same bucket/ same "containing type" public static void Overload(this Type3, ...) } ``` If the containing type is the static `Extensions` class, all three overloads considered together. ##### Conclusion The containing type is the static class. The extension block is not considered. #### Should we apply the "inconsistent accessibility" check on the receiver parameter even for static members? ```csharp public static class Extensions { extension(PrivateType p) { // We report inconsistent accessibility error, // because we generate a `public static void M(PrivateType p)` implementation in enclosing type public void M() { } public static void M2() { } // should we also report here, even though not technically necessary? } private class PrivateType { } } ``` We don't strictly need the to give an error here, but it is not a helpful since the underlying type is inaccessible. Providing the error would be consistent with the general behavior of accessing private types within public methods. ##### Conclusion Apply the "inconsistent accessibility" check on the receiver parameter. #### Confirm whether init-only accessors should be allowed in extensions `init` accessors are intended for use during object creation and limited to that timeframe. If we do not do this now, we could do this later. We could consider it in relation to other enhancements to object creation. ##### Conclusion We will not allow `init` accessors on extension properties. #### Extension static member lookup on generic types does not work This is a discussion of [Extension static member lookup on generic types does not work](https://github.com/dotnet/roslyn/issues/78129). ```csharp static void Test() { T.Hello(); // error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter } static class Ext { extension(T) { public static void Hello() { Console.WriteLine("Hello"); } } } ``` You cannot currently call static members on type parameters in non-extension code. A constraint is available and the static method can be called on the constraint. Calling static extension members with type parameters may be more useful than non-extensions. Design work would be needed to support this. ##### Conclusion This may be a valuable scenario, but we are going to wait for feedback. ================================================ FILE: meetings/2025/LDM-2025-04-23.md ================================================ # C# Language Design Meeting for April 23rd, 2025 ## Agenda - [Extensions](#extensions) - [Extension operators](#extension-operators) - [Overload resolution priority](#overload-resolution-priority) - [Dictionary Expressions](#dictionary-expressions) ## Quote of the Day - "This is worse, that's lambda calculus" "Is [redacted] in the meeting yet? Maybe they can explain it to us" "Not in one minute they can't" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/feb7a075739e81393def6c28ab12eb7b8fdc3bb6/proposals/extensions.md #### Extension operators Proposal: https://github.com/dotnet/csharplang/blob/feb7a075739e81393def6c28ab12eb7b8fdc3bb6/meetings/working-groups/extensions/extension-operators.md We started today by looking through the latest proposal on how we want to work with extension operators. We like the proposal in general, and spent time talking about the open question: whether to put restrictions on scenarios where the operator could potentially "lose" to a built-in conversion. For example, if an extension operator `+` is on a type parameter `T`, then when `int` is substituted for `T`, that operator could potentially silently lose to a language-default operator. While this is true, we don't think we need to try and come up with a set of rules to protect users from this possibility; if it happens, it happens. Unlike other types of restricted operators, there's no potential for the "wrong" thing to happen, where a built-in operator would be overridden by an extension operator. Given this, we're happy with the proposal, and will not put restrictions on type parameter extensions. ##### Conclusion Approved. We will not restrict extensions on type parameters with more rules about what operators are valid. #### Overload resolution priority Question: https://github.com/dotnet/csharplang/blob/feb7a075739e81393def6c28ab12eb7b8fdc3bb6/proposals/extensions.md#lookup Next, we looked at a brief question on whether `OverloadResolutionPriorityAttribute` should apply to properties in extensions. This scenario can occur when a single static class contains more than one extension block, which define an overloaded set of properties. In such a scenario, authors might want to use something like ORPA to avoid ambiguity errors for their users. We think this is a good idea; there might be some discoverability issues with this solution, given that ORPA can usually only be used on indexers, but ORPA itself is generally pretty niche and not well-known anyways. It's intended as an uncommon, last-ditch tool, not a generalized solution for any lookup problem. The main question is timing: we don't think this is critical for v1 of extensions, so if it has to slip to the next version of C# to get more important bits of extensions out the door, so be it. ##### Conclusion We will allow ORPA to be used on extension properties, and the containing type of the extension property is the static class that contains the extension block. ### Dictionary Expressions Champion issue: https://github.com/dotnet/csharplang/issues/8659 Specification: https://github.com/dotnet/csharplang/blob/feb7a075739e81393def6c28ab12eb7b8fdc3bb6/proposals/dictionary-expressions.md#question-special-case-comparer-support-for-dictionaries-and-regular-collections We [previously](./LDM-2025-04-14.md#arguments-for-interface-types) talked about what collection expression arguments should do on interface types, and as part of that discussion, wanted to explore a more specialized syntax for just providing comparers. This syntax would potentially entirely replace the generalized collection expression argument feature, so the working group went off and explored the idea. After looking at the results, we don't think a specialized syntax for just comparers is the right approach. Even within the BCL, there's potential for more than just `capacity` and `comparer` arguments. `ImmutableDictionary`, for example, has both `keyComparer` and `valueComparer` parameters. We also don't want to invent 2 ways to specify parameters for dictionaries, one that needs to be used when the target type is an interface, and one for when the target type is any other concrete type. Given that, we want to proceed with the full collection expression arguments feature, even for interfaces. Our main question at this point is whether to use a curated list of constructors for the dictionary interfaces, or let the full set of constructors of the underlying type show through. For `IDictionary`, it seems like it might be ok; after all, we guarantee what the concrete type used to create one is. However, for `IReadOnlyDictionary`, we do not guarantee this, we only say that a compatible implementation must be used. Therefore, for now, we will curate the constructors to just the ones that take a comparer, capacity, or both, and no others. We can expand in the future if there are motivating scenarios, rather than being very lax now and potentially regretting our inability to change later. #### Conclusion Specialized comparer-only syntax is abandoned. We will move forward with collection expression arguments for interface types, using a curated set of constructors that take either a comparer, capacity, or both. ================================================ FILE: meetings/2025/LDM-2025-05-05.md ================================================ # C# Language Design Meeting for May 5th, 2025 ## Agenda - [Extensions](#extensions) - [XML Docs](#xml-docs) - [Participation in pattern-based constructs](#participation-in-pattern-based-constructs) - [Skeleton type metadata](#skeleton-type-metadata) - [Single-phase consequences](#single-phase-consequences) ## Quote of the Day - "If you change your source from release to release, which people typically do" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Spec: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md #### XML Docs Questions: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md#xml-docs Related: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/meetings/working-groups/extensions/Extension-API-docs.md We started today by reviewing the proposed structure of XML doc comments. This review raised a few new questions as we confirmed various components. First, should we permit overriding the `param` documentation for the receiver parameter on individual members? Our common example, `Enumerable.cs`, does have different `this` parameter documentation throughout the file. `The source of elements to be cast` vs `The source of elements to be filtered`, etc. However, we don't know that this is critical functionality, or that `Enumerable.cs` would have wanted to take advantage of this if it was being rewritten from scratch today. There is already a workaround in declaring a new extension block. This is the same workaround that we advise for other things that cannot be overridden, such as nullability annotations, attributes, `this` parameter names, etc. Therefore, we are ok with simply adding the documentation comment to the list of things that cannot be overridden on individual members, for now, and can revisit later if the demand for this proves itself. Related, we also thought about the summary on the extension blocks themselves. In particular, we're not sure that this block would actually appear anywhere in the IDE or docs experience. We want to revisit this support and make sure that we actually have a use case for it before shipping. Another related issue we briefly mentioned is missing documentation comment warnings; we want to make sure that adding parameter comments to one member won't virally create missing parameter documentation comments on all the rest of the members in an extension block; the example here would be adding parameter documentation to one member, but not having documentation on the extension parameter. The user then gets a warning that the extension parameter is missing a comment. Then, when the user adds that comment, now every _other_ member in the block that has additional parameters is missing comments on those parameters. We need to cut off that cycle somewhere to strike a usability balance, which will we think about and come back with. Finally, we raised a question of how users can `cref` an extension property itself. The current design allows referring to either the getter or setter by using the disambiguation syntax, but has no way to refer to the entire property itself. We will need to design this and come back with a proposal. ##### Conclusion XML approach is generally approved, with a few open questions around extension block summaries, warning virality, and property crefs. #### Participation in pattern-based constructs Question: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md#pattern-based-constructs Next, we took a look at what contexts we want new extensions to work in. For methods, the answer is straightforward: everywhere they work today. For properties, it's more subtle. We wanted to start by refining the "why" of our current rules, so we can apply that why to properties and have it naturally fall out. Roughly speaking, we generally allow extensions where the extension is not about state in the underlying object. For example, we allow an extension `GetAwaiter`, which returns a new object that can track the state of an underlying object and implement the `await` pattern. However, we do not then allow an extension `GetResult` on the object returned by `GetAwaiter`, because our expectation is that the Result is intrinsic to the awaiter's state. Similarly, we do not allow extension `Dispose`, because that is intrinsic to the state of an object. We acknowledge, however, that this principle is not universally applied. We're not sure that we'd exclude `Slice` methods implemented as extensions today, for example, and `GetPinnableReference` is very related to an object's state. Given this, we'll also draw from any related patterns in a given area. We are ok with the list of areas as proposed here: allowing them in object/dictionary/`with` initializers, and property patterns. For now, we think we should not include `Count`/`Length` properties in the determination of whether or not an object is countable, meaning they won't participate in list patterns or in implicit `Range`/`Index` indexers. We think this because of our prior art around `Slice`, and while we think we might want to revisit that entire space, we want to do that as a whole item, not an individual piece. We are not settled on when delegate-returning properties will be accepted. We have conflicting prior art; `LINQ` works as a syntactic rewrite, and thus delegate-returning properties are accepted without question. Other areas are not syntactic rewrites, and do not accept delegate-returning properties. More thought needs to go into the space before we can make a decision. ##### Conclusion New extensions methods will behave like old extension methods in pattern-based locations. Extension properties will participate in in object/dictionary/`with` initializers and property patterns for now, with further expansion on the table for later. #### Skeleton type metadata Question: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md#namingnumbering-scheme-for-skeleton-type Public API tools that verify our unspeakable extension metadata have an issue with how we currently decide on names. Our current approach has some issues around determinism that we need to address, but even if we adjust this, it seems very likely that the public API validation tools are going to need to adjust. We don't think that we consider the names of the skeleton structures to be part of the API; they're not referred by implementations, and purely exist for the compiler to be able to understand underlying structure. We don't think there's a naming structure that we can come up with that won't run into collisions of some kind unless we have an incrementing number somewhere, which would then break if something is reordered. Given this, we believe the tool will still need to update to understand when the name isn't important. ##### Conclusion The compiler algorithm does need to be double-checked and verified, but we expect tooling that validates public APIs from release to release will need to update as well. #### Single-phase consequences Question: https://github.com/dotnet/csharplang/blob/555aeea68ef3ce29c313ddd68d4a0fdd68d84420/proposals/extensions.md#new-generic-extension-cast-method-still-cant-work-in-linq Related: https://github.com/dotnet/roslyn/issues/78415 Finally, we looked at an outcome of moving back to single-phase generic resolution. The BCL had been hoping to fix an issue with `Cast` on `IAsyncEnumerable`, where since `TResult` cannot be inferred from the parameters, both generic type parameters must be explicitly supplied, so things like `from Type t in asyncEnumerable`, which gets translated into `asyncEnumerable.Cast()`, cannot work due to differing arity. The first thing we considered was whether LINQ could do something about this, potentially specifying both type parameters in this case. We think that would be especially difficult given the current structure of query expressions; they're defined as pure syntactic rewrites, not involving semantics. This fallback would have to be based on semantic information. We also briefly talked about two-phase inference again. We do think that this scenario is something that we want to support in the language, but as in previous LDMs, we don't think that this limitation is specific to extensions. Instead, we want to investigate a more general approach that can work regardless of extension-ness. ##### Conclusion This is not the straw that breaks the camel's back. We will continue to investigate what we can do to make this scenario better in general. ================================================ FILE: meetings/2025/LDM-2025-05-07.md ================================================ # C# Language Design Meeting for May 7th, 2025 ## Agenda - [Collection Expression Arguments](#collection-expression-arguments) - [Interface target type](#interface-target-type) - [Constructor binding behavior](#constructor-binding-behavior) - [Syntax](#syntax) ## Quote of the Day - "`with(out var x)` sounds like we're leaving `var x` behind" ## Discussion ### Collection Expression Arguments Champion issue: https://github.com/dotnet/csharplang/issues/8887 Spec: https://github.com/dotnet/csharplang/blob/0eddbeca5dc6edfbf91f915ecea3379b8e337cc7/proposals/collection-expression-arguments.md #### Interface target type Spec section: https://github.com/dotnet/csharplang/blob/0eddbeca5dc6edfbf91f915ecea3379b8e337cc7/proposals/collection-expression-arguments.md#interface-target-type We started today by looking at the proposed set of constructors for interface types to ratify the list. The main bit of feedback here was on the readonly types; the proposal does not accept capacity for any of these types, and we briefly explored whether it might be useful to do so. We don't know of any examples where this would be useful from a runtime perspective, but we did want to consider whether it would allow lower code churn when switching from a mutable interface to a readonly interface. Ultimately, we don't think that this is useful, and we approve the constructors as proposed. ##### Conclusion Interface constructor list approved as proposed. #### Constructor binding behavior Spec section: https://github.com/dotnet/csharplang/blob/0eddbeca5dc6edfbf91f915ecea3379b8e337cc7/proposals/collection-expression-arguments.md#constructors Next, we looked at the rules for binding constructors and create methods in general. A particular wart that has come up as part of this is that we may end up skipping optional parameters as part of a signature without any user-specificity; further, users may decide that they need to mark parameters as optional even if they normally wouldn't do so, so that they can mark the collection as optional in their `Create` methods. For example, a user might have a method like this: ```cs public static MyCollection Create(IComparer comparer = null, ReadOnlySpan elements = default); ``` Here, users are forced to mark `elements` as optional so that they can mark `comparer` as optional, even though they might not want to do so. Namely, this design might force API designers into non-standard approaches so that their methods compile. There are also questions around `Create` methods that take multiple `ROS` elements: which should be the one we use for the elements? While there were good reasons to try and prefer the ROS as the last element in the argument list, we do also think that moving it to be the first element would be beneficial for these questions, so we want the working group to go and take a look at this question again. We also looked at `out` parameters and `params` parameters. We're ok with the meaning of these being the same in collection expression arguments. as currently defined, `params` is mainly useful in constructors, not in `Create` methods. We will not allow `params` parameters to be split across collection expression arguments and the collection expression itself. ##### Conclusion `out` and `params` are approved. The working group will revisit the rules around collection builder signature matching, and whether we can put the ROS first. #### Syntax Finally, we got to a long-simmering question: what is the real syntax we want to use for collection expression arguments. `with` was intended as a way to move forward with the various semantics, without needing to block implementation. We have always intended to revisit it, and now we're doing so. We threw a lot of syntaxes into a block to give inspiration: ```cs // Trailing arguments [a, b, c] with (comparer) // Leading arguments new(comparer, ......) [...] with(comparer, ......) [...] init(comparer, ......) [...] args(comparer, ......) [...] // Direct parentheses - conflicts with tuples in the wild [ (a, b, c), 1, 2, 4] // new as the keyword - conflicts with target-typed new in the wild [ new(a, c, c), 1, 2, 4] // Sigils - no conflict, but is it confusing? [ @(a, b, c), 1, 2, 4] // Already used for record mutation [ with(a, b, c), 1, 2, 4] // Already used for property setters [ init(a, b, c), 1, 2, 4] // Already used for main method arguments in top-level statements [ args(a, b, c), 1, 2, 4] // Previous examples, using semi-colons instead of commas [ (a, b, c); 1, 2, 4] [ with(a, b, c); 1, 2, 4] [ init(a, b, c); 1, 2, 4] [ args(a, b, c); 1, 2, 4] ``` To make it easier to start whittling down our options, we created some broad categories. First up, we looked at the location of the arguments; before the expression, inside the expression, or after the expression? We don't like after, due to order of operations, as it implies that the collection contents have to be evaluated entirely before the arguments can be evaluated. Before is better, but we don't like the mental stutter that is involved: you start reading an argument list, then see the `[` and realize what you read previously were the arguments for a collection expression, not a method call. Inside was the clear winner in our eyes. Next, we looked using a sigil, or a keyword. We very unanimously felt that a sigil was not C#-y, and too cryptic. Therefore, we will use a keyword. Next, we looked at the separation sigil. Our two leading contenders are `,` and `;`. `:` was eliminated early because it is used as the dictionary expression `key:value` separator. Between `;` and `,`, we lean slightly towards the `,`. The `;` is a harder separation, but we also don't have any usage of `;` inside an expression today, besides statements inside block-bodied lambdas, and we're not sure that this is a good location to change that. `,` does come with less visual separation, but we're very used to using `,`s inside expressions, which is a positive. We're leaning in the `,` direction, and will come back next time to settle for certain. Finally, in the waning minutes of the meeting, we looked at the various keywords that were proposed. The first choice of most LDT members was either `with` or `args`, and `init` as a distant 3rd. We'll come back next time as well to settle between `with` and `args`. ##### Conclusion The syntax will definitely be inside the collection expression at the front, and use a keyword of some kind. We've narrowed separator options to `,` vs `;`, and keyword options to `with` vs `args`. We will settle those choices next time. ================================================ FILE: meetings/2025/LDM-2025-05-12.md ================================================ # C# Language Design Meeting for May 12th, 2025 ## Agenda - [Collection expression arguments](#collection-expression-arguments) - [Empty argument lists](#empty-argument-lists) - [Constructor binding behavior](#constructor-binding-behavior) - [Syntax](#syntax) - [Dictionary expressions](#dictionary-expressions) ## Quote of the Day - "Why would you say that?" "Look, hubris is my thing" [much later] "Hey, I didn't jinx it after all!" ## Discussion ### Collection expression arguments Champion issue: https://github.com/dotnet/csharplang/issues/8887 Spec: https://github.com/dotnet/csharplang/blob/efb435c829ca6917dbdd34ca30eab4a47ff66c22/proposals/collection-expression-arguments.md #### Empty argument lists Question: https://github.com/dotnet/csharplang/blob/efb435c829ca6917dbdd34ca30eab4a47ff66c22/proposals/collection-expression-arguments.md#empty-argument-lists First up, we looked at when to permit empty argument lists. We want a simple rule here, something easy to explain to users. One option is just "allowed for everything", which is certainly easy to explain. However, it may run into friction in the spec and compiler; what does it actually mean, particularly for arrays and spans? Neither of those have a traditional "constructor" that can accept arguments, so what does the `with()` actually bind to? Another simple option is to permit it when there exists a constructor or `Create` method that can accept the empty argument list. For such a rule, the main question is then whether interfaces can accept an empty argument list. We think this might be slightly more common than for arrays or spans; it is reasonable for an API author to move from a specific type to an interface to abstract things in a breaking update or internally. It would mean that we would need to amend the list of accepted constructors for interface types to include the empty constructor as well, but that seems reasonable. A final option would be to simply disallow an empty `with()` entirely, but that feels too heavy-handed; being explicitly about not passing arguments is a perfectly reasonable thing to do. Therefore, we will allow `with()` for constructor types and builder types that can be called without arguments at all, and we will add empty constructor signatures for the interface types. ##### Conclusion We will allow `with()` for constructor types and builder types that can be called without arguments at all, and we will add empty constructor signatures for the interface types. Arrays and spans will not allow `with()`, as there are no signatures that would fit them. #### Constructor binding behavior Question: https://github.com/dotnet/csharplang/blob/efb435c829ca6917dbdd34ca30eab4a47ff66c22/proposals/collection-expression-arguments.md#collectionbuilderattribute-methods Following up from [last week](./LDM-2025-05-07.md#constructor-binding-behavior), we're revisiting the rules for create method binding. We have a new proposal that regularizes the approach by creating projection signatures: chopping off the last parameter of the builder method, then doing standard overload resolution rules, creating a signature that is similar in approach to a constructor signature for types that use constructor-based creation. This does nicely regularize the approach, and solves the questions around which ROS to use if multiple are in a signature, but does still present the question of what do users do to use optional parameters? After some discussion, we've hit on the mental model that users are likely to not know exactly what they're calling: a `Create` method, a constructor, a synthetic constructor for an interface, they all appear the same from a mental model perspective. The projection also fits in with that model, and that's what we intend to proceed with. ##### Conclusion Projection-based approach is approved. #### Syntax Also [last week](./LDM-2025-05-07.md#syntax), we had 2 syntax choices to finish on: whether to use `;` or `,` as the separator for collection arguments, and whether to use `with` or `args` as the keyword. We did very little discussion today, and instead went straight into a read of the room. This revealed that, while there is still some support for both `;` and `init`, both are in the minority. We will approve `with(),` as the syntax for collection expression arguments. ##### Conclusion `with` is the keyword chosen for collection expression arguments, and `,` is the separator. ### Dictionary expressions Champion issue: https://github.com/dotnet/csharplang/issues/8659 Spec: https://github.com/dotnet/csharplang/blob/efb435c829ca6917dbdd34ca30eab4a47ff66c22/proposals/dictionary-expressions.md#support-keyvaluepair-variance-with-params Finally today, we took a look at a question in dictionary expressions: should we support KVP variance in `params`. This pulls in two directions: as a bare parameter, KVP has no variance. However, if the user were to surround the provided `params` argument with a collection expression, it would start to work. We've further said in the past that we want to try and minimize the differences between what collection expressions can do, and what `params` can do. That being said, there are also good arguments for disallowing this. The general idea behind `params` is that the user doesn't know that they're calling something that accepts a collection, and they get the same experience they'd get if they called a method that just had a signature with however many parameters they provided. There are also other aspects of collections expressions and `params` that differ, such as extension `GetEnumerator`. We also aren't adding generalized KVP variance; it's specifically a feature of collection expressions. We therefore think we should keep it limited to just collection expressions. #### Conclusion Variance is limited to explicit collection expressions, `params` does not perform it. ================================================ FILE: meetings/2025/LDM-2025-05-28.md ================================================ # C# Language Design Meeting for May 28th, 2025 ## Agenda - [Nominal type unions](#nominal-type-unions) ## Quote of the Day - "The general rule of LDM is what happens in chat stays in chat" unless it serves as fodder for the quote of the day ## Discussion ### Nominal Type Unions Champion issue: https://github.com/dotnet/csharplang/issues/9411 Proposal: https://github.com/dotnet/csharplang/blob/402e0a75e53b6c1a301b1e75b58115b7b7315e8f/proposals/nominal-type-unions.md Today we reviewed the latest output from the unions working group: a proposal for nominal type unions. We spent most of our time examining the proposal and discussing its various limitations in certain scenarios. As with previous proposals, generic contexts remain a concern, as do reflection scenarios. JSON serialization presents particular challenges, where the natural representation for some scenarios would be a union type (such as `OneOf`), but this would fail when encountering specific cases instead of the union wrapper. We also raised concerns about the fragility of the emit approach, especially across assembly boundaries; optimizations would need to be curtailed to prevent undefined behavior during assembly upgrades. Ultimately, the LDM is not ready to move forward with this version of the proposal, and we need to return to the drawing board to continue designing. ================================================ FILE: meetings/2025/LDM-2025-06-04.md ================================================ # C# Language Design Meeting for June 4th, 2025 ## Agenda - [Extensions](#extensions) - [Should extension operators on nullable of extended type be disallowed](#should-extension-operators-on-nullable-of-extended-type-be-disallowed) - [Applicability of bitwise operators during evaluation of user-defined conditional logical operators](#applicability-of-bitwise-operators-during-evaluation-of-user-defined-conditional-logical-operators) - [Extension user-defined conditional logical operators](#extension-user-defined-conditional-logical-operators) - [Extension compound assignment operators](#extension-compound-assignment-operators) - [Delegate returning properties](#delegate-returning-properties) - [Extension declaration validation](#extension-declaration-validation) - [Cref references](#cref-references) ## Quote of the Day - "2b or not 2b?" "That was the question" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specifications: * https://github.com/dotnet/csharplang/blob/9ecaa5ad2cf7700cb837fac84dc89eb7a1c655de/proposals/extension-operators.md * https://github.com/dotnet/csharplang/blob/9ecaa5ad2cf7700cb837fac84dc89eb7a1c655de/proposals/extensions.md We spent today reviewing open questions in the extension space, starting first with extension operators specifically, then moving on to more general questions. #### Should extension operators on nullable of extended type be disallowed Question: https://github.com/dotnet/csharplang/blob/9ecaa5ad2cf7700cb837fac84dc89eb7a1c655de/proposals/extension-operators.md#should-extension-operators-on-nullable-of-extended-type-be-disallowed We started by thinking about whether we should allow nullable value type operators to be defined as extensions in an extension block for their underlying type. There are a few technical challenges in the specification and compiler to allowing this, and a clear workaround of simply defining the operators in an extension block on the actual nullable value type. Given that, we are fine with the proposed restriction for now, and can revisit at a later time if we get enough feedback on the restriction. ##### Conclusion Restriction accepted: extension operators can only be declared for the type being extended in an extension block, not for the nullable underlying type. #### Applicability of bitwise operators during evaluation of user-defined conditional logical operators Question: https://github.com/dotnet/csharplang/blob/9ecaa5ad2cf7700cb837fac84dc89eb7a1c655de/proposals/extension-operators.md#applicability-of-bitwise-operators-during-evaluation-of-user-defined-conditional-logical-operators An original principle of this rule is that `&` and `&&` should call the same `&` operator, which this proposal would violate. While there is some precedent for us allowing operators to change between similar operations (the new `+=` overloads vs `+`, for example), we don't think we have the use cases to justify this change at this point. ##### Conclusion We're not doing anything here for now. Rejected until we see use cases. #### Extension user-defined conditional logical operators Question: https://github.com/dotnet/csharplang/blob/9ecaa5ad2cf7700cb837fac84dc89eb7a1c655de/proposals/extension-operators.md#extension-user-defined-conditional-logical-operators We don't have any additional comments on this proposal. It seems logical and straightforward, and is accepted. ##### Conclusion Proposal accepted. #### Extension compound assignment operators Question: https://github.com/dotnet/csharplang/blob/9ecaa5ad2cf7700cb837fac84dc89eb7a1c655de/proposals/extension-operators.md#extension-compound-assignment-operators There is definitely value in allowing anything to work here, despite the potential for footguns, but we think there will be more design work needed that we do not currently have the bandwidth to do. Therefore, at least for now, the restriction is adopted. ##### Conclusion Restriction adopted, we can look at it again in the future when there is more bandwidth. #### Delegate returning properties Question: https://github.com/dotnet/csharplang/blob/9ecaa5ad2cf7700cb837fac84dc89eb7a1c655de/proposals/extensions.md#delegate-returning-properties First up in extensions proper, we took a quick look at the proposed rules for delegate-returning properties. We have no issues with the proposed rules, and adopt them as written. ##### Conclusion Rules adopted as written. #### Extension declaration validation https://github.com/dotnet/csharplang/blob/9ecaa5ad2cf7700cb837fac84dc89eb7a1c655de/proposals/extensions.md#extension-declaration-validation Next up, we looked at one of the more controversial restrictions of the current proposal, that only type parameters that are directly used in the extension parameter are allowed on the extension block itself. This ends up being a blocker for several scenarios in both the BCL and our early adopters. Given this, we want to lift the restriction, but the question then becomes "to what extent". The restriction does provide some useful guardrails, particularly for non-methods members that do not have a location to provide type parameters. We don't think there's any harm in fully lifting the restrictions on actual methods, but for non-methods, we think we need to keep a form of the restriction: the type parameters must be used within all parameters of the member. ##### Conclusion Restriction is loosened to "No restriction on extension methods. Other extension members must use all type parameters in their parameter types." #### Cref references Question: https://github.com/dotnet/csharplang/blob/9ecaa5ad2cf7700cb837fac84dc89eb7a1c655de/proposals/extensions.md#cref-references Finally today, we looked at the proposed cref syntax for referencing extensions. There's a wrinkle in this of which member a cref actually references: the implementation, or the skeleton facade. Both will have different IDs in the actual documentation file. However, this is a question in itself: should C# normalize this so users don't need to understand the difference between the two? There's a good argument for doing this, but we then need to decide which thing to normalize to. Another question that came up is whether there should be an entirely bespoke syntax here, or if users can just refer to things in extension or disambiguation form directly, ie as `` or ``. This form would encourage thinking of the implementation vs skeleton separately, which could be a problem, but is also more straightforward without needing to introduce new formats. A further item to consider is whether we want to allow linking directly to an extension block in a doc comment. We did not come to a decision here today, and will need to come back next week on it. ================================================ FILE: meetings/2025/LDM-2025-06-09.md ================================================ # C# Language Design Meeting for June 9th, 2025 ## Agenda - [Extensions](#extensions) ## Quote of the Day - "It really feels like a Monday morning" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/435111275ae5f8f949cbfa9c918645738ccf38a0/proposals/extensions.md#cref Today, we followed up on the cref questions from [last time](LDM-2025-06-04.md#cref-references), after the working group took another look at the area. An unfortunate detail here is that the skeleton implementation keeps surfacing: we try to paper over the implementation details for users, but in areas where it bleeds through, particular groups will likely need to understand the difference between the actual implementation of the extension member and the skeleton that exists to inform the language about the structure. This is true for anything that wants to interact with real structures, including both documentation comments and other things like reflection. After extensive discussion, we don't think there's a way around this in the end, particularly given our desire to keep `ExtensionType.Method` references working for existing `cref`s to extension methods in the new form. Another argument in favor is that doc comment XML does not reflect the structure of C#; it reflects the structure of metadata. Any tooling interacting with doc comment XML today already needs to know about the structure of metadata and how to map it back onto C# structure for output, and this would be no different. Therefore, we approve the following decisions today: * There will be one syntax to refer to extension skeleton members: `ExtensionType.extension(type).Member`. We will not offer "reduced" forms such as `type.Member` in `cref`s in the language for simplicity. * There will be one syntax to refer to extension implementation members: `ExtensionType.Member`. This will work for methods and things that reduce into methods, such as `E.extension(int).Prop` and `E.get_Prop`/`E.set_Prop`. * Extension blocks themselves are not able to be referred to at this time. `E.extension(int)`, on its own, is not a valid `cref` location. We have no examples of where we'd actually want to do this and currently think that it would not be a good idea to expose this concept, as extension blocks are not named entities in and of themselves in C#. This last point ended up being somewhat contentious, as we intend to allow `param` elements to be documented on the extension block itself. Do users expect to be able to write other documentation comments in that same area and have them reflected somewhere? Where would users assume such comments live? On the block itself, or on the members inside the block? We think conceptually we want to reinforce the idea that extension parameters and type parameters are copied to the members themselves, not to the block, and the same will extend to documentation on them. #### Conclusion The proposed `ExtensionType.extension(Type).Member` syntax is approved. Implementation members will be referenceable using `ExtensionType.Member`. Extension blocks are not referenceable entities. ================================================ FILE: meetings/2025/LDM-2025-06-11.md ================================================ # C# Language Design Meeting for June 11th, 2025 ## Agenda - [Extensions](#extensions) - [Dynamic resolution of operator `true`/`false`](#dynamic-resolution-of-operator-truefalse) - [Extension operators in LINQ expression trees](#extension-operators-in-linq-expression-trees) - [Built-in operator protection rules](#built-in-operator-protection-rules) - [Extension module initializers](#extension-module-initializers) - [Extension methods as entry points](#extension-methods-as-entry-points) - [Langversion behavior for new extensions](#langversion-behavior-for-new-extensions) - [`nameof`](#nameof) ## Quote of the Day Nothing particularly amusing was said today, sorry. ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specifications: * https://github.com/dotnet/csharplang/blob/fdc35c9c2c2ff4fd532c9733a9ade5f89d37018e/proposals/extension-operators.md * https://github.com/dotnet/csharplang/blob/fdc35c9c2c2ff4fd532c9733a9ade5f89d37018e/proposals/extensions.md #### Dynamic resolution of operator `true`/`false` Question: https://github.com/dotnet/csharplang/blob/fdc35c9c2c2ff4fd532c9733a9ade5f89d37018e/proposals/extension-operators.md#dynamic-evaluation We think the proposed restriction makes sense. The C# spec says that when one or both operands of a binary operator is dynamic, the entire operation, including lookup of operator `true`/`false`, occurs at runtime. The compiler performing the lookup at compile-time for operator `true`/`false` for the non-dynamic operand is an optimization, nothing more, and the dynamic binder would not be able to find extension `true`/`false` operators, just as it can't find instance extension methods today. ##### Conclusion Restriction accepted. Extension operator `true`/`false` are not used for `dynamic` `&&` or `||`. #### Extension operators in LINQ expression trees Question: https://github.com/dotnet/csharplang/blob/fdc35c9c2c2ff4fd532c9733a9ade5f89d37018e/proposals/extension-operators.md#use-of-extension-operators-in-linq-expression-trees The proposal was accepted without further discussion beyond what is in the question itself. ##### Conclusion Accepted. #### Built-in operator protection rules Specification: https://github.com/dotnet/csharplang/blob/fdc35c9c2c2ff4fd532c9733a9ade5f89d37018e/proposals/extension-operators.md#is-the-rule-an-extension-operator-may-not-have-the-same-signature-as-a-predefined-operator-worth-having-as-specified Further testing of our proposed rules for protecting built-in operators has revealed holes in our proposal, namely when conversions to built-in types are involved, bypassing the existing rule. While we could create a more complicated rule to address this, involving overload resolution, we don't like the end result: it's a complicated rule that we don't feel carries sufficient weight to justify the complexity. We could reduce this to a warning instead, and conceptually don't have an issue with it, but we don't think that such a warning must be present; we think that when a predefined operator is chosen over an extension operator, it will be fairly obvious what is happening. ##### Conclusion Rule is abandoned. We will not have built-in errors or warnings here. #### Extension module initializers Question: https://github.com/dotnet/csharplang/blob/fdc35c9c2c2ff4fd532c9733a9ade5f89d37018e/proposals/extensions.md#open-issues The question here is whether to permit extension implementation members to be module initializers. We don't have any motivating scenarios here and don't have a proposal for how the attribute would be applied to either skeleton or implementation members. Therefore, we will reject this until we have motivation to implement it. ##### Conclusion Rejected. #### Extension methods as entry points Question: https://github.com/dotnet/csharplang/blob/fdc35c9c2c2ff4fd532c9733a9ade5f89d37018e/proposals/extensions.md#open-issues We must keep the implementation methods for instance extension methods as entry point candidates, as it works today and we have a strong back-compat goal here. Therefore, we intend to continue permitting methods that end up having an implementation method with a valid entry point signature to be considered in the set of possible entry points. ##### Conclusion Implementation methods that have a valid signature will be considered among the possible entry point candidates. #### Langversion behavior for new extensions Question: https://github.com/dotnet/csharplang/blob/fdc35c9c2c2ff4fd532c9733a9ade5f89d37018e/proposals/extensions.md#open-issues After some consideration, we don't think we need to have lookup consider language version for the actual lookup process itself. The scenarios in which a user could run into issues are: * A delegate-returning property gets ahead of an old-style extension method, so `obj.M()` becomes an invocation of the delegate returned from the property `M`. * A method group used as an assignment/argument gets superseded by an extension property, so `M(e.MethodGroup)` goes from a method to a property. In either of these cases, we will issue a LangVersion diagnostic but won't otherwise complicate lookup with exceptions around excluding new-style extensions. The main concern is that we can't issue a diagnostic for instance extension methods, since you can already have them today and going from old to new style is not otherwise a breaking change. ##### Conclusion We will issue LangVersion diagnostics on successful lookups/overload resolutions that pick a non-instance extension method. #### `nameof` Question: https://github.com/dotnet/csharplang/blob/fdc35c9c2c2ff4fd532c9733a9ade5f89d37018e/proposals/extensions.md#nameof Finally today, we took a look at `nameof` rules. There's a bit of tension here: we want to allow referencing an extension property in a `nameof`, but we want to be consistent with current rules around extension-based lookup in `nameof`. Today, extension methods cannot be referenced in `nameof` off their extended type or an instance of their extended type. They must be looked up on the extension container. This restriction was mostly about cutting design time to get extension methods shipped, and we're mixed on whether to keep it long term. We don't think that this is the entire solution though, as ambiguity once again raises its head. We came up with 3 main approaches to the lookup: 1. `ExtensionContainer.extension(param).Member` - Essentially borrow the cref syntax. We think this is too heavy-handed for just `nameof` and want to avoid it. 2. `ExtendedType.Member` - Allow this form and likely lift the restriction on all extension members. Do nothing to provide disambiguation syntax. 3. `ExtensionContainer.Member` - Simply make this lookup work, even for extension properties and events. We think we want option 3; it's the most consistent with the current approach and ensures that there isn't multiple ways to reference the member. At the same time, though, we know that we likely will not have time to design and implement this lookup for C# 14; after design, we may even decide that we don't like how the rules turn out and need to go back to the drawing board. Given this, we've decided that we will likely not support `nameof` on extension properties for C# 14. We will revisit and take up the design pen again as soon as we can here, but do not want to jeopardize shipping the feature over `nameof` support. ================================================ FILE: meetings/2025/LDM-2025-06-18.md ================================================ # C# Language Design Meeting for June 18th, 2025 ## Agenda - [Iterators in lambdas](#iterators-in-lambdas) - [Extensions](#extensions) ## Quote of the Day - "A manager isn't on the hook" "Oh they are, they're just on the hook for making sure someone else is on the hook" ## Discussion ### Iterators in lambdas Champion issue: https://github.com/dotnet/csharplang/issues/9467 Specification: https://github.com/dotnet/csharplang/blob/ca5b8eb445846c2f5aa394a48cd0992bef76c277/proposals/iterators-in-lambdas.md We started today with a look at a proposal, allowing lambdas to be iterators. We are overall in support of this proposal, though several open questions will need further looks. The main questions we have are: * How close should we align with Visual Basic's behavior? * What should the default return type of an iterator be? `IEnumerable`, or `IEnumerator`? * We do think that once we decide on that, sync vs async should be easier: lambdas marked `async` should be `IAsyncEnumera(ble|tor)`, and non-`async` lambdas would be `IEnumera(ble|tor)`. We will accept this proposal into the working set for future work. #### Conclusion Proposal accepted, moved to the working set. ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specification: https://github.com/dotnet/csharplang/blob/ca5b8eb445846c2f5aa394a48cd0992bef76c277/proposals/extensions.md Reviewed Document: https://github.com/dotnet/csharplang/blob/ca5b8eb445846c2f5aa394a48cd0992bef76c277/meetings/working-groups/extensions/content-based-naming.md We spent most of the meeting reviewing the new proposal for how extension metadata is preserved in naming. One of the key issues we want to address is stability; while these are mostly implementation details to end users, there are various implementation details in the middle that need to care. `cref`s, for example, or public API tracking across library versions. These intermediates need to understand the metadata layout in order to provide good tracking, and by adjusting our proposed mechanisms, we can better serve these applications while still providing the same end user experience. One thing we've realized, as we delved into the details here, is that we don't know how to entirely have our cake and eat it too. The runtime does not allow us to overload on constraints, so that means that constraints must be included in the generated names that we end up using. But that also means that removing a constraint becomes a breaking change for these intermediary tools, which is not something that has been true in C# before. We think we're ok with this; for things like the API tracking tools, this is something they can adapt to, and is easier to adapt to than the larger changes that would have happened in the previous versions of the tooling. For crefs, it's unfortunate, but we don't think that crefs are a large enough issue to need further design here. We also considered whether to adopt a version of the encoding that would allow methods to avoid the breaks; properties and events can't avoid it, but methods could. We don't think the tradeoff in complexity is worth it, so we will stick with the initial proposal. We also considered hashing algorithms for a bit. We're ok with the proposal, particularly the reason for avoiding hashes used in cryptographic scenarios. #### Conclusion New approach is accepted. ================================================ FILE: meetings/2025/LDM-2025-06-23.md ================================================ # C# Language Design Meeting for June 23rd, 2025 ## Agenda - [Extensions](#extensions) - [Use of extension operators in LINQ expression trees](#use-of-extension-operators-in-linq-expression-trees) - [`ref` encoding and conflict checks](#ref-encoding-and-conflict-checks) - [Extern support](#extern-support) - [Lookup](#lookup) ## Quote of the Day - "So we pick the method" "But the spec says that should have been an ambiguity" - "That can't be it, we have 20-odd minutes left in the meeting. That's illegal!" ## Discussion ### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 Specifications: * https://github.com/dotnet/csharplang/blob/d9aac41d8b0ff4d2bb322ffb8f85a4a4a14690df/proposals/extensions.md * https://github.com/dotnet/csharplang/blob/d9aac41d8b0ff4d2bb322ffb8f85a4a4a14690df/proposals/extension-operators.md #### Use of extension operators in LINQ expression trees Question: https://github.com/dotnet/csharplang/blob/main/proposals/extension-operators.md#use-of-extension-operators-in-linq-expression-trees Previously, we'd decided to use the existing `Expression` factory methods for expression trees. This works in most cases, but falls over in scenarios that need multiple extension operators, such as `&&` or `||`. For these scenarios, the ideal fix would be to add a new factory method, but we don't think this is something that we can do at this time. Therefore, we'll simply note the issue in documentation, and if we make future changes to expression trees, we could potentially address this shortcoming then. ##### Conclusion Proposed rules approved. `&&` and `||` implemented via extensions will not be allowed in expression trees. #### `ref` encoding and conflict checks Question: https://github.com/dotnet/csharplang/blob/d9aac41d8b0ff4d2bb322ffb8f85a4a4a14690df/proposals/extensions.md#metadata > Confirm that `ref` should not be included in extension type name After some discussion here, we realized that the question has some fundamental issues that need to go back to the working group. https://github.com/dotnet/roslyn/issues/79043 demonstrates an issue with our current conflict checking rules; because we do not consider `ref`ness when doing signature conflict checking, we prevent this scenario from migrating to new extensions. We also don't think this is particularly edge case code; it's certainly not mainstream, but it's not a truly niche thing. The proposed rules for `ref` name encoding would be at odds with allowing this code to be valid C#; while that particular scenario would be okay because the constraints would force the creation of two different skeleton containers, the constraints are not the important bit that allows the code to be valid. What allows the code to be valid is the `ref`ness, and it would be odd to have a form that is allowed in C# but we can't actually emit in many cases. Given this, the working group will take another look at the conflict rules and bring back a new proposal for how to encode these bits. #### Extern support Question: https://github.com/dotnet/csharplang/blob/d9aac41d8b0ff4d2bb322ffb8f85a4a4a14690df/proposals/extensions.md#extern We confirmed support here. Existing extensions support `extern`, so the new ones will too. #### Lookup Question: https://github.com/dotnet/csharplang/blob/d9aac41d8b0ff4d2bb322ffb8f85a4a4a14690df/proposals/extensions.md#lookup ##### Question 1 > Confirm that we want betterness rules to apply even when the receiver is a type Next, we looked at the proposed static member betterness rules. The existing rules are based on the model that there is an implicit parameter here, the type itself, and that therefore everything that goes into standard parameter checking should go into betterness for static invocations. We don't feel that this is a convincing argument, as there is no value to applying refness to here. Further, it would mean that users would need to understand that it would be a source-breaking change to move an extension from an `extension(in int)` block to an `extension(int)` block, because it could affect the tiebreaking with another library's extensions. We therefore conclude that static extension lookup will only look at the type being extended when doing resolution, not any other tiebreaking criteria. ###### Conclusion For static extension members, we will only use the type for the extra "parameter". ##### Question 2 > Confirm that we don't want some betterness across all members before we determine the winning member kind We're concerned here that if we don't support some form of betterness, it will block users from being able to move things that are currently written as extension methods but would be better written as extension properties. These APIs are expressed as well as they can be today, given that we don't have extension properties today. We feel strongly that these APIs should be able to work, but we are also uncertain as to whether we can reasonably deliver this experience before C# 14 releases. The status quo does protect our ability to design rules that by and large do the "right" thing by default, invoking methods where appropriate and calling properties when appropriate. We'll therefore proceed with this, and work on a design for making this "just work". ###### Conclusion Ambiguity rules will be left in place while we work on creating rules for making code work as the user would expect. ================================================ FILE: meetings/2025/LDM-2025-06-25.md ================================================ # C# Language Design Meeting for June 25th, 2025 ## Agenda - [Unions](#unions) - [Stand-alone issues](#stand-alone-issues) - [Mutually exclusive issues](#mutually-exclusive-issues) ## Quote of the Day - "Runtime typo unions" ## Discussion ### Unions Champion issue(s): * Closed enums: https://github.com/dotnet/csharplang/issues/9011 * Nominal type unions: https://github.com/dotnet/csharplang/issues/9411 Documents: * https://github.com/dotnet/csharplang/blob/0809d6632e3f8d15d3ead205bfc83bd35816aa8f/meetings/working-groups/discriminated-unions/Trade%20Off%20Matrix.md Today, we took a dive into unions. The unions working group has come back with a set of discrete proposals to try and help narrow the potential options for where we should invest in unions in C#. There are 5 discrete proposals here, 3 of which stand on their own, and then 2 of which are likely mutually exclusive. We did not dig into syntax or precise semantics for any of these today. #### Stand-alone issues * https://github.com/dotnet/csharplang/blob/0809d6632e3f8d15d3ead205bfc83bd35816aa8f/meetings/working-groups/discriminated-unions/Closed%20Enums.md * https://github.com/dotnet/csharplang/blob/0809d6632e3f8d15d3ead205bfc83bd35816aa8f/meetings/working-groups/discriminated-unions/Closed%20Hierarchies.md * https://github.com/dotnet/csharplang/blob/0809d6632e3f8d15d3ead205bfc83bd35816aa8f/meetings/working-groups/discriminated-unions/Case%20Classes.md These are closed enums, closed hierarchies, and case classes. We feel that these are fairly orthogonal to the type union proposals below, and make sense on their own. Closed enums and hierarchies are mainly about getting users the ability to be exhaustive in such a way that they will get explicit source-breaking changes when new cases are added, and case classes are then a syntax sugar to allow declaring such type hierarchies in a more ergonomic fashion. We strongly believe that these should be a priority for the LDM in the near term. #### Mutually exclusive issues * https://github.com/dotnet/csharplang/blob/0809d6632e3f8d15d3ead205bfc83bd35816aa8f/meetings/working-groups/discriminated-unions/Runtime%20Type%20Unions.md * https://github.com/dotnet/csharplang/blob/0809d6632e3f8d15d3ead205bfc83bd35816aa8f/meetings/working-groups/discriminated-unions/Nominal%20Type%20Unions.md The other two proposals are likely mutually exclusive. We're not certain that C# has room for two different sets of semantics here. There is an elegance in the nominal approach in the way that it does not try to pretend about the cases, and that there is no `is-a` relationship that would either leak through or need runtime support for. The timelines for it are also shorter, as runtime support would take some non-trivial amount of time. Given these, we lean towards nominal type unions here. #### Conclusion We will continue working on 4/5 of these proposals: closed enums, closed hierarchies, case classes, and nominal union types. ================================================ FILE: meetings/2025/LDM-2025-06-30.md ================================================ # C# Language Design Meeting for June 30th, 2025 ## Agenda - [Type parameter inference from constraints](#type-parameter-inference-from-constraints) - [Target-typed static member lookup](#target-typed-static-member-lookup) ## Quote(s) of the Day - "You need to get better at ventriloquism" - "When you said messy field, were you referring to the `field` keyword?" "I was seeing if anyone would stoop that low" ## Discussion ### Type parameter inference from constraints Champion issue: https://github.com/dotnet/csharplang/issues/9453 Specification: https://github.com/dotnet/csharplang/blob/746d354ce523385ab4f36a94792d4acd64f3b531/proposals/type-parameter-inference-from-constraints.md We started today by looking at a proposed breaking change for C#: allowing the type inference phase to use constraints when doing type inference. This is a longstanding community request that has been previously rejected due to the risk of breaking changes. However, we feel that in the time since it was rejected, other breaking changes in similar areas, most closely the lambda/method group natural type change in C# 10, have given us a blueprint for how to proceed here. In particular, long preview periods will be essential in collecting comprehensive examples of where the breaks will end up happening. In particular, this is an area where we don't have great tools to find potential breakage today, as this is not something that is easily searchable via regex. That being said, we are unanimously in support of moving forward with this change, and look forward to seeing it in a future version of C#. Future meetings will need to review the exact rules being proposed, as we did not get into the nitty gritty of type inference dependency changes today. #### Conclusion Approved in principle, into the working set. ### Target-typed static member lookup Champion issue: https://github.com/dotnet/csharplang/issues/9138 Specification: https://github.com/dotnet/csharplang/blob/746d354ce523385ab4f36a94792d4acd64f3b531/proposals/target-typed-static-member-lookup.md Next and last, we looked at another new proposal: target-typed static member lookup. This one is more controversial, both in the community and in the LDM itself. There's definite tension around a few points: * How far do we want to take this? There are various cut points we could consider, such as enum members, properties, methods, etc. The more permissive we are, the greater the chance for this to be used to make unreadable code. On the other hand, there are legitimate use cases that we would also like to support. For example, union types may want to be able to infer methods from their containers for creation. We think we need more examples here; both good and bad. This is a gut feeling area, and while we can look to some adjacent ecosystems for inspiration, such as Swift and Dart, ultimately we need to make the decision about what is best in C#. * What type of sigil should we use, or should we not use a sigil at all? Again, contemporaries here seem to use `.` as the sigil, and there are some definite benefits to using a discrete sigil. It would ensure that we can't run into `Color Color` problems where we would have to decide whether `Color` is target-typed member access, or a reference to the type named `Color`, or a reference to the member in the current type named `Color`. This area is already complex for both humans and compilers, and making it more so is concerning. One analogy we liked during the meeting was an analogy to `new()`: when you remove the type from expression, `new()` is what remains. Similarly for this feature, when you remove the type from the expression, `.Member` is what remains. We also considered the `_.` syntax as an additional option, but it got no real support after initial suggestion. After some lively debate on this subject, we are moving forward on this topic, with most of the LDM preferring an explicit sigil in `.`, a small contingent preferring to not take the feature, and a general need to see more examples and think through the impact they will have further. #### Conclusion Topic is moved to the working set, will continue to be reviewed further. ================================================ FILE: meetings/2025/LDM-2025-07-30.md ================================================ # C# Language Design Meeting for July 30th, 2025 ## Agenda - ['/main' and top-level statements](#main-and-top-level-statements) - [Union proposals overview](#union-proposals-overview) - [Add type parameter restriction to closed hierarchies](#add-type-parameter-restriction-to-closed-hierarchies) - [Standard unions](#standard-unions) - [Union interfaces](#union-interfaces) - [Custom unions](#custom-unions) - [Non-boxing access pattern](#non-boxing-access-pattern) ## Quote(s) of the Day - (Attendee lifting their cat) "is having their Lion King moment!" ## Discussion ### '/main' and top-level statements Pull request: https://github.com/dotnet/csharplang/pull/9546 Should we allow specification of an entry point with `/main` even when there are top-level statements? This would help some scenarios using the new single-file `app.cs` capability. The core concern is that the top-level program cannot then be called programmatically from other entry points, because it is generated with an unspeakable name. Arguably, this should be paired with a proposal to make top-level programs callable. #### Conclusion We're ok adopting the proposal now, so that scenarios can start lighting up, and then likely embracing a design for callable top-level programs in the future. ### Union proposals overview As a PSA, the [Union proposals overview](https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/discriminated-unions/union-proposals-overview.md) is a useful document to follow the many union-related proposals currently being investigated and adopted. ### Add type parameter restriction to closed hierarchies Pull request: https://github.com/dotnet/csharplang/pull/9560 This attempts to plug a hole in the current [Closed hierarchies](https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md) proposal where "fresh" type parameters on a class deriving from a closed class can hamper exhaustiveness in switches: ```csharp closed class C { ... } class D : C { ... } class E : C { ... } C c = ...; _ = c switch { D d => ..., E e => ..., // Can't put anything as '???' to exhaust all instantiations of `E` } ``` While there only exists one instantiation of `D<...>` that is implicitly convertible to `C` (namely `D`), there's an infinite number of instantiations of `E<...>` (anything where the first type argument is `int`) that are. So even though `C` is closed, it is not possible to write an exhaustive switch without a fallback clause, undermining the point of declaring `C` closed in the first place. We would like to catch this problem at the declaration of `E`. The proposal suggests doing this by requiring all type parameters of a class deriving from a closed class to be used in the base class specification. E.g. in `class D : C` all type parameters (`T`) are being used in `C`, whereas in `E : C` only `T` is used in `C`, not `U`. We think the proposed rule goes a long way to plug the hole, but are not entirely convinced it is enough. We discussed adding stronger rules, e.g. requiring type parameters to be used *as*, not just *in*, type arguments to the base class, or preventing derived classes from being explicitly generic altogether (they would have to be nested). We also touched on generic variance. Class type parameters cannot be variant today, but in the future we might extend the closed hierarchies feature to interfaces, or even allow classes to express variance. If we were to do either we would need to reevaluate whether we have the right rules in place here: With variance, more than one generic instantiation of a derived type could still be implicitly convertible to the base type; however there might be one that is the base type of all the other instantiations and could be used in exhaustive switches. #### Conclusion We adopted the proposal as an improvement; however, we need to keep thinking about this; in particular, a constructive description of how to get from a generic instantiation of the closed class to at most one of each derived class would be useful. We think the proposed tighter restrictions are too draconian and affect real scenarios. As for variance, we can cross that bridge when we get there; as a fallback we can always forbid variant type parameters in a closed type. ### Standard unions Specification: https://github.com/dotnet/csharplang/blob/5736dbf3d4008498acb120669a4ede96dd69e104/meetings/working-groups/discriminated-unions/standard-unions.md The proposal is to offer standard generic `Union<...>` types in the BCL, similar to e.g. `Action` and `Func` types: ```csharp public union Union(T1, T2); public union Union(T1, T2, T3); ... // etc ``` Unlike e.g. `ValueTuple<...>` the current proposal does not bother with a "nesting" scheme, but just caps the number of case types offered in this way. The benefit is similar to e.g. `Func`: Smaller scenarios, where a named abstraction isn't really helpful, can just grab a standard union type "off the shelf". There is a small concern that it won't be intuitive that these don't unify across different orders of the case types; e.g., `Union` vs `Union`. However, since the types are not special language syntax but just library types, hopefully it will be unsurprising that they act as such. #### Conclusion Adopted as proposed, with the understanding that the .NET library team has final say in the shape of these types. ### Union interfaces Specification: https://github.com/dotnet/csharplang/blob/5736dbf3d4008498acb120669a4ede96dd69e104/meetings/working-groups/discriminated-unions/union-interfaces.md #### Non-generic 'IUnion' interface The proposal is to add an `IUnion` interface that simply enshrines the `Value` property of unions: ```csharp public interface IUnion { object? Value { get; } } ``` Union declarations in C# would automatically add this interface to the lowered type. This allows code to dynamically test if something is of a union type, and if so, get at its contained value. It also can be used as a marker for e.g. the compiler to treat a type as a union type; see [Custom unions](#custom-unions) below. In some ways this is analogous to `ITuple` which allows tuple values to be dynamically discovered and interacted with. ##### Conclusion Adopted as is. #### Generic 'IUnion' interface The proposed interface declares a static method to test whether an incoming value could be contained in the implementing union type, and if so creates such a union value from it: ```csharp public interface IUnion : IUnion where TUnion : IUnion { static abstract bool TryCreate(TValue value, [NotNullWhen(true)] out TUnion union); } ``` Again union declarations in C# would automatically add this interface to the lowered type. We noted that the method is a "GVM" - a generic virtual method, which is bad for AOT compilation and generally avoided, especially in widely-used library types. The type is generic only so that it can avoid boxing when the incoming value is of a value type. We could instead declare the method non-generically as: ```csharp public interface IUnion : IUnion where TUnion : IUnion { static abstract bool TryCreate(object? value, [NotNullWhen(true)] out TUnion union); } ``` ##### Conclusion We're on board with the value of the proposal, but want to go with the non-GVM version of the `TryCreate` method. #### 'IUnionUnboxed' interface The proposed interface would allow a general means of querying union types for content of a given type, without causing boxing of the value. This is as an alternative to asking though the `IUnion.Value` property described above, which would cause boxing of value types. ##### Conclusion We summarily dismissed this proposal, as it relies fundamentally on a GVM. As with the previous interface we want to start from a position of preferring potential boxing to the risks of GVMs. ### Custom unions Specification: https://github.com/dotnet/csharplang/blob/5736dbf3d4008498acb120669a4ede96dd69e104/meetings/working-groups/discriminated-unions/custom-unions.md The overall proposal is to allow developers to write their own union types "manually" instead of having them generated by the new `union` syntax. When following the proposed pattern (implement the non-generic `IUnion` interface and offer a public constructor for each case type), the compiler would allow the type to be consumed equally to a compiler-generated union type, in terms of: - Implicit conversion from each case type to the union type, - Pattern matching that implicitly indirects through the `Value` property, and - Exhaustiveness in switch expressions. This allows existing types that are already effectively union types to be imbued with first-class language behavior, as well as new union types that for various reasons use other strategies for e.g. content storage. #### Conclusion Adopted. It's possible the specific pattern will evolve slightly as we build the feature, but this is a great place to start from. ### Non-boxing access pattern Specification: https://github.com/dotnet/csharplang/blob/5736dbf3d4008498acb120669a4ede96dd69e104/meetings/working-groups/discriminated-unions/non-boxing-access-pattern.md For efficiency, some custom unions may store their contents in a non-boxing manner. However, such efficiency gains are thwarted if the value can only be accessed through the `Value` property, which would just box them anyway. The proposal is for the compiler to recognize an optional alternative access pattern on unions that avoids boxing. With this pattern, the custom union type offers a `HasValue` property for null checking, and a `TryGetValue` method for each case type, e.g.: ```csharp public struct MyUnion : IUnion { public bool HasValue => ...; public bool TryGetValue(out Case1 value) {...} public bool TryGetValue(out Case2 value) {...} object? IUnion.Value => ...; } ``` Pattern matching will then be implemented in terms of these members rather than the `Value` property when possible. #### Conclusion Adopted. ================================================ FILE: meetings/2025/LDM-2025-08-13.md ================================================ # C# Language Design Meeting for August 13th, 2025 ## Agenda - [Unions](#unions) ## Quote of the Day - "The operation succeeded, but the patient died" ## Discussion ### Unions Champion issue: https://github.com/dotnet/csharplang/issues/8928 Union overview: * https://github.com/dotnet/csharplang/blob/c3325533e57dec6aec3266e066e39abf7260e87a/meetings/working-groups/discriminated-unions/union-proposals-overview.md Specs: * https://github.com/dotnet/csharplang/blob/eb5381bf21b884a0dca53c9e127c0f527503fdfc/proposals/closed-hierarchies.md * https://github.com/dotnet/csharplang/blob/eb5381bf21b884a0dca53c9e127c0f527503fdfc/proposals/nominal-type-unions.md * https://github.com/dotnet/csharplang/blob/eb5381bf21b884a0dca53c9e127c0f527503fdfc/proposals/case-declarations.md Reviewed document: * https://github.com/dotnet/csharplang/blob/eb5381bf21b884a0dca53c9e127c0f527503fdfc/meetings/working-groups/discriminated-unions/to-nest-or-not-to-nest.md We spent the meeting discussing union type nesting. There are two axes to consider: closed hierarchies vs. unions, and nested vs. non-nested types. We examined all four combinations and, while we did not reach a decision, several ideas emerged: 1. For closed hierarchies, we're unsure whether we actually need a "brief" syntax. We think these are scenarios that are naturally more complex and don't have built-in assumptions, so we might not benefit much from an opinionated, more succinct syntax. 2. We're also unsure whether unions need a brief syntax for top-level cases. We think these might be the more opinionated types, much like records are more opinionated than classes, and that nesting might be the opinion that unions have. 3. For type inference, we aren't too concerned by this given the above defaults, but we are open to the new type inference improvements being proposed as part of this feature. We don't think they are blockers, though. 4. There's some similarity between the designs we've arrived at and Scala 3's design decisions, so we do have some prior art that we can examine further. ================================================ FILE: meetings/2025/LDM-2025-08-18.md ================================================ # C# Language Design Meeting for August 18th, 2025 ## Agenda - [C# 15 Kickoff and Themes](#c-15-kickoff-and-themes) - [Unions](#unions) - [Extensions](#extensions) - [Dictionary expressions](#dictionary-expressions) - [Type inference](#type-inference) - [Continuing the null monad](#continuing-the-null-monad) - [Object initialization](#object-initialization) - [Top-level methods](#top-level-methods) - [Caller type name](#caller-type-name) - [Switch statement/expression improvements](#switch-statementexpression-improvements) ## Quote of the Day - "Well, now I can't unsee that." "I can't unsee that either." "Well, it used to be a you problem." ## Discussion ### C# 15 Kickoff and Themes Today we took a look through some active topics on the minds of various LDM members for C# 15, which follow below in a somewhat raw form. > [!NOTE] > While we will be working on many/most of these topics during the C# 15 timeframe, no release timeframe or guarantee is given at this time. > Particularly for highly requested topics like unions, please keep in mind: we'll get there when we get there. #### Unions Champion issue: https://github.com/dotnet/csharplang/issues/8928 We have a lot of union work in progress, and an overview can be found at https://github.com/dotnet/csharplang/blob/c3325533e57dec6aec3266e066e39abf7260e87a/meetings/working-groups/discriminated-unions/union-proposals-overview.md. We'll be continuing design work here and are hopeful that C# 15 will at least have preview versions of features in this area. #### Extensions Champion issue: https://github.com/dotnet/csharplang/issues/8697 C# 14 will deliver the next iteration of extension members, but we did not get to all the member types we want yet. We expect to turn the crank on this in C# 15. Of particular interest are the other basic member types that we haven't implemented yet and disambiguation on methods vs. properties. #### Dictionary expressions Champion issue: https://github.com/dotnet/csharplang/issues/8659 A decent amount of design work went into dictionary expressions in C# 14, but time constraints ultimately meant we did not get to ship them. We'd like to get them over the line for C# 15. #### Type inference Champion issues: * https://github.com/dotnet/csharplang/issues/9453 * https://github.com/dotnet/csharplang/issues/9626 * https://github.com/dotnet/csharplang/issues/9627 Several type inference proposals have come up at this point, both on their own and as part of unions. We're starting to think there may be a theme of type inference improvements we could make for C# 15. #### Continuing the null monad We have a few proposals around `foreach?` and `await?`, maybe it's time to revisit these. #### Object initialization There are a few improvements around object initialization that have been considered for a few years, such as expanded object initializers, `init` fields, final validators, and more abilities in `with` expressions. #### Top-level methods A few LDM members have been looking at a proposal around allowing top-level methods more broadly, and also expanding where extension blocks can be declared. We may end up delving further into this. #### Caller type name Of all the caller info attributes, `CallerTypeName` is one of the most requested. #### Switch statement/expression improvements Block bodies for switch expressions and improvements to the switch statement have been on our minds for a while. ================================================ FILE: meetings/2025/LDM-2025-08-20.md ================================================ # C# Language Design Meeting for August 20th, 2025 ## Agenda - [Discussion](#discussion) - [Union interfaces](#union-interfaces) - [Target-type inference improvements](#target-type-inference-improvements) ## Quote of the Day - "So essentially, we have 1007 votes for" ## Discussion ### Union interfaces Champion issue: https://github.com/dotnet/csharplang/issues/9566 Unions overview: https://github.com/dotnet/csharplang/blob/c3325533e57dec6aec3266e066e39abf7260e87a/meetings/working-groups/discriminated-unions/union-proposals-overview.md Specification: https://github.com/dotnet/csharplang/blob/c3325533e57dec6aec3266e066e39abf7260e87a/proposals/union-interfaces.md We started today by looking at a proposal for a set of interfaces that would allow existing union-like types to be considered unions by the language. This proposal is somewhat complicated, and after some discussion, we're not sure that the problem of adapting existing union-like types needs to be part of the initial set of union features we ship. We have some concerns about whether this will be confusing for new union types to implement, as these interfaces would be preferred even when explicitly implemented on a type, and we're not convinced that these interfaces would only be used by older types. Given this, we want to keep this in our back pocket for now and wait to see user scenarios and explicitly design around them, rather than trying to solve it now. #### Conclusion Tabled for now; we will pick this back up with explicit scenarios in mind. ### Target-type inference improvements Champion issues: * https://github.com/dotnet/csharplang/issues/9626 * https://github.com/dotnet/csharplang/issues/9627 Specifications: * https://github.com/dotnet/csharplang/blob/c3325533e57dec6aec3266e066e39abf7260e87a/proposals/target-typed-generic-type-inference.md * https://github.com/dotnet/csharplang/blob/c3325533e57dec6aec3266e066e39abf7260e87a/proposals/inference-for-constructor-calls.md Next and last, we looked at proposals for type inference improvements. These affect two main scenarios: * When calling generic members and providing a target type, and * When calling the constructor of a generic type. While these scenarios are initially motivated by our work in unions, they are by no means only applicable to them. For example, a number of generic factory methods exist only because generic type inference can be performed from the arguments of methods, but not on constructors. We are in favor of improvements here, but there will be some difficult compat needles to thread. On the one hand, we want users to be able to apply rules that are already understood to other locations; methods, for example, can be overloaded on generics today. It would be nice if the same intuition that has been built up around this scenario could apply to types overloaded on generics in constructor calls. On the other hand, we didn't do this back in C# 2 because of concerns about breaking changes, and it has been a long time since then. However, we are agreed that we need to do some investigation and experimentation to find the correct point on the dial for compat, and we do think that these are improvements we would like to see in C#. #### Conclusion Proposals accepted for the working set. ================================================ FILE: meetings/2025/LDM-2025-08-27.md ================================================ # C# Language Design Meeting for August 27th, 2025 ## Agenda - [Type inference in patterns](#type-inference-in-patterns) - [Type value conversion](#type-value-conversion) - [Union syntax](#union-syntax) ## Quote of the Day - "Boiled frogs taste delicious." "Not if you're the frog." ## Discussion ### Type inference in patterns Champion issue: https://github.com/dotnet/csharplang/issues/9630 Specification: https://github.com/dotnet/csharplang/blob/12e6f5b0d512d15d32c8e7ae95674bd070b2758f/proposals/inference-for-type-patterns.md First up today, we looked at the next proposal in the list for type inference improvements. These proposals arise from the general unions discussion but can also be useful outside of unions. In this case, we're looking at type inference inside patterns. Unlike the previous two proposals, which were easier sells, LDM is more skeptical about this one. In particular, type inference has more latitude for patterns than it does for constructor calls, since you can start with a base type and narrow to a derived type. In constructors, the `new` must refer to something that either is the same type as the target, or has a direct conversion to the target. In patterns, that isn't the case. You can have a pattern that downcasts, upcasts, or checks a completely unrelated interface, which affords much more freedom. We also know of users who will explicitly state types to serve as a `null` check in their patterns, even if that's an identity conversion. There are mitigations though: if a user has a generic type in hand and they're pattern matching on it, we think it's unlikely that they would upmatch to the non-generic base rather than to a more derived type. The main scenario we can think of in this category would be something like: ```cs IEnumerable e = ...; if (e is IList) { ... } ``` Under the current proposal, this would continue to match `System.Collections.IList`, and not infer `System.Collections.Generic.IList`, but should it? Would we consider breaking this behavior? There's also concern that it may be surprising that a pattern, which is supposed to match against a type, might do type inference that isn't immediately visible at the pattern site. One potential option would be to always require some sigil or syntax to inform both the compiler and the user that inference is happening and expected. For example, `Type<>`. This particular syntax didn't immediately resonate with everyone though; a few LDT members preferred no syntax at all, and others felt that it might be confusing given that open types in `typeof` require commas to differentiate how many omitted type parameters exist. Another topic we briefly considered was whether to push inference further. An `is Type` check is effectively an `as` combined with an immediate `null` check, and an `as` is just one form of cast. Should both `as` casts and hard casts also support this? It seems reasonable that if `option is Some` would benefit, `(Some)option` would similarly benefit. #### Conclusion We came up with more questions than answers. This will need further investigation; we see value in the proposal, but we need to think about it more. ### Type value conversion Champion issue: https://github.com/dotnet/csharplang/issues/8928 Document: https://github.com/dotnet/csharplang/blob/12e6f5b0d512d15d32c8e7ae95674bd070b2758f/meetings/working-groups/discriminated-unions/type-value-conversion.md Next, we looked at yet another union-related proposal, this time on allowing type expressions to be directly converted to instances. While we think this is useful for singleton union cases, we also think it has applicability outside of unions. We think there are at least three approaches we can take to this problem: 1. A conversion, as specified right now. The main disadvantage of this is that conversions generally can't happen in the middle of a dotted expression, except on extension receivers. The advantage, though, is that we don't need to solve a new `Color Color` problem, since we wouldn't be introducing a new spot where type and instance confusion can occur. 2. Allowing users to define a property with the same name as a nested type. This is the real root of the specific problem that this proposal is solving: we want to allow things like `Option.None` as a value. If that was just a property with the same name as the nested type, then it would work fine. That would give us a new instance of the `Color Color` problem, though, as we'd need to determine, every time we see `Option.None`, whether `None` refers to the property or the type. 3. Introduce a general operator for this (not a conversion) that can occur in the middle of dotted expressions. This would also require us to solve the general `Color Color` problem, but it is more generally useful than option 2 for non-nested singleton types. After discussion, we preferred option 3: it's the most generally useful and can solve this for both nested and non-nested types. There are still plenty of design decisions to make, but we like the direction. #### Conclusion We'll work on specifying option 3 more completely. ### Union syntax Champion issue: https://github.com/dotnet/csharplang/issues/9411 Union overview issue: https://github.com/dotnet/csharplang/issues/8928 Specification: https://github.com/dotnet/csharplang/blob/12e6f5b0d512d15d32c8e7ae95674bd070b2758f/proposals/nominal-type-unions.md Finally, we took a brief look at our union syntax again. A few members of the LDM and some members of the community are not satisfied with the current `union Pet(Cat, Dog, Bird);` syntax. Much of the current concern can be summarized as "it looks like a primary constructor that implies order, which it's not." One thing we want to make sure of is that we're not over-indexing on making new syntax stand out; is this a real consistency issue, or is this just an initial confusion that will fade with usage? We brainstormed a few other syntax options: 1. The original: `union Pet(Cat, Dog, Bird);` 2. Using bars: `union Pet(Cat | Dog | Bird);` 3. Using or: `union Pet(Cat or Dog or Bird);` 4. Using enum bodies: ```cs union Pet { Cat, Dog, Bird } ``` Option 2 received little support. Option 3 provides a nice symmetry with how consumption in patterns will be structured. Option 4 has a nice symmetry with enums and potentially provides a single syntax we could use for defining members on both unions and enums. We didn't make a decision today, but we do think it's clear that there's enough here to warrant revisiting the syntax decision. ================================================ FILE: meetings/2025/LDM-2025-09-10.md ================================================ # C# Language Design Meeting for September 10th, 2025 ## Agenda - [Unions overview](#unions-overview) ## Quote of the Day - "I will say 'case' 100 times where no pun is intended, except this one time, where it was intended." ## Discussion ### Unions overview Champion issue: https://github.com/dotnet/csharplang/issues/9662 Specification: * https://github.com/dotnet/csharplang/blob/dab5b33a0b41a95cdc0a8a7b9c5bd33d60eb224b/proposals/unions.md * https://github.com/dotnet/csharplang/blob/dab5b33a0b41a95cdc0a8a7b9c5bd33d60eb224b/meetings/working-groups/discriminated-unions/union-proposals-overview.md Today, we took time for a broad overview of the various union proposals being considered for C#, as compiler team members are about to start implementation. We need general agreement on the direction and scope of what is being proposed, using the overview linked above and the proposals linked from it. What follows are the points brought up during discussion: * Union interfaces are still a debated topic, including: * Do we need them at all, or should we wait for concrete use cases? * If we don't add them in the first ship cycle, can we ever add them later? * Do we need both `IUnion` and `IUnion`? We don't have both `IAsyncEnumerable` and `IAsyncEnumerable`; only the generic version exists. * We need to revisit these questions during the first ship cycle, but we'll keep the interfaces as defined for now to avoid blocking compiler work. * The specification likely can't claim that a struct union with a single field of a reference type is set/read atomically. C# and ECMA-335 do not guarantee this for non-primitive types, and regardless of whether current implementation realities make it effectively true, nothing stops a compliant runtime implementation from adding padding or otherwise changing the layout of a struct to make reads and writes non-atomic. We may need helpers to ensure this atomicity. * Union conversions need more detail, such as whether they are standard conversions and where they participate. * Union constructors and their assumptions around well-formedness may need another look: would it be simpler for users and type authors if they received errors instead of the compiler presuming that, given multiple constructors, any will work? * There are still general syntax questions; we expect to have full proposals to compare and contrast soon. * We may want to start more restrictively regarding user-defined versions of generated properties. It's better to start stricter and loosen later rather than the other way around. #### Conclusion We have a strong opening proposal to start implementation. There are still plenty of decisions to make, but we are confident we can proceed and start getting this into people's hands for feedback. ================================================ FILE: meetings/2025/LDM-2025-09-17.md ================================================ # C# Language Design Meeting for September 17th, 2025 ## Agenda - [Unsafe evolution](#unsafe-evolution) ## Quote of the Day - "I looked at the MITRE CVEs, and this is the bad stuff they classify as memory safety. Let's have none of that." ## Discussion ### Unsafe evolution Champion issue: https://github.com/dotnet/csharplang/issues/9704 Proposal: https://github.com/dotnet/designs/blob/656ade997fceb32d6ff1c3ebd5a8ef10e798361e/proposed/caller-unsafe.md Today, we took a look at a proposal on expanding the definition of unsafety in C#. This was a very broad overview session; we did not dig heavily into specifics of how it would actually work, other than a bit of digression around inheritance. The proposal is far more complex than we have gone over today and will need much more detail to be formalized before LDM can truly sign off on the specifics. On inheritance, we had some discussion around what `unsafe` means on a type in this new world, and whether overrides can add `unsafe` where the overridden method is not marked `unsafe`. Initial reactions here suggest that we may block both of these things; in this new proposal on unsafety, types are not inherently unsafe in and of themselves. Just having a pointer field is not unsafe by itself; the unsafety occurs when that pointer is dereferenced, or passed to a location that the compiler cannot verify for safety (such as interop). Overall, we are positive on the direction of this feature, but we'll definitely need to dig into specifics of the language proposal as it becomes clearer and more filled out. ================================================ FILE: meetings/2025/LDM-2025-09-24.md ================================================ # C# Language Design Meeting for September 24th, 2025 ## Agenda - [Union Syntax Thunderdome Part 1](#union-syntax-thunderdome-part-1) ## Quote(s) of the Day - "You'll all choke on your own bile and say that can't possibly be the solution" - "I got a head start on showing [redacted] that they're wrong. You know, I gotta get my hand up there and make sure I'm first in line." - "Let's get a yummy rare steak in the ground" ## Discussion ### Union Syntax Thunderdome Part 1 Champion issue: https://github.com/dotnet/csharplang/issues/9662 Specification: https://github.com/dotnet/csharplang/blob/38fd5f33d285cb190268f98cea16223cc0a5b8bc/proposals/unions.md Proposals: * https://github.com/dotnet/csharplang/blob/38fd5f33d285cb190268f98cea16223cc0a5b8bc/meetings/working-groups/discriminated-unions/allows.md * https://github.com/dotnet/csharplang/blob/38fd5f33d285cb190268f98cea16223cc0a5b8bc/meetings/working-groups/discriminated-unions/brace-syntax.md Today we begin the syntax thunderdome for unions: while we don't expect to make every decision today (and spoiler alert, we did not), we need to make enough progress today and in the next couple of meetings for the compiler team to start implementation without later needing to significantly redesign the parsing stages. We have a few different options on the table, and we started to break them down by philosophies. Specifically, we have 2 main flavors of union here: a union that states that its cases are pre-existing types, and a union that states that its cases are types that should be generated inline. One major question, therefore, is whether or not we expect these different flavors to mix; do we expect users to often declare intermixed unions of both pre-existing types and new types, or do we expect it to be homogenous. And, if we often expect it to be homogenous, do we really need a syntax that allows both flavors in a single union declaration? One key insight is that the union of newly created types is just sugar over the union of pre-existing types; we can likely formalize the former as literally desugaring into the latter, with the new types declared as nested types within the union. This then potentially allows us to nicely relate this back to `enum`s; we can say that `enum`s are for declaring _new_ cases (whether that's numeric cases or types cases), and `union` is specifically for bundling a group of existing types. We could then potentially allow a grow-up story for `enum`s where they can add new types of members. Some difficult hurdles remain in that area though: * How does the language decide whether an `enum` is number-based or type-based? Is the difference just in whether any of the cases has nested values? Is that too subtle? * Other languages do use just this nested value presence in similar fashions, like Swift and Rust, but neither are quite like C# enums. * Could we perhaps use a modifier like `enum class` or `enum struct` to convey this difference? * How would such enums interact with `System.Enum`? Would we change the runtime to allow them to inherit from `System.Enum`? Would that be too large of a breaking change? * If they don't interact with `System.Enum`, is that too large a divergence to continue using the `enum` keyword? We do think that this idea has some merit and needs to be explored, and several LDM members plan to do just that ahead of Monday's meeting. We also took some initial reads of the room on the basic building blocks of unions. So far, LDM leans towards unions not having a dedicated shorthand syntax for declaring new types; they are made up of existing types. We are not ruling out the separate `enum` of types idea that would desugar into a `union` with nested declarations, but to use an analogy to `class`es and `record`s, `union`s would be the more general building block on which a potential `enum` of types feature would build, just as `class`es are the building block on which `record`s build. We also thought about whether the cases of `union`s should be inside or outside the curly braces of the body. Again, there are no easy options here. If they are outside the curly braces, our precedent for scoping visibility suggests that any nested types from inside the `union` are not visible without being qualified by the type of the `union`, including any generic type arguments. However, in order to implement closed type hierarchies, we may need to have a list of the allowed implementations, similar to Java's `permits` clause, both for compiler performance and for reader comprehension. If we do need to have such a clause for that case, it might be a good idea to unify that with `union`s. On the other hand, `union`s could also reuse the `enum` syntax; again, if we expect to allow other member types to be defined in an `enum`, we will need to solve the syntax question of both listing the cases and other members in the body of the enum, and `union` could potentially reuse that same syntax. Subjectively, we do have a slight leaning that by writing the members inside the body, rather than at the declaration level, it implies that they are _new_ things, rather than existing things. Given that, we have a very slight lean towards outside the body of the `union`, but will explore more proposals on Monday and hopefully make further concrete progress on this very gnarly question. ================================================ FILE: meetings/2025/LDM-2025-09-29.md ================================================ # C# Language Design Meeting for September 29th, 2025 ## Agenda - [Union Syntax Thunderdome Part 2](#union-syntax-thunderdome-part-2) - [Enums vs Unions](#enums-vs-unions) ## Quote of the Day - "Only this option here can have llamas in it, so that's its claim to superiority" ## Discussion ### Union Syntax Thunderdome Part 2 Champion issue: https://github.com/dotnet/csharplang/issues/9662 Specification: https://github.com/dotnet/csharplang/blob/38fd5f33d285cb190268f98cea16223cc0a5b8bc/proposals/unions.md Proposals: * https://github.com/dotnet/csharplang/blob/38fd5f33d285cb190268f98cea16223cc0a5b8bc/meetings/working-groups/discriminated-unions/allows.md * https://github.com/dotnet/csharplang/blob/38fd5f33d285cb190268f98cea16223cc0a5b8bc/meetings/working-groups/discriminated-unions/brace-syntax.md Today we are following up from [last time](./LDM-2025-09-24.md#union-syntax-thunderdome-part-1). We've decided that we want to have syntax outside of the body of the union for the list of allowable cases, and today we need to decide what the initial form of that will be. This is by no means a truly final decision, and part of the reason for front-loading this decision is so that we can get previews into user hands for feedback. We gathered a number of possible syntaxes here, though we did not discuss the merits of every single one. ```cs union Pet(Cat, Dog, Bird); union Pet[Cat, Dog, Bird]; union Pet(Cat | Dog | Bird); union Pet(Cat or Dog or Bird); union Pet allows Cat, Dog, Bird; union Pet includes Cat, Dog, Bird; union Pet contains Cat, Dog, Bird; union Pet has Cat, Dog, Bird; union Pet with Cat, Dog, Bird; union Pet of Cat, Dog, Bird; union Pet switch Cat, Dog, Bird; union Pet case Cat, Dog, Bird; union Pet is Cat, Dog, Bird; union Pet is Cat or Dog or Bird; ``` Broadly speaking, our ideas fall into two categories: a list surrounded by some kind of delimiter, such as `()` or `[]`, and a list started by some keyword, more similar to generic constraint clauses. When we considered these, we also wanted to make sure we thought about them in the broader context of what a `union` type will be able to do: have generic type parameters, constraints, implement interfaces, have a body with declarations, etc. Below are some assorted notes from discussions of a few of these syntaxes: * `union Pet(Cat, Dog, Bird)` looks like primary constructor syntax at first glance, will it conflict with actual primary constructors if we add them? * `union Pet(Cat or Dog or Bird)` has a nice initial symmetry with patterns, but is that actually a good thing? If we matched on a pattern with `val is (Dog or Cat) dogOrCat`, `dogOrCat` isn't going to magically become an anonymous union of `Dog or Cat`, it will still be a `Pet`. * `allows` means the opposite in C# today; it is "everything normally _and also_ ref structs". In this context, it would mean "only the types listed". * `case` does have some symmetry with the proposed `case` classes, but those are also currently being used to actually declare a new case type. Here, it would just be the valid options of `Pet`, not declaring a new case. * `is` has the same issues as the parenthesized `or` case above. After discussion here, we have a leaning towards the first option: `union Pet(Cat, Dog, Bird)`. We do want to hear from users on this decision though, so we will make sure to revisit this with feedback in mind to determine if that's the final form, particularly if we also decide to support primary constructors in this area. #### Conclusion We are moving forward with the `union Pet(Cat, Dog, Bird)` syntax form. ### Enums vs Unions Champion issue: https://github.com/dotnet/csharplang/issues/9662 Specifications: * https://github.com/dotnet/csharplang/pull/9711 * https://github.com/dotnet/csharplang/pull/9713 For our second topic today, we discussed the evolution of ADT hierarchies in C#. There are two possible approaches on the table at the moment: expanding out the `union` feature to allow declaration of cases implicitly, or expanding out the existing `enum` features to allow declaring non-numeric cases. While these both sound dissimilar at first description, one thing that we realized after discussion is that these ideas are extremely compatible at the semantic level, with different skins around the same general concept of having a shorthand for declaring an ADT. This is validating for the kernel of the idea itself, as no one is pushing back that this is generally a thing that we want C# to have. However, it again puts us in a thunderdome of syntax, of `union` vs `enum`. One nicety about `enum` is that it already has the implication that the cases are newly-declared constructs; for us to use `union` as the keyword, we'd have to either have an additional modifier on the `union` declaration itself, or we'd have to have `case` or some similar modifier on the type declarations. The `enum` approach would also give us precedent to expand `enum`s in other directions; [`string` `enum`s](https://github.com/dotnet/csharplang/discussions/8804), for example, are a very highly-requested item that we have thus far avoided due in part to this issue. There is also an opportunity to use this as the point when we refresh `enum`s, allowing the declaration of other member types that cannot exist in traditional `enum`s today. This doesn't come without consequences. Today, `enum`s mean numbers. It's going to be a large adjustment for users if this is no longer true, and is generally a very large shift in the way we think about these things. But we like the direction, and while we haven't settled on exact syntax (is it just `enum`? Is it `enum union`? Is it `enum class`? Are the cases declared with `case`? and other such debates), we feel confident in this direction. We expect this to come after the `union` feature, building on that as the way that these `enum`s get translated into actual types, but we do expect it to happen. #### Conclusion We will move forward with the enum-centric approach. ================================================ FILE: meetings/2025/LDM-2025-10-01.md ================================================ # C# Language Design Meeting for October 1st, 2025 ## Agenda - [Closed enums](#closed-enums) - [Closed Hierarchies](#closed-hierarchies) ## Quote of the Day - "Have a good vacation Mads!" ## Discussion ### Closed enums Champion issue: https://github.com/dotnet/csharplang/issues/9011 Specification: https://github.com/dotnet/csharplang/blob/7603e3bee2ea3edb53e6a3b27efbaf24949b609f/proposals/closed-enums.md First up today, we took another look at the current proposal for closed enums, to verify that after all of our related work on unions, we are still interested in this feature. Ultimately, we are, but there are definitely some thorny questions to answer around just how strict we can or should make this. One example is in the BCL: do the various `Enum` members need to behave differently in the face of a closed enum? What about exhaustiveness in switch statements; presumably we want it to handle not needing a `default` case for returning, but how will that interact with `switch` statements that are intentionally not exhaustive, or in `void`-returning methods where there isn't a "you forgot to return" error? We do think that this is still an area we want to pursue, so we will keep working on these questions. #### Conclusion We will be pursuing closed enums. ### Closed Hierarchies Champion issue: https://github.com/dotnet/csharplang/issues/9499 Specification: https://github.com/dotnet/csharplang/blob/7603e3bee2ea3edb53e6a3b27efbaf24949b609f/proposals/closed-hierarchies.md Next up, we did the same pass for closed hierarchies, making sure that we are still interested in this now that we have a better understanding of our goals for unions. Again, we are still interested in the feature, and we want to pursue implementation. One question that we will need to answer is around performance; one major argument for having an explicit list of allowed subtypes is for compiler performance, to ensure that we don't have to scan the entire compilation for potential subtypes. This is something that we need to play with in real code to see what the performance is in reality, to ensure that we're not basing a language decision on pessimistic assumptions. There is still a chance that we'll decide that we need the list for programmer convenience, but this is something that we think we need real feedback and usage on. There are various options for how such a list could be constructed: we could use the same syntax as we have for `union`s, or we could force all declarations to live in the same file or have partial parts in the same file. But we will start with no listing requirement and see what that looks like. #### Conclusion We will be moving forward with closed hierarchies. ================================================ FILE: meetings/2025/LDM-2025-10-13.md ================================================ # C# Language Design Meeting for October 13th, 2025 ## Agenda - [Collection expression safe-context bug fix](#collection-expression-safe-context-bug-fix) - [Immediately-enumerated collection expressions](#immediately-enumerated-collection-expressions) ## Quote of the Day - '"Bonafide Memory Safety Bug" is a pretty good band name.' ## Discussion ### Collection expression safe-context bug fix Issue: https://github.com/dotnet/csharplang/issues/9750 We started today by examining a proposed bug fix for an issue around safe contexts and collection expressions. The current behavior is a bug, but one that will cause new errors to be reported when it's fixed to reflect the specification as written. We do think that it's a worthy change though, similar in nature to the breaking change around variable scoping in loops in C# 5. We also briefly considered whether the compiler can be smarter and take the safe-context of the destination into account, but there are immediate and easy examples of where the assignment flows through multiple levels, making it hard for both the compiler and human readers to track. Given this, we will update the compiler to follow the specification as written, and will revisit if this break causes too many follow-on issues. #### Conclusion Break is accepted. ### Immediately-enumerated collection expressions Champion issue: https://github.com/dotnet/csharplang/issues/9754 Specification: https://github.com/dotnet/csharplang/blob/cb0652db1e5b7cbc1b3faae61b0df9ee175b9b90/proposals/immediately-enumerated-collection-expressions.md#use-of-special-language-level-collection-type-for-immediately-enumerated-collections Next up today, we looked at a proposal for allowing immediately enumerated collection expressions in the language. One thing that came up immediately was that this is really 2 proposals in one; while the goal was originally just for non-observable collections to be enumerated (in spreads or `foreach`), the rules end up accomplishing more as they push type inference through more locations that aren't obvious from the title. We're concerned that there are effects beyond the examples in the specification that need to be thought through for this inference; for example, how should array creation expressions and lambda types be affected by the type inference rules? After some reflection, we're comfortable breaking this into two separate parts; the first is as in the title of the proposal, immediately-enumerated collection expressions. This change would allow `foreach` over a collection expression, but by itself wouldn't permit spreading a ternary of collection expressions. The rules for type inference can then be fleshed out further and the corners explored, and brought back as a separate proposal that enables its own abilities (such as `M(b ? [1, 2] : [3, 4])`). We also looked at the "type" that we want to use for these immediately-enumerated scenarios. We feel that we want to start restrictive, and later relax restrictions, in order to protect future work in natural types. For example, if we were to say that the type of an immediately-enumerated expression is an array, then that would permit pointer types. If we were to then choose `List` as the natural type of a collection expression, that would then create inconsistency on what you can `foreach` vs what you can assign to a `var` local. Another interesting aspect is `ref foreach`. We think there's some thorny questions on what an interpretation of this code would be: ```cs int x = 1; // Does this create a copy of `x`, and `y` is a ref to that copy, or is `y` a ref to `x`? foreach (ref int y in [x]) { y = 2; } Console.Write(x); ``` Either interpretation is potentially confusing, so we think it's best to simply avoid the question and disallow `ref foreach` in this area. After some debate, we landed on `IEnumerable` as the type to use in `foreach`. It should preserve our ability to make changes in the future without ending up in an inconsistent space, while also still allowing the implementation to do whatever efficient form it chooses. Finally, we looked at whether we could apply similar optimizations for `foreach (var x in new[] { 1, 2, 3 })`. We ultimately don't think this is the purview of the language. There are a few types like array and `List` that we could special case, but we can't do this in general. The runtime has been working on escape analysis to avoid heap allocations for this type of scenario, and will continue to do so. We think that's the appropriate place for this work to go. #### Conclusion Proposal is split up into immediately-enumerated collection expressions and type inference improvements for collection expressions. The former is approved. The type of the immediately-enumerated expression will be `IEnumerable`. We will not pursue optimizing other types of immediately-enumerated collection creation. ================================================ FILE: meetings/2025/LDM-2025-10-29.md ================================================ # C# Language Design Meeting for October 29th, 2025 ## Agenda - [Unsafe evolution](#unsafe-evolution) ## Quote of the Day I did not record any particularly amusing quotes today, sorry ## Discussion ### Unsafe evolution Champion issue: https://github.com/dotnet/csharplang/issues/9704 Specification: https://github.com/333fred/csharplang/blob/1b22088adb4e42323a3906c4e26105d001c79761/proposals/unsafe-evolution.md Related: https://github.com/dotnet/designs/blob/2b9e1b311d16ea142b4047d6eddeef15979ba6de/proposed/caller-unsafe.md Today, we started reviewing the initial specification for evolving `unsafe` at the C# level. We spent most of the session simply getting clarity on what is being proposed, the impacts to code, and the limits of what can be detected by the compiler. Of particular interest was the boundary between safe and unsafe code: can we strengthen guarantees about what must be in an `unsafe` block? We considered this hypothetical bug: ```cs int* ptr = stackalloc int[] { 1, 2, }; int addr = 0; AddOne(ref addr); unsafe { Console.WriteLine(ptr[addr]); } void AddOne(ref int i) { i++; i++; // Oops, someone accidentally duplicated a line } ``` If that bug were checked in, the memory unsafety occurs in the `unsafe` section, but the true bug that _caused_ the unsafety occurred in C# that has no reason to be marked as `unsafe`, and no part of the calculation that was in error is required to be inside the `unsafe` block. Could there perhaps be an expanding rule, such that any inputs to an `unsafe` operation must also be in an `unsafe` block? We ultimately don't think this expansion is worth it; in general, we do not expect that it will be possible to detect every single input that is ultimately unsafe. A counterexample is something like: ```cs void ReadFromArrayWithOffset(int[] array, int addr) { if (addr + 1 >= array.Length) { throw new ArgumentOutOfRangeException(); } AddOne(ref addr); fixed (int* ptr = array) { unsafe { Console.WriteLine(ptr[addr]); } } } ``` How would the compiler be able to detect that, in fact, `addr` (up until the `AddOne` call) was actually perfectly safe and valid? It is an input to the unsafe operation, so virality would likely demand that `ReadFromArrayWithOffset` be declared as `unsafe`. Aliasing rules further complicate this. The goal of the `unsafe` feature is to better call out what parts of the code need more manual scrutiny, but they ultimately don't _replace_ that scrutiny. We think it is the job of the reviewers looking at this code to understand what is really involved in the calculations and enforce `unsafe` boundaries appropriately here. We made no conclusions today, but have a better understanding of where the boundaries of the feature are and what the principles we will be using to make further decisions are. We will come back next week to continue work here. ================================================ FILE: meetings/2025/LDM-2025-11-05.md ================================================ # C# Language Design Meeting for November 5th, 2025 ## Agenda - [Unsafe evolution](#unsafe-evolution) ## Quote of the Day - "Do COM interfaces need to be unsafe?" "[with a look of visible pain] Well that's a \*\*\*\* question" ## Discussion ### Unsafe evolution Champion issue: https://github.com/dotnet/csharplang/issues/9704 Specification: https://github.com/dotnet/csharplang/blob/ff88c654e26def239d2f7bcab52fe4e4d016dfba/proposals/unsafe-evolution.md Related: https://github.com/dotnet/designs/blob/2b9e1b311d16ea142b4047d6eddeef15979ba6de/proposed/caller-unsafe.md Picking up from [last time](LDM-2025-10-29.md#unsafe-evolution), we continue to discuss how we want to evolve `unsafe` in C#. From the last time, the proposal was revised to include a few more open questions, as well as to reduce the level of breaking change it was proposing. Today, we were able to discuss 2 main topics: 1. Do we accept the core kernel of the proposal, moving `unsafe` from meaning "there exists a pointer type" to "there exists memory unsafety, as defined by the [caller unsafe](https://github.com/dotnet/designs/blob/2b9e1b311d16ea142b4047d6eddeef15979ba6de/proposed/caller-unsafe.md) document being proposed for the platform? 2. Will the new rules be warnings, errors, or some form of suppressible error between? For the first question, we agreed that yes, this moving `unsafe` into this newer world is acceptable. That left the majority of the meeting for discussion on warning vs error, which also bridges into areas of "how would the state be controllable?". The repeated analogy here is to the nullable reference types feature, and we discussed nearly every way in which nullable allows configuration. However, while the analogy works quite well, we think there are two major differences from the nullable feature: * We expect the amount of code in the average project that will need to change to adopt new memory safety rules is quite low. The majority of projects shouldn't even be impacted. This is extremely different to nullable, where every single line of executable code, and most lines of definitions, were impacted. We expect that this should reduce the number of configuration options needed. * Ignoring nullable warnings can cause bugs, and potentially even vulnerabilities in the form of DOS attacks, but memory safety issues are much more severe. Reading an unintentional `null` can crash an app, but reading uninitialized or unmanaged memory incorrectly can expose the app's data or users to attack. We therefore feel much more strongly about getting memory safety properly annotated across the ecosystem. Given both the expected smaller blast radius and the higher importance of addressing issues, we believe that we want to start with stricter versions of the rules. We will therefore start with memory safety diagnostics being errors, with no dedicated suppression mechanism. Wrapping new errors in `unsafe` with todo comments attached will be the main form of "suppress for now and come back later" available to authors. This does pose some adoption problems for users who depend on source generators, as there will be no special affordance given to source generators. This can mean that if a generator is not updated, and is emitting code that has new memory safety errors in it, the consumer of the generated code will be blocked from enabling the new rules. While this is harsh, we expect that the majority of the generators that use `unsafe` code are either first-party, or are from highly-engaged community members who will be quick to jump on the new rules. If these rules prove too onerous and block widespread adoption of the feature, we can revisit, but we want to start at the more secure end of the spectrum. We also don't expect that source generators need to be concerned by "unexpected" `unsafe`. Either the generator was written with the intent of using `unsafe` code in mind, or it is an error on the consumption side to provide the generator an `unsafe` member to interact with if it wasn't expecting it. Next up, we will be discussing how members are marked `unsafe`, and what `unsafe` on a type means. #### Conclusion The core principle of the `unsafe` evolution feature is accepted. `unsafe` diagnostics will be errors by default, and no affordance for source generated code will be given. ================================================ FILE: meetings/2025/LDM-2025-11-12.md ================================================ # C# Language Design Meeting for November 12th, 2025 ## Agenda - [Unsafe evolution](#unsafe-evolution) ## Quote of the Day - "Did you just say Romulan?" "No, I said cromulent." ## Discussion ### Unsafe evolution Champion issue: https://github.com/dotnet/csharplang/issues/9704 Specification: https://github.com/dotnet/csharplang/blob/4c6bee6ceaa48dc64a42f25d6a50155e1def2930/proposals/unsafe-evolution.md Related: https://github.com/dotnet/designs/blob/2b9e1b311d16ea142b4047d6eddeef15979ba6de/proposed/caller-unsafe.md Today, we [continued](./LDM-2025-11-05.md#unsafe-evolution) talking about the future of `unsafe` in C#. Our main focus today was on how members are declared as requiring an `unsafe` context: do we repurpose the `unsafe` keyword on members to mean this? Or do we do something new, such as a new keyword or having an attribute to indicate this? After some discussion, we do believe that `unsafe` conveys what we want here. Much like nullable reference types, the future we want to get to isn't with the `unsafe` keyword as this odd vestigial thing, we want the feature to feel unified. Given that we feel pretty confident that we want to repurpose `unsafe` on members, even if that means increasing the scope of the breaking change in the language. Next, we considered the broader application of `unsafe` on types. The new rules are very explicit that types cannot be considered `unsafe` by nature: it is only specific types of memory accesses that are `unsafe`. We therefore have a few options for what to do: 1. Automatically mark all members inside an `unsafe` type as `unsafe`. This is the current proposal. 2. Keep the historical meaning of `unsafe`: the body of the type is in an `unsafe` context, but members will still need a separate `unsafe` modifier to be considered `unsafe` to call. 3. Disallow `unsafe` on types. 4. Allow `unsafe` as a modifier on types, but remove all meaning. Produce a warning that it is vestigial. All of these are fairly large changes in one way or another; a simple search across GitHub indicates at least 150K results for `unsafe class` or `unsafe struct`, excluding forked projects. A decent number of spot checks of these have shown that they're not actually `unsafe`; the developer simply marked the type as `unsafe` even though no pointers or `unsafe` memory accesses occurred in the type. There are plenty of real `unsafe` spots, though, so unless we were to adopt option 2, it would be a large breaking change. Looking more at options 1 and 2 though, we're not actually convinced that these truly help bring down the magnitude of the breaking change. In either case, developers really do still need to adjust their members. Option 1 will bleed `unsafe` out into the rest of the code, and both options 1 and 2 make the scope of the `unsafe` blocks far too broad. In both cases, we don't actually _want_ users to put `unsafe` on their types. It implies that a type can be `unsafe`, which is not how the feature works. We are then left with options 3 and 4, where 4 is just 3 but without a hard error. There's definitely appetite for fully prohibiting `unsafe` on types altogether, but we're hesitant to fully commit at this time. We'll proceed with option 4 for now, and come back to the question of 3 vs 4 in a few weeks after we've had some time to think more about it. Another area we considered is whether or not `unsafe` as a modifier on a member should make the entire body of that member an `unsafe` context. Rust did this originally, and has since moved to requiring an explicit `unsafe` block, even for a member marked as `unsafe`. This helps call out the specific unsafety, rather than saying "this entire 100 line method is unsafe, good luck finding the actual bit of concern". We don't have conclusions on this today, but will add it to our list of questions to resolve. #### Conclusion We will use `unsafe` on members as the way to denote that calling a member must be performed in an `unsafe` context. `unsafe` on a type will have no meaning, and we will revisit the question of whether to outright disallow it, or just add a warning. ================================================ FILE: meetings/2025/LDM-2025-12-10.md ================================================ # C# Language Design Meeting for December 10th, 2025 ## Agenda - [Collection expression arguments](#collection-expression-arguments) ## Quote of the Day - "It's almost like it's been a few weeks and we're a bit rusty." "We're not Rusty, we're C#y" ## Discussion ### Collection expression arguments Champion issue: https://github.com/dotnet/csharplang/issues/8887 Spec: https://github.com/dotnet/csharplang/blob/34a80fa9cfef3bfe3bcb66c412928c4d6bd735b4/proposals/collection-expression-arguments.md Diff: https://github.com/dotnet/csharplang/commit/2afe544f5facf319b3d76a044bf58d07349ce5b9 Today, we wanted to validate an assumption made during implementation, that the specific constructors we expose for `IList` and `ICollection` are constructors from `List`. After some discussion, we think that this is ok to do, and will extend to `IDictionary` as well for dictionary expressions. The main thing we want to update is that the spec itself won't care about the name of the parameter. If some version of the BCL decides to name `capacity` differently, then it is free to do so; users of that BCL would have to specify that different name instead of `capacity`. #### Conclusion The candidate signatures for `IList` and `ICollection` bind to the constructors with the corresponding signatures in `List` when constructing the value. The spec will not say the names of these parameters, it will only specify the parameter type. ================================================ FILE: meetings/2025/LDM-2025-12-17.md ================================================ # C# Language Design Meeting for December 17th, 2025 ## Agenda - [Collection expression arguments parsing question](#collection-expression-arguments-parsing-question) - [Null-conditionals evaluating to a pointer type](#null-conditionals-evaluating-to-a-pointer-type) - [Triage](#triage) - [Top-Level Members](#top-level-members) - [Using local declaration improvements](#using-local-declaration-improvements) ## Quote of the Day - "We can't do advent of triage? Just one more?" ## Discussion ### Collection expression arguments parsing question Champion issue: https://github.com/dotnet/csharplang/issues/8887 Spec: https://github.com/dotnet/csharplang/blob/01e436bf45790b7118c116100c27a8172148c58d/proposals/collection-expression-arguments.md#finalizing-an-open-concern-from-httpsgithubcomdotnetcsharplangblobmainmeetings2025ldm-2025-03-17mdconclusion We started today by revisiting an earlier temporary decision; when we were considering whether `with` would be a breaking change at the top level for collection expressions, we initially decided that we would take the breaking change for preview, but since we hadn't yet finalized our syntax and were planning on previewing it for .NET 10, we didn't want to potentially break users for syntax we weren't certain of. Since then, we've had our debates, settled on `with` as the syntax, and now we want to revisit the breaking change downlevel. Our default approach to parsing changes is to always parse with new language features, and issue errors when users don't have the appropriate language version enabled. Searching through GitHub we are unable to find examples of code that will actually break in this, so we are comfortable following our usual approach; the .NET 11 SDK/C# 15 compiler will always parse `with` as collection expression arguments, regardless of the language version of the project. We also wanted to talk about the formal definition of the feature, and in particular, how the grammar rules actually will work. Do we want to have a rule where the `with` has to be the top-level expression in the collection expression element, or do we want a rule where, if we encounter `with(` to start a collection expression element, that element is a `with` element, regardless of following content? Concretely, the question is how do we want something like this to parse: `[with() + with()]`. With the top-level rule, this would just be a binary operator `+` with method calls to methods named `with` in either arm. With the "first token" rule, it would be a `with` element, followed by a binary operator `+` whose left operand is missing, and the right operand is a method invocation named `with`. After some debate, we decided that it is simpler to go with the "first token" rule, and matches our precedent with `spread` elements, where a `..` always starts a spread, never a range. This makes the parsing simple, and parentheses can easily disambiguate for scenarios where the user really wants to call a method, just like it works for range. #### Conclusion We will not take language version into account when parsing, and will always parse `with` elements as `with` elements. If a collection expression element starts with `with(`, that means it is a `with` element, following the `..` spread precedent. ### Null-conditionals evaluating to a pointer type Issue: https://github.com/dotnet/roslyn/issues/7502 Spec: https://github.com/dotnet/csharpstandard/blob/41694caebb0fb759b75ef66b9d11d37006aab000/standard/expressions.md#12813-null-conditional-element-access Next up, we discussed a potential compiler bug/specification bug. This has been open for a long time, but was recently resurfaced and we wanted to investigate to clarify our definitions. The question hinges on what the definition of what a non-nullable value type is. If a pointer is a non-nullable value type, then the rules as written state that the type would have to be `Nullable`, which is indeed illegal. But if pointers are not non-nullable value types, then the cases would fall through to the last bullet of the spec and it should just work, as the specified transformation would be legal for a pointer. Certainly on the surface, pointer types are nullable: `int* i = null;` is legal, as is comparing them with `null`. Given that, and the definitions in [§8.2.1](https://github.com/dotnet/csharpstandard/blob/41694caebb0fb759b75ef66b9d11d37006aab000/standard/types.md#821-general), we are comfortable treating this as a compiler bug, and will fix it to work. We will also leave the decision on clarifying the specification further to the ECMA 334 committee. #### Conclusion Null-conditional expressions that produce a pointer type not being allowed is a compiler bug and will be fixed as such. ### Triage Finally today, we did some triage on newly-championed issues over the past few months. #### Top-Level Members Champion issue: https://github.com/dotnet/csharplang/issues/9803 Spec: https://github.com/dotnet/csharplang/blob/e878e7860d9b6420112fd7b99b910a79c7103132/proposals/top-level-members.md There are some strong feelings on this one in the LDM; nothing that we think would hold us from doing a deep design dive on it, but by no means is there agreement on the feature at this point. We'll put it into the working set to have that debate. #### Using local declaration improvements Champion issue: https://github.com/dotnet/csharplang/issues/8606 Spec: https://github.com/dotnet/csharplang/blob/afad49721a7b9923efbbc36ae6c455d85b994543/proposals/using-with-a-discard.md Finally, we looked at a proposal for creating `using` local declarations without actually having to declare a local, marrying the implicit scope benefits of `using` locals with the lack of variable pollution available from `using` statements. We are interested, but want to rename the proposal from the current state to better reflect that goal, rather than a specific implementation of achieving that goal. ================================================ FILE: meetings/2025/README.md ================================================ ## C# Language Design Notes for 2025 ### Wed December 17, 2025 [C# Language Design Meeting for December 17th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-12-17.md) - Collection expression arguments parsing question - Null-conditionals evaluating to a pointer type - Triage - Top-Level Members - Using local declaration improvements ### Wed Dec 10, 2025 [C# Language Design Meeting for December 10th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-12-10.md) - Collection expression arguments ### Wed Nov 12, 2025 [C# Language Design Meeting for November 12th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-11-12.md) - Unsafe evolution ### Wed Nov 5, 2025 [C# Language Design Meeting for November 5th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-11-05.md) - Unsafe evolution ### Wed Oct 29, 2025 [C# Language Design Meeting for October 29th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-10-29.md) - Unsafe evolution ### Mon Oct 13, 2025 [C# Language Design Meeting for October 13th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-10-13.md) - Collection expression safe-context bug fix - Immediately-enumerated collection expressions ### Wed Oct 1, 2025 [C# Language Design Meeting for October 1st, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-10-01.md) - Closed enums - Closed Hierarchies ### Mon Sep 29, 2025 [C# Language Design Meeting for September 29th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-09-29.md) - Union Syntax Thunderdome Part 2 - Enums vs Unions ### Wed Sep 24, 2025 [C# Language Design Meeting for September 24th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-09-24.md) - Union Syntax Thunderdome Part 1 ### Wed Sep 17, 2025 [C# Language Design Meeting for September 17th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-09-17.md) - Unsafe evolution ### Wed Sep 10, 2025 [C# Language Design Meeting for September 10th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-09-10.md) - Unions overview ### Wed Aug 27, 2025 [C# Language Design Meeting for August 27th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-08-27.md) - Type inference in patterns - Type value conversion - Union syntax ### Wed Aug 20, 2025 [C# Language Design Meeting for August 20th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-08-20.md) - Union interfaces - Target-type inference improvements ### Mon Aug 18, 2025 [C# Language Design Meeting for August 18th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-08-18.md) - C# 15 Kickoff and Themes - Unions - Extensions - Dictionary expressions - Type inference - Continuing the null monad - Object initialization - Top-level methods - Caller type name - Switch statement/expression improvements ### Wed Aug 13, 2025 [C# Language Design Meeting for August 13th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-08-13.md) - Unions ### Wed Jul 30, 2025 [C# Language Design Meeting for July 30th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-07-30.md) - '/main' and top-level statements - Union proposals overview - Add type parameter restriction to closed hierarchies - Standard unions - Union interfaces - Custom unions - Non-boxing access pattern ### Mon Jun 30, 2025 [C# Language Design Meeting for June 30th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-30.md) - Type parameter inference from constraints - Target-typed static member lookup ### Wed Jun 25, 2025 [C# Language Design Meeting for June 25th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-25.md) - Unions - Stand-alone issues - Mutually exclusive issues ### Mon Jun 23, 2025 [C# Language Design Meeting for June 23rd, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-23.md) - Extensions - Use of extension operators in LINQ expression trees - `ref` encoding and conflict checks - Extern support - Lookup ### Wed Jun 18, 2025 [C# Language Design Meeting for June 18th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-18.md) - Iterators in lambdas - Extensions ### Wed Jun 11, 2025 [C# Language Design Meeting for June 11th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-11.md) - Extensions - Dynamic resolution of operator `true`/`false` - Extension operators in LINQ expression trees - Built-in operator protection rules - Extension module initializers - Extension methods as entry points - Langversion behavior for new extensions - `nameof` ### Mon Jun 9, 2025 [C# Language Design Meeting for June 9th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-09.md) - Extensions - crefs ### Wed Jun 4, 2025 [C# Language Design Meeting for June 4th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-04.md) - Extensions - Should extension operators on nullable of extended type be disallowed - Applicability of bitwise operators during evaluation of user-defined conditional logical operators - Extension user-defined conditional logical operators - Extension compound assignment operators - Delegate returning properties - Extension declaration validation - Cref references ### Wed May 28, 2025 [C# Language Design Meeting for May 28th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-28.md) - Nominal type unions ### Mon May 12, 2025 [C# Language Design Meeting for May 12th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-12.md) - Collection expression arguments - Empty argument lists - Constructor binding behavior - Syntax - Dictionary expressions ### Wed May 7, 2025 [C# Language Design Meeting for May 7th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-07.md) - Collection Expression Arguments - Interface target type - Constructor binding behavior - Syntax ### Mon May 5, 2025 [C# Language Design Meeting for May 5th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-05.md) - Extensions - XML Docs - Participation in pattern-based constructs - Skeleton type metadata - Single-phase consequences ### Wed, Apr 23, 2025 [C# Language Design Meeting for April 23rd, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md) - Extensions - Extension operators - Overload resolution priority - Dictionary Expressions ### Wed Apr 16, 2025 [C# Language Design Meeting for April 16th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-16.md) - Extensions ### Mon Apr 14, 2025 [C# Language Design Meeting for April 14th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-14.md) - Dictionary expressions - Collection expression arguments ### Wed Apr 9, 2025 [C# Language Design Meeting for April 9th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-09.md) - Dictionary expressions - Collection expression arguments ### Mon Apr 7, 2025 [C# Language Design Meeting for April 7th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-07.md) - Breaking change discussion: making partial members in interfaces virtual and/or public - Interpolated string handler argument values ### April 2, 2025 [C# Language Design Meeting for April 2nd, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-02.md) - User Defined Compound Assignment Operators - Should readonly modifier be allowed in structures? - Should shadowing be allowed - Should we have any consistency enforcement between declared += and + operators? - Readonly setters - Expansions ### Mon Mar 24, 2025 [C# Language Design Meeting for March 24th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-24.md) - Dictionary Expressions - Support dictionary types as `params` type - Type inference for `KeyValuePair` collections - Overload resolution for `KeyValuePair` collections - Extensions - Signature conflict rules - `extension` vs `extensions` ### Wed Mar 19, 2025 [C# Language Design Meeting for March 19th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-19.md) - Readonly setters on non-variables ### Mon Mar 17, 2025 [C# Language Design Meeting for March 17th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md) - Collection expression arguments - `with` breaking change - Collection expression arguments and conversions - Dictionary expressions - Extensions - Accessibility - Static factory scenarios ### Mon Mar 12, 2025 [C# Language Design Meeting for March 12th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-12.md) - Optional and named parameters in expression trees - Collection builder method parameter order - Ignored directives ### Mon Mar 10, 2025 [C# Language Design Meeting for March 10th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-10.md) - Extensions - Property method calling - Scoping and Shadowing - Type parameter inferrability - Accessibility ### Wed Mar 5, 2025 [C# Language Design Meeting for March 5th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-05.md) - Dictionary expressions - Target-typed static member lookup ### Mon Mar 3, 2025 [C# Language Design Meeting for March 3rd, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-03.md) - Extensions ### Wed Feb 26, 2025 [C# Language Design Meeting for February 26th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-02-26.md) - Extensions ### Mon Feb 24, 2025 [C# Language Design Meeting for February 24th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-02-24.md) - Extensions - Interpolated string handler method names ### Wed Feb 19, 2025 [C# Language Design Meeting for February 19th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-02-19.md) - Extensions ### Wed Feb 12, 2025 [C# Language Design Meeting for February 12th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-02-12.md) - User-defined instance-based operators - Left-right join in query expressions ### Wed Jan 22, 2025 [C# Language Design Meeting for January 22nd, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-22.md) - Partial events and constructors - Collection expression arguments - Extensions disambiguation syntax ### Wed Jan 15, 2025 [C# Language Design Meeting for January 15th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-15.md) - `fieldof` - Simple lambda parameters - Interpolated string handler method names ### Mon Jan 13th, 2025 [C# Language Design Meeting for January 13th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-13.md) - Discriminated Unions ### Mon Jan 6, 2025 [C# Language Design Meeting for January 6th, 2025](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-06.md) - Ignoring `ref struct`s in `Expression`s - Extensions ================================================ FILE: meetings/2026/LDM-2026-01-12.md ================================================ # C# Language Design Meeting for January 12th, 2026 ## Agenda - [Triage](#triage) - [Relaxed ordering for `partial` and `ref` modifiers](#relaxed-ordering-for-partial-and-ref-modifiers) - [Deconstruction in lambda parameters](#deconstruction-in-lambda-parameters) - [Unsigned sizeof](#unsigned-sizeof) - [Labeled `break` and `continue` Statements](#labeled-break-and-continue-statements) - [Extra accessor in property override](#extra-accessor-in-property-override) - [Immediately Enumerated Collection Expressions](#immediately-enumerated-collection-expressions) - [Allow arrays as CollectionBuilder Create parameter type](#allow-arrays-as-collectionbuilder-create-parameter-type) ## Quote(s) of the Day - "Wouldn't an imaginary array return a length of `i`?" - "I think I just saw a thread abort in real time as you were talking." ## Discussion ### Triage #### Relaxed ordering for `partial` and `ref` modifiers Champion issue: https://github.com/dotnet/csharplang/issues/9804 Spec: https://github.com/dotnet/csharplang/blob/c4ec6fb60c2e174b1abb6c019f22bb15b9b13f6c/proposals/relaxed-partial-ref-ordering.md We are in favor of this. Historical reasons for this restriction were both implementation driven and a more conservative approach to breaking changes. We want to move forward, into the working set. #### Deconstruction in lambda parameters Champion issue: https://github.com/dotnet/csharplang/issues/9848 Spec: https://github.com/dotnet/csharplang/blob/c4ec6fb60c2e174b1abb6c019f22bb15b9b13f6c/proposals/deconstruction-in-lambda-parameters.md More skepticism on this one. There are some concerns about the readability of the syntax, particularly in single-parameter cases, and an overall question as to whether this piece of syntactical sugar is worth it, particularly since several members think that it would never be able to apply to non-lambdas (given that parameter names matter more there). We'll put it in the working set to take a closer look though, and make a more comprehensive determination. #### Unsigned sizeof Champion issue: https://github.com/dotnet/csharplang/issues/9633 Spec: https://github.com/dotnet/csharplang/blob/c4ec6fb60c2e174b1abb6c019f22bb15b9b13f6c/proposals/unsigned-sizeof.md We think that this proposal is probably too much work for the benefits that it would bring; adding any new conversion comes with considering breaking changes, and we're not sure it's worth that level of effort for just `sizeof`. However, we do think that there could be an interesting expansion that would potentially pay for itself in a broader "non-negative integer" feature, that could apply not just to `sizeof`, but also collection count/length. We already special case these a bit in pattern matching, where the pattern `arr switch { [_, ..] or { Length: 0 } => "" }` is considered exhaustive, even though `Length: < 0` is not matched on. If we can square that with `sizeof`, and expand it to the broader language, then we may be more amenable to that. For now, though, this will go into the backlog. #### Labeled `break` and `continue` Statements Champion issue: https://github.com/dotnet/csharplang/issues/9875 Spec: https://github.com/dotnet/csharplang/blob/c4ec6fb60c2e174b1abb6c019f22bb15b9b13f6c/proposals/labeled-break-continue.md The LDM is unsure overall here, but wants to delve deeper in a full session to hash it out. We'll put it in the working set for now. #### Extra accessor in property override Champion issue: https://github.com/dotnet/csharplang/issues/9921 Spec: https://github.com/dotnet/csharplang/pull/9920 We like it. Into the working set. #### Immediately Enumerated Collection Expressions Champion issue: https://github.com/dotnet/csharplang/issues/9754 Spec: https://github.com/dotnet/csharplang/blob/c4ec6fb60c2e174b1abb6c019f22bb15b9b13f6c/proposals/immediately-enumerated-collection-expressions.md This was delayed so we could assess feedback from collection expressions on where natural types are desired. The areas that we've heard are addressed by this proposal, so we're ready to move forward and we'll put it into the working set. This proposal also subsumes https://github.com/dotnet/csharplang/issues/9739, so that will be closed as a duplicate. #### Allow arrays as CollectionBuilder Create parameter type Champion issue: https://github.com/dotnet/csharplang/issues/9923 Spec: https://github.com/dotnet/csharplang/pull/9922 One big question is how this behaves in the face of spread cases. The goal of the proposal is to avoid extra copies, but that copy seems like it may be inevitable in such cases, since the compiler can't know how big of an array to allocate the likelihood that it will guess correctly is low. After some discussion, we've settled on a different goal: make `ReadOnlyMemory` work with collection expressions. We'll investigate approaches for doing so, and if other types can come along for the ride, all the better, but we won't have it as the overriding goal unless we can see good evidence that the type is widely used and supported. This issue will go on the backlog until that happens. ================================================ FILE: meetings/2026/LDM-2026-01-21.md ================================================ # C# Language Design Meeting for January 21st, 2026 ## Agenda - [Unsafe evolution](#unsafe-evolution) ## Quote of the Day - "To be more meaningfully unsafe" ## Discussion ### Unsafe evolution Champion issue: https://github.com/dotnet/csharplang/issues/9704 Spec: https://github.com/dotnet/csharplang/blob/37a912407b377f8a3fd5f7396549977e1207e759/proposals/unsafe-evolution.md Proposal: https://github.com/dotnet/csharplang/blob/37a912407b377f8a3fd5f7396549977e1207e759/meetings/working-groups/unsafe-evolution/unsafe-alternative-syntax.md Today we took another look at the syntax around the `unsafe` proposal. So far, our discussions have leaned heavily into the breaking changes aspect, taking a fairly maximally-breaking approach. However, we wanted to fully explore our options for non-breaking versions of the change as well. To help guide discussion, there are two types of break we talk about in this area: * A _syntax_ breaking change. This is taking existing syntax, and changing the meaning of it. This is the type of change where, given a method definition, a reader cannot know what it means without knowing the flags passed to the compiler. Namely, does `public unsafe void M() {}` mean that the caller must be in an `unsafe` context, or not? * An ecosystem breaking change. This is where, upon upgrading to a new version of .NET and turning on the feature, what is unsafe and what is not changes interpretation, because the user is consuming a BCL and set of dependencies that have added safety information to their APIs. This break is not under debate today; it will happen, but it can happen independently of the syntax break. There is a tension in our design between wanting a simple set of rules, without needing to know about new attributes or keywords, and ensuring that users can actually adopt the changes and drive a safer .NET ecosystem. We feel that, given a brand new language, we would likely have `unsafe` on the method body mean the caller must be in an `unsafe` context, which would then likely drive other decisions such as `unsafe` on the method signature not automatically marking the body as an `unsafe` context. However, this is truly the largest syntax breaking change we would have ever done in C#. Even though we hope it will only hit a small segment of our users, it will likely hit those users heavily, and those are the users we want to adopt the change to ensure safety for the rest of the ecosystem. This was a shorter session, so we didn't arrive to any kind of final conclusion today. The LDM is generally leaning in favor of trying to avoid the syntax break, but how exactly (i.e., attribute vs a new keyword) is not something we were able to drill into today. Further, several members of the LDM are still interested in the original proposal, syntax break and all. Therefore, we want to continue exploring this topic, in particular drilling into the attribute vs new keyword discussion, and we'll come back to this next week. ================================================ FILE: meetings/2026/LDM-2026-01-26.md ================================================ # C# Language Design Meeting for January 26th, 2026 ## Agenda - [Alternative syntax for caller-unsafe](#alternative-syntax-for-caller-unsafe) ## Quote(s) of the Day - "[redacted], you are the most punctual person ever. You joined exactly when the clock hit [meeting start time]. Are you using some kind of auto-meeting joiner?" "No, I was just watching the clock." - "ų̷n̷s̷a̷f̷e̷" - "That wasn't a particularly bad dad joke, was it? It wasn't quote of the day material." "Well that was" ## Discussion ### Alternative syntax for caller-unsafe Champion issue: https://github.com/dotnet/csharplang/issues/9704 Spec: https://github.com/dotnet/csharplang/blob/61f06216967ed264a8f83c71bff482f3eb6ac113/proposals/unsafe-evolution.md Working group notes: https://github.com/dotnet/csharplang/blob/61f06216967ed264a8f83c71bff482f3eb6ac113/meetings/working-groups/unsafe-evolution/unsafe-alternative-syntax.md Continuing from [last week's discussion](./LDM-2026-01-21.md), the LDM had a strong lean toward not repurposing the existing `unsafe` keyword for caller-unsafe semantics. Today we explored the remaining design space: should we use an attribute like `[RequiresUnsafe]`, or a new keyword other than `unsafe`? Several arguments were raised in favor of an attribute approach. An attribute is a safer incremental choice; we could ship an attribute, learn from user feedback during the .NET 11 preview cycle, and add a keyword later if needed. We cannot remove a keyword once introduced. Attributes also provide more flexibility for distinguishing between existing members that became caller-unsafe (where consumers may need an escape hatch during migration) versus new members that are caller-unsafe from the start (where there's no conceptual reason to allow opting out). On the other hand, strong arguments were made for a dedicated keyword. A keyword conveys the seriousness with which C# is treating memory safety and makes it a first-class language concept. If caller-unsafe were just an attribute, it would be effectively the same as shipping an analyzer; it wouldn't feel like part of the language. The signal value matters: as the industry increasingly demands memory-safe languages, C# needs to demonstrate that safety is built into the language itself, not bolted on as an afterthought. The discussion also touched on audit ergonomics. The guidance that the libraries team is working on is to keep `unsafe` blocks as small as possible, drawing the eye to precisely where unsafety occurs. This has implications for the syntax design: having both `unsafe` at the method level and `[RequiresUnsafe]` on the same member is effectively incorrect, you probably never want both. When `unsafe` is at the method level, it suppresses all warnings internally, making it impossible to audit which specific operations are unsafe. This means that when users enable the feature, they cannot easily audit their existing code; they would need to go through every usage of `unsafe` line by line. Or, we have to make the breaking change even larger, and change `unsafe` on a member to not create an `unsafe` block inside the member at all. This is currently an active question in the proposal, but if we proceed with `unsafe` as the keyword, then we would need to answer that question. We then brainstormed extensively on possible syntaxes: 1. `[RequiresUnsafe]` 2. `[CallerUnsafe]` 3. `[Requires("unsafe")]`, `[Requires("experimental")]` 4. `callerunsafe` 5. `requiresunsafe` 6. `unsafe(caller)` 7. `requires(unsafe)` 8. `requires unsafe` 9. `unsafe required` 10. `#require unsafe` 11. `requires-unsafe` 12. `unsafe-caller` 13. `modifier(unsafe)` 14. `unprotected` 15. `dangerous` 16. `access unsafe` 17. `[RequiresUnsafeContext]` 18. `propagate unsafe` 19. `unsafe(propagate)` 20. `[UnsafeOnly]` 21. `leakunsafe` 22. `exportunsafe` After an initial round of voting, the options were narrowed down to `[RequiresUnsafe]`, `[CallerUnsafe]`, `requiresunsafe`, `unsafe(caller)`, `requires unsafe`, and `unsafe required`. A second round of voting further narrowed the field to `[RequiresUnsafe]`, `[CallerUnsafe]`, `[UnsafeOnly]`, `requires unsafe`, `unsafe(caller)`, and `requiresunsafe`. None of the keyword options were liked by a majority of the LDM, with most preferring the attribute approach, with `[RequiresUnsafe]` being the preferred option. We then compared this against the original proposal: repurposing `unsafe` itself. `[RequiresUnsafe]` is preferred over repurposing the `unsafe` keyword, so we will proceed with the alternative syntax proposal. #### Conclusion We will use an attribute, `[RequiresUnsafe]`, for communicating when the caller of a method or user of a field/property must be in an `unsafe` context. ================================================ FILE: meetings/2026/LDM-2026-02-02.md ================================================ # C# Language Design Meeting for February 2nd, 2026 ## Agenda - [Extension indexers](#extension-indexers) - [Open question: params on indexer setters](#open-question-params-on-indexer-setters) - [Open question: should extension Length/Count properties make a type countable?](#open-question-should-extension-lengthcount-properties-make-a-type-countable) - [Open question: extension indexer access ordering](#open-question-extension-indexer-access-ordering) ## Quote of the Day - "Does Count count?" "Only after a Lengthy discussion" ## Discussion ### Extension indexers Champion issue: https://github.com/dotnet/csharplang/issues/9856 Spec: https://github.com/dotnet/csharplang/blob/7a58785d96afc903219cb33f45360744211c3c4c/proposals/extension-indexers.md The LDM reviewed the extension indexers proposal and had no comments on the overview. #### Open question: params on indexer setters Open question: https://github.com/dotnet/csharplang/blob/7a58785d96afc903219cb33f45360744211c3c4c/proposals/extension-indexers.md#dealing-with-params When an extension indexer has a `params` parameter, the setter's implementation method ends up with `params` in a non-final position (the value parameter comes after it). The question was whether to disallow this scenario or simply emit the `params` attribute as-is. This isn't actually an extension-specific problem: regular instance indexers with `params` already emit setters with the `params` attribute in the middle. The difference is that for regular indexers, users can't directly invoke the accessor methods, so they never observed this signature (though there is a possibility it can be observed through C# consuming a VB non-default indexed property). With extension indexers, the implementation methods are invokable via the disambiguation syntax, so it can be more directly observed. Both the C# and VB compilers are designed to ignore `params` attributes on any parameter other than the last. While we should verify that tooling (like IDE signature help) doesn't display `params` in confusing locations, the consensus was that we should simply let things fall out naturally without special-casing this scenario. ##### Conclusion Emit the `params` attribute on the setter implementation method even though it appears on a non-final parameter. Compilers will ignore it in that position, maintaining consistency with how regular indexers already behave. We will ensure that the IDE behaves well in these scenarios. #### Open question: should extension Length/Count properties make a type countable? Open question: https://github.com/dotnet/csharplang/blob/7a58785d96afc903219cb33f45360744211c3c4c/proposals/extension-indexers.md#should-extension-lengthcount-properties-make-a-type-countable In C# 14, extension properties were explicitly excluded from contributing to implicit indexers (the pattern-based indexing that uses `Length`/`Count` with an `int` indexer or `Slice` method). This was because extension methods also didn't contribute to that pattern. However, now that we're adding extension indexers that can directly take `System.Index` or `System.Range`, a question arises: if you define an extension indexer taking `Index` and an extension `Length` property, should list patterns work on that type? ```cs class C { } static class E { extension(C c) { object this[System.Index] => ...; C this[System.Range] => ...; int Length => ...; } } // Should this work? if (c is [.., var y]) { } ``` There was strong sentiment in the LDM for a "maximalist" approach: extension members should participate in language patterns wherever possible. This mostly matches our past approaches, and how we've loosened similar restrictions on other language features recently. We considered whether to require that all members contributing to a pattern come from the "same origin" (same extension block or all from the instance type). The rationale was to prevent confusing situations where unrelated extensions from different libraries combine unexpectedly. However, counter-arguments noted that this would force users to redeclare members that already exist (like `Length`), potentially causing ambiguity errors when both namespaces are imported. Those extensions may not even be ever usable, if the issue was that the underlying type did not provide a `Length`, for example, then an extension being forced to provide a `Length` when it can never be invoked as an extension would be odd. The LDM concluded that enforcing same-origin rules would add more complexity than it prevents. If the type already has a `Length` property that wasn't intended to participate in patterns, that's unfortunate, but preventing extensions from filling in the rest of the pattern only works if the member happens to be named `Length`; otherwise an extension could add it anyway. Analyzer rules could flag suspicious combinations if this becomes a real problem in practice. A follow-up question was whether extension members should also contribute to *implicit* indexers (the fallback that uses `Length` + `int` indexer instead of direct `Index`/`Range` indexers). The same maximalist reasoning applied: if you add an extension `int` indexer, you shouldn't need to also add an `Index` indexer just to use `c[^1]`. ##### Conclusion We will allow permissive extension applicability. Extension members can contribute to pattern-based indexer matching from multiple sources. #### Open question: extension indexer access ordering Open question: https://github.com/dotnet/csharplang/blob/7a58785d96afc903219cb33f45360744211c3c4c/proposals/extension-indexers.md#confirm-whether-extension-indexer-access-comes-before-or-after-implicit-indexers The final open question concerned lookup ordering. When binding an element access, the current order is: 1. Instance indexer (`Index`/`Range` signature) 2. Instance implicit indexer (`Length` + `int`/`Slice`) 3. Extension indexer The previous decision to allow extensions to contribute to implicit indexers opened a can of worms about priority. What happens when an inner extension scope defines `Length`, an outer scope defines an `int` indexer, and an even outer scope defines an `Index` indexer? Different orderings lead to different behavior. Several approaches were discussed: 1. Process instance members fully (both direct and implicit indexers) before any extensions 2. At each extension scope, try direct indexers then implicit indexers, then move to the next scope 3. Prioritize direct `Index`/`Range` indexers everywhere, then fall back to implicit patterns There was agreement that instance members should be fully exhausted before looking at extensions, but the exact ordering when multiple extension scopes are involved remained unclear. We think this requires more investigation with concrete examples to understand all the consequences. Depending on how we do searches, and for what piece of the language, there could be very odd priority inversions and member selections that are not obvious to readers. For example, if there are 3 scopes in order from `Length`, `Index`-based indexer, and `int`-based indexer, what is the expectation on what is being called for `underlying[^1]`? ##### Conclusion We need to revisit this question with a proposal that works through the combinations systematically. The extensions working group will investigate and bring back a more concrete recommendation. ================================================ FILE: meetings/2026/LDM-2026-02-04.md ================================================ # C# Language Design Meeting for February 4th, 2026 ## Agenda - [Discriminated unions patterns](#discriminated-unions-patterns) - [Null ambiguity in constructor selection](#null-ambiguity-in-constructor-selection) - [Marking unions with an attribute instead of IUnion interface](#marking-unions-with-an-attribute-instead-of-iunion-interface) - [Factory method support](#factory-method-support) - [Union member providers](#union-member-providers) ## Quote of the Day - "The distant past. You know, late summer or early fall." ## Discussion ### Discriminated unions patterns Champion issue: https://github.com/dotnet/csharplang/issues/9662 Spec: https://github.com/dotnet/csharplang/blob/d614288f7dab369f763520ba48b12623c34d8542/proposals/unions.md#union-declarations Working group notes: https://github.com/dotnet/csharplang/blob/d614288f7dab369f763520ba48b12623c34d8542/meetings/working-groups/discriminated-unions/union-patterns-update.md Today, we went over a few open questions in unions. #### Null ambiguity in constructor selection When a union has multiple reference type case types, passing `null` creates ambiguity about which constructor to use. For example, with `Pet(Dog)` and `Pet(Bird?)` constructors, `null` could apply to either. Currently, the compiler picks an arbitrary applicable constructor when there is ambiguity, based on the assumption that well-formed unions have constructors that behave identically for the same input. However, this can lead to surprising behavior: if the compiler picks the `Dog` constructor but `Bird?` is the one intended to accept `null`, a nullability warning will be issued even though the union author intended `null` to be valid. We discussed several approaches: 1. Using standard overload resolution betterness rules to select among applicable constructors 2. Requiring the user to be explicit when multiple cases could apply (warning or error on ambiguity) 3. Ensuring nullability annotations factor into the decision We liked options 1 and 2 here. Option 3 is not how the compiler works today, and doesn't solve the scenario in nullable disabled locations. There are also scenarios where null isn't involved, such as a union of two interfaces and a type that implements both. We also brought up operator conversions, as union conversions are very similar to user-defined operators. We like that analogy, and want to apply similar rules where applicable. ##### Conclusion The working group will consider requiring explicit disambiguation when ambiguity cannot be resolved through standard betterness rules, similar to how conversion operators are resolved. #### Marking unions with an attribute instead of IUnion interface The current design uses the `IUnion` interface to indicate that a type should be treated as a union. This causes problems: - A type cannot implement `IUnion` without becoming a union (it might just want runtime participation) - Derived types of a union type automatically become unions themselves, which may not be intended and raises awkward questions about what their case types should be - It conflates the runtime interface (useful for generic code that handles unions) with the compiler recognition mechanism The proposal is to use a `[Union]` attribute instead, with the compiler looking for members by shape rather than through the interface. The `IUnion` and `IUnion` interfaces would still exist for runtime scenarios (such as generic code that queries whether something is a union or accesses its value), but they would be optional and not required for compiler recognition. ##### Conclusion Approved. The design will change from requiring `IUnion` interface implementation to using a `[Union]` attribute for compiler recognition. Members will be looked up by shape (constructors with case type parameters, a `Value` property). #### Factory method support Constructors have limitations: they cannot be defined on a type other than the one they construct, and they cannot be inherited or delegated. Static factory methods provide more flexibility for scenarios involving type hierarchies, object pooling, or delegating union member definitions to a separate type. The proposal is to allow `public static Create` methods as an alternative to constructors for defining case types. When factory methods are present, they take precedence over constructors. There was significant concern about implicit recognition of factory methods based on shape alone. A method like `Parse(string)` returning the union type could be unintentionally picked up as defining a `string` case type. Even if we restrict to just a single name (`Create`), we don't have enthusiasm for the magic shape recognition among different overloads here. The LDM expressed strong support for requiring explicit markers rather than relying on implicit shape matching. The proposal of a `[UnionCase]` attribute was well-received: authors would mark constructors, factory methods, or even user-defined implicit conversion operators that should contribute to the union's case types. A follow-up question arose: should the `[UnionCase]` attribute be required on everything, including constructors, or should there be a simple default mode where constructors work implicitly? Two positions emerged: 1. Attributes on everything: Any member used by the compiler for union behavior must be marked with `[UnionCase]`. 2. Default behavior for constructors: If a union type has only constructors (no factory methods or conversion operators), they work implicitly without attributes. Once you want any customization (factory methods, conversion operators, or selective inclusion of constructors), you must use `[UnionCase]` on all participating members. A read-of-the-room vote showed preference for the second approach: allow the simple constructor-only case to work without attributes, while requiring explicit marking for any advanced scenarios. However, we want the working group to take a look at the implications and come back with a concrete proposal and recommendations. ##### Conclusion Move forward with the `[UnionCase]` attribute approach. The working group will produce a concrete proposal with specific rules. There is no requirement for a specific factory method name when the attribute is used. #### Union member providers This proposal addresses scenarios where the union's desired behavior conflicts with its public surface area. Two cases were identified: 1. The type has members that would be incorrectly recognized as union members 2. The type needs union members but does not want them in its public API The idea was to allow delegating union member lookup to a separate "provider" type (e.g., a nested interface). Given the decision to use explicit `[UnionCase]` attributes, the first scenario is largely addressed: members won't be picked up unless explicitly marked. For the second scenario, user-defined implicit conversion operators (if supported) could address types like `Result` that expose conversions but not constructors. However, we don't have good examples of what could actually need this ability. Given that, we will simply table this addition for now, and come back when we have concrete scenarios to address. ##### Conclusion This feature will be saved for later. The explicit attribute approach addresses the primary motivating scenarios. If compelling use cases emerge that require full delegation to a provider type, the design space remains open. ### General principle established Throughout the discussion, a general principle emerged: the LDM is comfortable with some implicit behavior for well-known members (like recognizing a property named `Value`), but prefers explicit marking for case type definitions due to the inherent variability and potential for false positives. It's possible further consideration of real use cases may cause us to want to go even further into the explicit territory (explicitly marking `Value` or the `TryValue` methods, for example), but the working group will explore this space. ================================================ FILE: meetings/2026/LDM-2026-02-09.md ================================================ # C# Language Design Meeting for February 9th, 2026 ## Agenda - [Closed hierarchies open questions](#closed-hierarchies-open-questions) - [Confirming API shape](#confirming-api-shape) - [Blocking subtyping from other languages](#blocking-subtyping-from-other-languages) - [Multiple `CompilerFeatureRequired` attributes](#multiple-compilerfeaturerequired-attributes) - [Same module restriction](#same-module-restriction) - [Permit explicit use of `abstract` modifier](#permit-explicit-use-of-abstract-modifier) - [Subtype metadata](#subtype-metadata) ## Quote(s) of the Day - "I will always call `unions`, `onions`." - "This class, despite its name, is not `closed`" ## Discussion ### Closed hierarchies open questions Champion issue: https://github.com/dotnet/csharplang/issues/9499 Spec: https://github.com/dotnet/csharplang/blob/fab1ad040df56d5cfcca0b271b02a20f30e389a6/proposals/closed-hierarchies.md Today we went through the open questions in the closed hierarchies spec. #### Confirming API shape Spec section: https://github.com/dotnet/csharplang/blob/fab1ad040df56d5cfcca0b271b02a20f30e389a6/proposals/closed-hierarchies.md#closedattribute We started by confirming the shape of the API. We may add `interface`s as a supported target in the future, but for now limiting this simplifies the specification. Otherwise, we like the shape, modulo getting it approved by the libraries API review. ##### Conclusion API approved. #### Blocking subtyping from other languages Spec section: https://github.com/dotnet/csharplang/blob/fab1ad040df56d5cfcca0b271b02a20f30e389a6/proposals/closed-hierarchies.md#blocking-subtyping-from-other-languagescompilers The spec proposes applying `[CompilerFeatureRequired("ClosedClasses")]` to all constructors of closed classes. Since closed classes are abstract, their constructors can only be invoked from constructor initializers in derived types. Compilers that do not understand the feature will see the `CompilerFeatureRequired` attribute and block derivation, effectively preventing unauthorized subtyping from older C# compilers or other .NET languages. The `[Obsolete]` attribute that was used alongside `CompilerFeatureRequired` for `required` members is no longer considered necessary, since `CompilerFeatureRequired` has been around long enough that compilers are expected to understand it. The LDM approved this approach. There was also a suggestion about whether constructors of closed types should be required to have `private protected` accessibility, to further express the intent that these constructors are only for use by known subtypes. However, this was considered more trouble than it is worth: `private protected` is a niche accessibility, primary constructors pick up the accessibility of their containing type, and synthesized constructors would need special handling. Requiring more restrictive accessibility would add complexity without meaningful benefit, since `CompilerFeatureRequired` already blocks unauthorized use. The LDM decided to proceed without constructor accessibility restrictions and keep an eye on it going forward. ##### Conclusion The `CompilerFeatureRequired` approach on constructors is approved. No additional constructor accessibility restrictions will be required. #### Multiple `CompilerFeatureRequired` attributes Spec section: https://github.com/dotnet/csharplang/blob/fab1ad040df56d5cfcca0b271b02a20f30e389a6/proposals/closed-hierarchies.md#use-of-multiple-compilerfeaturerequired-attributes When a closed class also has required members, both `[CompilerFeatureRequired("RequiredMembers")]` and `[CompilerFeatureRequired("ClosedClasses")]` end up on the constructors. The question was whether to emit only the most recent attribute, on the theory that a compiler supporting closed classes must already support required members. The LDM concluded that all attributes should be emitted. Each part of the compiler that checks for a given feature can remain independent, without needing to know about other features. It is also a valid scenario for a language or compiler to support one feature but not another; for example, a language might support closed hierarchies but not required members. Users also get better diagnostics when all relevant attributes are present, rather than receiving confusing errors about only one of the features. While the number of attributes could grow over time, that concern can be revisited if it ever actually becomes a problem. ##### Conclusion Emit a `[CompilerFeatureRequired]` attribute for each feature independently. #### Same module restriction Spec section: https://github.com/dotnet/csharplang/blob/fab1ad040df56d5cfcca0b271b02a20f30e389a6/proposals/closed-hierarchies.md#same-module-restriction The proposal strengthens the "same assembly" restriction to "same module," since the compiler supports loading dependencies with multiple modules in a single assembly. The reasoning is the same as for cross-assembly subtyping: the original module needs to know the complete set of subtypes for exhaustiveness checking, and allowing subtypes from another module would break that. In practice, this distinction is largely academic since modern .NET no longer supports multi-module assemblies. The restriction effectively changes nothing for real-world users, but it is the correct formulation from the language specification perspective, which generally talks about programs in terms of modules. ##### Conclusion Approved. The spec will use "same module" language instead of "same assembly." #### Permit explicit use of `abstract` modifier Spec section: https://github.com/dotnet/csharplang/blob/fab1ad040df56d5cfcca0b271b02a20f30e389a6/proposals/closed-hierarchies.md#permit-explicit-use-of-abstract-modifier A `closed` class is implicitly abstract. Should the language also permit writing `closed abstract class`? Arguments for allowing it centered on consistency with how the language handles default accessibility: when there is a default, C# typically allows you to state it explicitly for clarity. Partial declarations were raised as a scenario where one part of a type might have `closed` and another might have `abstract`, and requiring the `abstract` to be removed would be unnecessary friction. Additionally, unlike `abstract` on interfaces (which is truly meaningless), `abstract` on a closed class does have a concrete meaning: it is part of the definition of `closed`. Arguments for blocking it drew an analogy to `static` classes, which are emitted as abstract sealed types but do not allow either `abstract` or `sealed` as explicit modifiers. The key difference from the default accessibility case is that there is no alternative here: you cannot make a closed class non-abstract, so stating `abstract` is purely redundant rather than picking from multiple options. Blocking the combination also ensures that every closed class declaration looks the same, which reduces cognitive overhead. A point was also raised about modern coding agent workflows: languages that define a single uniform way to write things tend to produce better agent experiences, as agents are more likely to produce consistent output when there is exactly one way to express something. After heavy discussion, we have decided to move forward with blocking for now. Starting restrictive is the prudent approach: if a compelling scenario arises where allowing `abstract` alongside `closed` is important, the restriction can always be relaxed later. ##### Conclusion `closed abstract class` will not be allowed. The `closed` modifier implies `abstract`, and specifying both is an error. #### Subtype metadata Spec section: https://github.com/dotnet/csharplang/blob/fab1ad040df56d5cfcca0b271b02a20f30e389a6/proposals/closed-hierarchies.md#todo-should-subtypes-be-marked-in-metadata The spec raised whether the compiler should emit an attribute on the closed type listing all of its direct subtypes (e.g., `[ClosedSubtype(typeof(Subtype1), typeof(Subtype2), ...)]`), so that consuming tools do not have to scan the assembly to discover them. From the compiler's perspective, this is not needed: the typedef table in the assembly metadata has an "extends" column, and scanning it for subtypes of a given type is fast. Even for large assemblies like `Microsoft.CodeAnalysis.CSharp`, the typedef table only has around 1,400 entries. However, concerns were raised about scenarios beyond the compiler. Runtime discovery via reflection would be significantly more expensive and is also incompatible with trimming and AOT, since scanning an assembly for subtypes could return different results depending on what types the trimmer kept. Without breadcrumbs in metadata, closed hierarchies would be effectively a compiler-only feature with no affordable way for runtime tools to discover the hierarchy. The LDM concluded that this is not a language-level concern. The compiler does not need it, and there are no pending requests from runtime, reflection, or serialization teams. If such requests arise, the compiler can emit additional metadata without changing language-level semantics. For now, subtypes will be discovered by scanning the assembly metadata tables. ##### Conclusion No subtype listing attribute will be emitted at this time. The compiler team will respond to requests from runtime or tooling teams if they need cheaper runtime discovery. ================================================ FILE: meetings/2026/LDM-2026-02-11.md ================================================ # C# Language Design Meeting for February 11th, 2026 ## Agenda - [Union patterns update](#union-patterns-update) ## Quote of the Day - "My Result type is a GC hole." "I'd put that on a t-shirt." ## Discussion ### Union patterns update Champion issue: https://github.com/dotnet/csharplang/issues/9662 Spec: https://github.com/dotnet/csharplang/blob/0e3a99401b0fb0fbde2ad976e63332c4d603e6d5/proposals/unions.md Working group notes: https://github.com/dotnet/csharplang/blob/0e3a99401b0fb0fbde2ad976e63332c4d603e6d5/meetings/working-groups/discriminated-unions/union-patterns-update.md The discriminated unions working group returned to LDM with an updated proposal for union member patterns on custom union types. In the [previous discussion](./LDM-2026-02-04.md#union-patterns-update), the LDM had expressed a preference for explicit marking of case types via a `UnionCase` attribute, rather than having them be implicitly recognized. The working group took that feedback and explored the attribute approach further, but found that attributes still lead to significant complexity in certain real-world scenarios. The core problem is that existing types that want to become union types may already have members that would be recognized as union members, but that serve a different purpose. In some cases, these members even clash with what the union feature needs, so an attribute cannot resolve the conflict. A common real-world example is a `Result`-like union type that has a `Value` property meaning something different from what the union pattern expects. Instead of attributes, the working group proposed a delegation approach: a union type can optionally delegate all of its union members to a nested interface called `IUnionMembers` that the union type implements. Since constructors cannot appear in interfaces, factory methods named `Create` are used instead. The interface can include an abstract `Value` property that is explicitly implemented on the union type, keeping it off the public surface area. The compiler can then use constrained calls to avoid the overhead of interface dispatch. The walkthrough included several real-world union-like types to demonstrate how the approach works in practice: - **`SumType`** (from Roslyn/LSP): This type already happens to expose exactly the union pattern. It has constructors for each case type and an `object?`-returning `Value` property. It can simply be marked with the `[Union]` attribute and everything works. It was noted that this type also has `TryGet` methods but they use different names, so they do not accidentally match the non-boxing access pattern, which is appropriate since the type stores values in a boxed form anyway. - **`OneOf`** (a popular third-party library): This type has a `Value` property of the right shape but does not expose constructors. This is a case where delegation is needed: a nested `IUnionMembers` interface can be added with `Create` factory methods for each case type, plus a `Value` property. The type would then implement the interface, and since the existing `Value` property already satisfies the interface member, no additional implementation is needed. - **`Results`** (from ASP.NET Minimal APIs): This type has a property that serves the same purpose as `Value` but has a different name and a more specific return type (`IResult` rather than `object?`). This is another case where delegation is needed. This example also raised a question about whether the `Value` property should be allowed to have a more specific return type in general, which could enable better nullability analysis and more efficient code. That question was noted but deferred to a separate discussion. It was observed that all three real-world types already implement their own implicit conversion operators, which would shadow the compiler-generated union conversion in all cases. This means the `Create` factory methods on the `IUnionMembers` interface would primarily serve to inform the compiler about the case types, rather than being called at runtime. This prompted a brief discussion about "consume-only" unions, where the type does not want to expose the ability for users to create union values directly. A possible future solution was sketched out involving a separate method name (e.g., `CaseTypeOf`) that returns `void` and only lists the case types, but this is not proposed for now. A question was raised about whether the non-boxing access pattern (`TryGetValue` methods) could actually be harmful if applied to types that already store values in a boxed form. The LDM agreed that guidance will be needed to explain when it is appropriate to expose the non-boxing pattern, but the specifics of the compiler heuristics for choosing between `Value` and `TryGetValue` are not yet fully worked out. Regarding the `IUnionMembers` interface, it was confirmed that this interface must be defined as a nested type directly within the union type, not inherited from a base type. An inherited `IUnionMembers` interface would have the wrong type parameters in its factory methods and would not be useful. Even though the language generally picks up members from base types in lookup, this particular interface is scoped to only the declaring type. There was a question about whether static non-DIM (default interface method) members in interfaces are supported on older runtimes. The C# compiler currently blocks static members in interfaces when the target runtime does not support DIMs, even though non-virtual static members in interfaces have always been supported by the runtime independently of DIM support. The working group followed up and confirmed that static non-virtual interface members have been properly exercised on the Desktop CLR: C++/CLI has offered them at the language level all along, and F# has also been targeting them for some time. So this is not a concern. A concern was raised about metadata bloat from the additional interface type. The working group noted that they had considered an even simpler approach where every union, including those from the `union` keyword, always delegates to an interface, but rejected it because generating two types for every union declaration felt excessive. The working group followed up on this concern as well, and confirmed that the nested-interface approach is not concerning from a metadata bloat perspective, given how uncommon the delegation scenario is expected to be. #### Conclusion The LDM is in favor of the `IUnionMembers` delegation approach as proposed by the working group. We will move forward with the proposal as written. ================================================ FILE: meetings/2026/LDM-2026-03-09.md ================================================ # C# Language Design Meeting for March 9th, 2026 ## Agenda - [Extension indexers](#extension-indexers) - [Ordering for implicit indexers and list patterns](#ordering-for-implicit-indexers-and-list-patterns) - [Slice extensions for range access](#slice-extensions-for-range-access) - [Spread optimization in collection expressions](#spread-optimization-in-collection-expressions) ## Quote(s) of the Day - "Ordering or orering?" "I'm writing ordering, sorry if that confuses you" ## Discussion ### Extension indexers Champion issue: https://github.com/dotnet/csharplang/issues/9856 Spec: https://github.com/dotnet/csharplang/blob/ee06dd0eaaff1ddb16d0f6680107a1a00aecee92/proposals/extension-indexers.md We continued discussion of the remaining open questions around extension indexers and their interactions with list patterns. The core tension is that multiple lookup principles all seem desirable in isolation: instance members should continue to beat extensions for compatibility, inner extension scopes should generally beat outer scopes, and real `this[Index]`/`this[Range]` members are usually preferable to implicit indexers assembled from `Length` or `Count` together with `this[int]` or `Slice`. Unfortunately, these preferences conflict once extension members are allowed to contribute to indexing. #### Ordering for implicit indexers and list patterns The hardest question was how to order extension lookup once implicit indexers are involved. We compared two broad approaches. One would continue to prioritize inner extension scopes over outer scopes, even when that meant selecting an implicit indexer assembled from closer members instead of a real indexer found farther out. The other would keep the strong preference that instance members always beat extensions, but once extension lookup was required it would give a weak priority to real indexers over implicit ones, even if the real indexer came from an outer extension scope. We chose the second ordering. We feel that a real indexer remains a more coherent single declaration than an implicit indexer assembled from multiple members, and that this weak preference was worth preserving once the search had already moved beyond instance members. This does make extension lookup somewhat irregular, because the language will no longer uniformly prefer the closest extension-provided solution in every case, but the LDM preferred that irregularity over a rule that would make real extension indexers lose to extension-based implicit combinations more often. List patterns were discussed alongside ordinary indexer access. We do not want list patterns to use one `Length` or `Count` member for shape checking and then silently use a different one when resolving indexing behavior. Instead, list pattern support should be described in terms of independently resolving the members needed for the operation, using the same overall ordering principles as indexer access. We workshopped the following specese during the meeting, which will need to be refined before inclusion in the speclet: > 1. List patterns are resolved as if we look for Length/Count, Index indexer and Range indexer individually > 2. For Index and Range indexers, proceed as follows: > a. With instance lookup only, find the "real" index if possible > b. With instance lookup only, find the parts of the implicit indexer if possible > c. With full lookup (instance+extension), find the "real" index if possible > d. With full lookup (instance+extension), find the parts of the implicit indexer if possible (each in individual lookups) ##### Conclusion When resolving extension-based indexing behavior, instance lookup remains strictly better than extension lookup. After that, the language will give a weak preference to real `Index` and `Range` indexers over implicit indexers, even when that means an outer extension scope can beat an implicit solution formed from a closer scope. List patterns will resolve the required members using the same overall ordering model rather than treating implicit indexers in extensions as unsupported. #### Slice extensions for range access We next looked at the `Slice` portion of implicit range support. The question was whether, once extension members are allowed to contribute, only extension-block `Slice` members should count, or whether classic extension methods named `Slice` should count as well. Historically, neither form contributed here, so enabling extension participation would cause some code to start compiling where it previously produced an error. We see no good reason to distinguish the two forms. The extensions feature has consistently tried to make classic extension members and extension-block members interchangeable from the user's point of view, and this scenario should follow that same principle. Moving from an error to a successful binding was also considered acceptable here, because it does not break previously-working code and is a natural consequence of allowing extensions to participate in range access. ##### Conclusion Both classic extension `Slice` methods and extension-block `Slice` members will contribute to implicit range access under the new rules. #### Spread optimization in collection expressions Finally, we considered whether extension `Length` or `Count` properties should participate in the optimization for spread elements in collection expressions. That optimization exists to avoid unnecessary reallocations by pre-sizing the destination when the compiler can cheaply determine the number of elements that will be produced. The concern was that an extension `Length` or `Count` member is much less likely to represent cheap, intrinsic knowledge about the underlying type. In many cases, such an extension would have to enumerate the source to discover the count, which would defeat the purpose of the optimization by adding an extra traversal before the actual spread occurs. We therefore prefer to keep the optimization tied to information directly exposed by the source type itself. If compelling scenarios emerge later, or if there is a better hook for efficient optional counts, the language can revisit this decision. ##### Conclusion Extension `Length` and `Count` members will not participate in spread optimization for collection expressions. ================================================ FILE: meetings/2026/README.md ================================================ # Upcoming meetings for 2026 All schedule items must have a public issue or checked-in proposal that can be linked from the notes. ## Schedule ASAP ## Schedule when convenient - [Anonymous using declarations](https://github.com/dotnet/csharplang/blob/665a9392e172e6f4f16347c502d9f80220a6e7a4/proposals/anonymous-using-declarations.md) (jnm2, 333fred, CyrusNajmabadi) - Triage (working set) ## Recurring topics - *Triage championed features and milestones* - *Design review* ## Schedule ### Wed Apr 29, 2026 ### Mon Apr 27, 2026 ### Wed Apr 22, 2026 - [Final initializers](https://github.com/dotnet/csharplang/blob/5055b97eee8c10d12f822f6d4db9464329615947/proposals/final-initializers.md) - [LDM in 2020](https://github.com/dotnet/csharplang/blob/main/meetings/2020/LDM-2020-04-27.md#primary-constructor-bodies-and-validators) approved the syntax. Next is discussing semantics. ### Mon Apr 20, 2026 ### Wed Apr 15, 2026 - [Deconstruction in lambda parameters](https://github.com/dotnet/csharplang/blob/c4ec6fb60c2e174b1abb6c019f22bb15b9b13f6c/proposals/deconstruction-in-lambda-parameters.md) (CyrusNajmabadi, jnm2) - [Last conclusion](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-01-12.md#deconstruction-in-lambda-parameters): take a closer look in LDM. ### Mon Apr 13, 2026 ### Wed Apr 8, 2026 - [Target-typed static member access](https://github.com/dotnet/csharplang/blob/c2465a0605180e9624ee5ea9d6e0eab7e93a7c5b/proposals/target-typed-static-member-access.md) (jnm2, CyrusNajmabadi) - Continue discussing scope and open questions - [Labeled `break` and `continue` Statements](https://github.com/dotnet/csharplang/blob/c4ec6fb60c2e174b1abb6c019f22bb15b9b13f6c/proposals/labeled-break-continue.md) (CyrusNajmabadi) - [Last conclusion](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-01-12.md#labeled-break-and-continue-statements): delve deeper in a full session. ### Mon Apr 6, 2026 ### Wed Apr 1, 2026 ### Mon Mar 30, 2026 (One hour only) - MVP Summit feedback (Mads) ### Wed Mar 11, 2026 - [Target-typed static member access](https://github.com/dotnet/csharplang/blob/c2465a0605180e9624ee5ea9d6e0eab7e93a7c5b/proposals/target-typed-static-member-access.md) (jnm2, CyrusNajmabadi) ## C# Language Design Notes for 2026 ### Mon Mar 9, 2026 [C# Language Design Meeting for March 9th, 2026](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-03-09.md) - Extension indexers - Ordering for implicit indexers and list patterns - Slice extensions for range access - Spread optimization in collection expressions ### Wed Feb 11, 2026 [C# Language Design Meeting for February 11th, 2026](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-02-11.md) - Union patterns update ### Mon Feb 9, 2026 [C# Language Design Meeting for February 9th, 2026](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-02-09.md) - Closed hierarchies open questions - Confirming API shape - Blocking subtyping from other languages - Multiple `CompilerFeatureRequired` attributes - Same module restriction - Permit explicit use of `abstract` modifier - Subtype metadata ### Wed Feb 4, 2026 [C# Language Design Meeting for February 4th, 2026](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-02-04.md) - Discriminated unions patterns - Null ambiguity in constructor selection - Marking unions with an attribute instead of IUnion interface - Factory method support - Union member providers ### Mon Feb 2, 2026 [C# Language Design Meeting for February 2nd, 2026](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-02-02.md) - Extension indexers ### Mon Jan 26, 2026 [C# Language Design Meeting for January 26th, 2026](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-01-26.md) - Alternative syntax for caller-unsafe ### Wed Jan 21, 2026 [C# Language Design Meeting for January 21st, 2026](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-01-21.md) - Unsafe evolution ### Mon Jan 12, 2026 [C# Language Design Meeting for January 12, 2026](https://github.com/dotnet/csharplang/blob/main/meetings/2026/LDM-2026-01-12.md) - Triage - Relaxed ordering for `partial` and `ref` modifiers - Deconstruction in lambda parameters - Unsigned sizeof - Labeled `break` and `continue` Statements - Extra accessor in property override - Immediately Enumerated Collection Expressions - Allow arrays as CollectionBuilder Create parameter type ================================================ FILE: meetings/README.md ================================================ # C# Language Design Meetings C# Language Design Meetings (LDM for short) are meetings by the C# Language Design Team and invited guests to investigate, design and ultimately decide on features to enter the C# language. It is a creative meeting, where active design work happens, not just a decision body. Each C# language design meeting is represented by a meeting notes file in this folder. ## Purpose of the meetings notes Meeting notes serve the triple purposes of - recording decisions so they can be acted upon - communicating our design thinking to the community so we can get feedback on them - recording rationale so we can return later and see why we did things the way we did All have proven extremely useful over time. ## Life cycle of meeting notes - If upcoming design meetings have a specific agenda, for instance to suit the schedule of visitors, there may be a meeting notes file with that agenda even before the meeting happens. - Otherwise the meeting agendas are determined just-in-time based on urgency, arrival of new information or ideas, challenges found in design and implementation, and so on. - After the meeting, notes will be saved directly here. - Usually they will be raw notes in need of subsequent cleaning up. If that's the case, they will be clearly marked as such, and a [Meeting notes](https://github.com/dotnet/csharplang/labels/Meeting%20notes) work item will track the task of cleaning up the notes. - When the notes are finalized, a notification is posted as a [discussion issue](https://github.com/dotnet/csharplang/labels/Discussion) to encourage discussion of the decisions made. While quick comments are welcome directly on that issue, it is recommended to open a separate issue for deeper or more topic-specific discussions. - If the notes impact current proposals, [proposal](https://github.com/dotnet/csharplang/labels/Proposal) work items will track updating those proposals, assigned to their [champions](https://github.com/dotnet/csharplang/labels/Proposal%20champion). - When updated, the proposals link back to the meetings where the proposal was discussed. ## Style of design notes The notes serve as the collective voice of the LDM. They cover not just the decisions but the discussion, options and rationale, so that others can follow along in the discussion and provide input to it, and so that we don't forget them for later. However, *the notes are not minutes*! They *never* state who said what in the meeting. They will occasionally mention people by name if they are visiting, provided input, should be collaborated with, etc. But the notes aim to represent the shared thinking of the room. If there's disagreement, they will report that, but they won't focus on who wants what. This approach is intended to reinforce that the LDMs are a safe space, and a collaborative, creative effort. It is not a negotiation between representatives of different interests. It is not a voting body, and it is not a venue for posturing. Everybody cooperates towards the same end: creating the best language for today's and tomorrow's C# developers. When there are external inputs to the notes (such as open questions, presentations, supplemental documents, etc), they will be committed to this repository and linked to from the notes, with a specific commit, to ensure that any future reorganization will keep the notes working. ## Working groups Working groups who choose to do so will keep meeting notes in a subfolder of the [working-groups](working-groups/) folder belonging to that group. These notes will be anonymized, like LDM notes, but are likely to be much rougher than dedicated LDM notes. We will not create dedicated discussions when posting these working group notes. Users that want to comment on them can either comment on the issue for the group or create a discussion, as they choose. Working groups will each have a leader, and that leader will be responsible for coordinating with the broader LDM on when things need to be brought back to the full group. ================================================ FILE: meetings/working-groups/collection-literals/CL-2022-10-06.md ================================================ # Collection literals working group meeting for October 6th, 2022 This is the first meeting of the collection literals working group. The proposal is currently located at . ## Agenda - Overview of the current proposal and its goals - Discovering an initial consensus on open questions from which to make recommendations - Fleshing out a design for dictionary literals ### Collection literals discussion #### Natural types There was consensus that there should be a natural type and that it should be `List`. It’s not uncommon today for there to be more modifications following a collection initializer. The kicking-the-tires scenario was important to us; the experiences of trying both `var x = [a];` and subsequently `x.Add(...);` are things we think will be formative first impressions of the feature, and we don’t want them to feel confusing or broken. We think that `List` will only be an additional allocation beyond `T[]` or `ImmutableArray`. `List` can expose an `init void Init(T[])` method which takes ownership of the array. As a consequence, the list capacity would be initialized to exactly match the item count. In a similar vein, we think *unknown-length* collection literals such as `[..enumerable]` should work with fixed-length collections such as `ImmutableArray`. This is a reasonable scenario, and it will make the feature feel confusing or broken if the compiler doesn't allow it. We will look for a codegen strategy to enable this. #### Eager or lazy enumeration when the target type is `IEnumerable` Collection literals create an instance of the concrete target type, whether that's `ImmutableArray` or `HashSet`. If the target type is `IEnumerable`, we could imagine the expectation that a lazily-enumerating generator will be produced so that `[..a, ..b]` behaves exactly like `a.Concat(b)`. However, we think it's clear and explicit enough that the *collection* literal `[`...`]` always creates a new collection. This proposal is not about list comprehensions. #### Other observations 1. We think collection literal syntax should be on par with or better than the runtime performance of the current methods of initializing collections. For example, we need to compare the codegen for `(ImmutableArray)[..enumerableOfT]` to `ImmutableArray.CreateRange`. 1. We think `[..a]` will be a common way to do a shallow clone from one collection type to another. 1. The repetition in `init void Init(...)` looks strange. `init void Construct(...)` was suggested as an alternative name for the compiler to bind to. This would parallel `Deconstruct`. 1. If the language added `let`, we could consider making the natural type of `let x = [a];` be ImmutableArray similar to Swift. 1. The `Init(...)` method could more naturally be static and return the constructed instance, rather than an instance method, except that it would not be possible to write extension methods to extend a fixed-length collection type so that collection literals can be used with it. This is a concession to the language's current lack of static extension methods. ### Dictionary literals discussion We liked the dictionary literal having the natural type `Dictionary`, assuming `List` as the natural type of a collection literal. It should feel parallel, so if the collection literal natural type was `ImmutableArray`, it would be `ImmutableDictionary`. We noted that there are not many framework types to choose between, unlike with collection literals. #### Syntactic options We considered the following syntaxes for dictionary literals: ```cs var dict1 = { "key1": "value1", "key2": "value2" }; var dict2 = [ "key1": "value1", "key2": "value2" ]; var dict3 = [ ["key1"] = "value1", ["key2"] = "value2" ]; var dict4 = [ "key1" => "value1", "key2" => "value2" ]; ``` Of the square bracket syntaxes, we agreed on the `dict2` style as being minimalist and the most familiar coming from other languages. - Curly braces are too overloaded. We don’t this to be visually confusing with block expressions or impede that space. - `[ ["a"] =`... starts out visually similar to a collection literal containing another collection literal. - Fat arrows could make it confusing to disambiguate lists of lambdas. Like with collection literals, we like the idea of making pattern matching resemble construction: ```cs var dict2 = [ "key1": "value1", "key2": "value2" ]; if (dict5 is [ "key1": var value ]) { ``` There’s an existing community proposal for indexer patterns which would combine indexer access with property patterns: . This would be more general than dictionaries but would include dictionaries. The community proposal has the syntax `{ [a]: b }` and would enable patterns such as `{ a.b: c, [d].e: f }`, and so on. This would suggest yet another syntax permutation: ```cs var dict5 = { ["key1"] = "value1", ["key2"] = "value2" }; if (dict5 is { ["key1"]: var value }) { ``` Open question: which way do we resolve our favored construction syntax with indexer pattern matching syntax? The two existing dictionary initialization syntaxes have different behaviors for duplicate keys. One calls `Add`, which typically throws if the key already exists: `{ { a, b }, { a, c } }` The other calls the indexer setter, which typically overwrites if the key already exists: `{ [a] = b, [a] = c }`. This raises the question of whether dictionary literals should replace or throw. We leaned towards replacing rather than throwing. The throw behavior could annoyingly block a valid and intended scenario. Also, dictionary spread can be seen as a cousin of `with` expressions: `obj with { prop = a }` is a lot like `[..dict, prop: a]`. Swift has `[:]` to denote an empty dictionary. It would be rare to find this useful for C# dictionary literals: `[]` would also denote an empty dictionary when the target type is a dictionary type rather than a collection type, and neither `[]` nor `[:]` could be used when there is no target type, e.g. `var x = [];`. One place where `[:]` could be useful is to disambiguate between overloads when there is a collection type in one overload and a dictionary type in another overload. We doubt this motivates the syntax; there are other ways to disambiguate. #### Dictionary literal implementation strategy While planning dictionary literals, we need to propose a strategy for how they will initialize dictionaries. Extrapolating from the approach in the collection literals strategy provided a starting point: ```cs // Or ReadOnlySpan<>, just like with collection literals init void Init(KeyValuePair[] values) ``` Even without a new `Init(...)` method, a similar extrapolation could work for mutable dictionaries by calling the `Add` method. Like with collection literals, we should make sure the runtime performance of dictionary literals is on par with existing methods of construction. ## Future agenda The next meeting is currently planned for October 14th, 2022. - Continuing on dictionary literals - Implementation strategies - Initializing fixed-length collections from unknown-length collection literals - Unresolved question 2 from the proposal: > Stack allocations for huge collections might blow the stack. Should the compiler have a heuristic for placing this data on the heap? Should the language be unspecified to allow for this flexibility? We should follow what the spec/impl does for params `Span`. - If there is time, exploring other open questions from the proposal ================================================ FILE: meetings/working-groups/collection-literals/CL-2022-10-14.md ================================================ # Collection literals working group meeting for October 14th, 2022 This is the second meeting of the collection literals working group. The proposal is currently located at . ## Agenda - `List<>`/`Dictionary<,>`/`KeyValuePair<,>` being first-class concepts - Use of `Enumerable.TryGetNonEnumeratedCount` - Known-length and unknown-length spreads not affecting what the user sees - Calling `.Count` along with each spread element evaluation or in a later phase - `k: v` dictionary element equivalent to KeyValuePair expression element - Natural typing with empty literals - Unsupported cases - Allowing target-typing to interfaces - Implementation strategy for dictionary spreads - Type parameters of the natural type for dictionary literals ### Dictionaries Positive reception for tying `k: v` syntax to KeyValuePair and allowing dictionary elements alongside expression elements. If there's a typo and a colon is accidentally added or removed, we think the compiler can create a helpful diagnostic. What should we do if someone tries to use dictionary elements to call collection indexers? ```cs List list = [0: "first", 1: "second"]; ``` We could identify if the indexer being called is mapped to a list interface indexer and not a dictionary interface indexer. We should think about this more because we'd want to consider graph-like collections. To determine the type parameters for the `Dictionary<,>` natural type, we definitely need both expressions (from the dictionary elements) and types (from the expression elements and spread elements) as inputs to the best common type algorithm. We want `[..dict1, ..dict2]` to produce a dictionary, so the target type will be a dictionary when the expression elements and spread elements bring in only KeyValuePair instances even if there is no dictionary element `k: v`. We agreed that there should be no natural type for `[k: v, someObject]`. If there is a dictionary element and don't get a dictionary, that's very confusing. If you want a list, you can explicitly type as `List` which will then contain the KeyValuePair created by `k: v`. ## Codegen efficiency We like using `Enumerable.TryGetNonEnumeratedCount` in the codegen when available. We aren't sure where to draw the line in specifying improvements like this in the spec instead of leaving it up to the compiler, which the spec already invites to take initiative in improving codegen for runtime performance. We think it will be imperative for span users that codegen uses stackalloc when target-typing `Span`. Is it acceptable for there to be resizes (`TryGetNonEnumeratedCount` returns false)? We want input from experts in these areas about how to make good use of stack and heap memory for these use cases, and whether to allow spreads of known or unknown length when target-typing to spans. If we disallow spreads when target-typing to spans, users can create an array using `(T[])[...]` to obtain a heap-allocated span. Positive reception for the general concept that users won't get errors that force them to understand known-length versus unknown-length spreads when the target type is fixed-length and doesn't have a `Construct` method. We can always generate code to make this work. Hesitation around not being visible enough when unknown-length spreads are resulting in resize reallocations. However, analyzers can show when this happens, either any spread or ones that don't have a known length. It feels important to be able to spread unknown-length enumerables into arrays or immutable collections. Roslyn code does this commonly and it would be unfortunate not to be able to use collection literals for this. Collection literals should be thing that gets you the best performance rather than everyone figuring it out by hand. Roslyn wouldn't want to give up its ArrayBuilder helper, but maybe the compiler can implement private pools in its codegen for collection literals. This would require a robust codegen strategy, but but we like that. The codegen strategy could improve over time. The yearly .NET performance blog post could say, "Are you using collection literals? Now your code is faster!" ## Other discussion We don't want users to be surprised at later spread elements having been evaluated if calling `.Count` on an earlier spread element throws an exception. However, this is necessarily the case anyway if an exception is thrown during enumeration. We decided to table this for now. `[]` should have a special type, similar to how the null literal has a null type, which allows it to participate in things such as conditional expressions or lists of lists. We'll work on speccing this. We'd like to see if we can make `var list = cond ? [str] : [obj];` work too. Let's reoutline the spec so nuggets of reasoning aren't buried. We want to invest in realistic examples before presenting at the LDM too. ## Future agenda The next meeting is currently planned for October 21st, 2022. We will discuss remaining unresolved questions from the spec: - Immediate indexing into a collection literal - Use of AddRange or CopyTo when available - Use of `Add(KeyValuePair)` when the collection is a dictionary - Allowing spreads when target-typing to spans and which kinds (requiring intermediate resizes or not) - Stackalloc and spans ================================================ FILE: meetings/working-groups/collection-literals/CL-2022-10-21.md ================================================ # Collection literals working group meeting for October 21st, 2022 This is the third meeting of the collection literals working group. The proposal is currently located at [#5354](https://github.com/dotnet/csharplang/issues/5354). ## Agenda * `Span` and stack allocation * Preliminary review of collection literal speclet --- Supporting `Span` (and `ReadOnlySpan`) is considered very important for completeness for the feature. Stack allocation of data (using `stackalloc`) is also considered highly desirable for the ability to avoid incurring a GC cost, including avoiding overhead when the literal itself isn't observed but only its elements are. However, stack allocation is very dangerous as well. It can trivially cause stack explosions when not used carefully. It turns out this space is being heavily examined in the [`params span`](https://github.com/dotnet/csharplang/blob/main/proposals/params-span.md) proposal. We strongly believe that we would want the following to be equivalent: ```c# void M(params ReadOnlySpan values); M(1, 2, 3, 4); // as params span M([1, 2, 3, 4]); // as direct span ``` As such, our position is to align with whatever the `params span` working group comes up with for using spans and `stackalloc`. Currently, that spec says that the span: > will result in an array T[] created on the stack if the params array is within limits (if any) set by the compiler. Otherwise the array will be allocated on the heap. Following the same approach for collection literals would then always allow a literal to construct a `Span`, albeit sometimes using stack allocation, and sometimes heap allocation. We know that there will be some users and some use cases where this is unpalatable. For example, code which wishes to use a `Span` knowing that it will *only* stack-allocate. To that end, we expect the compiler to issue *hidden* diagnostics (a capability already supported today) when such a collection literal might initialize a span using a heap allocation. Concerned users would then enable a warning or error for that diagnostic, and could block any cases which the compiler doesn't guarantee will be on the stack. Importantly, we do not anticipate that stack allocating means a *direct translation* of a *collection literal expression* to `stackalloc`. For example, given: ```c# foreach (...) { Span values = [GetA(), GetB(), GetC()]; // do things with values } ``` a simplistic translation to: ```c# foreach (...) { Span values = stackalloc T[3]; ``` would be undesirable. This would grow the stack on each iteration of the loop, easily leading to a blowup at runtime. The compiler is allowed to translate that using `stackalloc` however it wants, as long as the `Span` meaning stays the same and [`span-safety`](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md) is maintained. For example, it can translate the above to: ```c# Span __buffer = stackalloc int[3]; foreach (...) { __buffer[0] = GetA(); __buffer[1] = GetB(); __buffer[2] = GetC(); Span values = __buffer; // do things with span. It would likely be appropriate to clear __buffer when the scope ends. } ``` This approach ensures that the stack does not grow in an unbounded fashion, though it may not be possible in all collection-literal cases. This would incur a cost even if the loop was never taken. But it feels absolutely more desirable than incurring growing for the much more common cases where the loop iterates multiple times. If the compiler decides to allocate on the heap, the translation for `Span` is simply: ```c# T[] __array = [...]; // using existing rules Span __result = __array; ``` --- A preliminary review of the spec was performed with another LDM member. The purpose of the review was to get an early read on how the spec is shaping up, and how the decisions of the working group feel against the backdrop of the philosophy and history of C#. Reviews around the intuitions behind constructibility, convertibility, the type system, etc. were performed, including whether the concepts matched C# so far and whether the descriptions so far could easily translate into the actual rules needed for the specification. The review was highly positive, with understanding and approval of the decisions of the working group so far, and of what collection literals both aim to support and not support. An entire topic was discovered that we have not considered so far: how collection literals operate with generic type inference. Specifically, the feature does not currently support: ```c# void M(T[] values); M([1, 2, 3]); ``` However, there was strong agreement that this should work and that `T` should be inferrable to `int` here. We believe the rules to support this should not be too difficult, but it certainly gets interesting with more complex cases like the following: ```c# void M(ImmutableArray values); M([1, 2, 3]); ``` For inference, we would likely need to see that `ImmutableArray` was *constructible* through the `init void Construct(T[] values)` method. Inference would then have to continue with `T[]` and `[1, 2, 3]`. That *base case* of inference would find that `T` was `int` which would flow out all the way to the top level inference. This topic has been added to the agenda for the working group to work through. ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-04-05.md ================================================ # Collection literals working group meeting for April 4, 2023 ## Agenda * Allow efficient construction of collections that are typically represented as arrays, particularly `ImmutableArray`. ## Discussion We discussed possible _construction method_ patterns that would allow the compiler to write directly to the underlying storage for collections such as `ImmutableArray` and `List`, and avoid extra allocations or copying in cases where the collection has a _known length_. There were two promising approaches that provide equivalent capability, with the difference of whether the underlying storage is allocated by the caller or by the collection. ### Pass ownership of array One possible approach is a _construction method_ that takes an array as an argument and uses that array directly as the underlying storage of the collection, with the caller passing ownership of the array to the collection instance. For instance, given the following method for creating an `ImmutableArray`: ```csharp // Create ImmutableArray using the array as the underlying storage. public static ImmutableArray AsImmutableArray(T[] array) { // ... } ``` The compiler could translate `ImmutableArray result = [x, ..e, y];` to: ```csharp T[] __array = new T[e.Count + 2]; int __i = 0; __array[__i++] = x; foreach (T __t in e) __array[__i++] = __t; __array[__i++] = y; ImmutableArray result = AsImmutableArray(__array); ``` ### Write directly to storage span An alternative is a _construction method_ that creates a collection of a given size and returns the collection _and_ a span that refers to the underlying storage, and the caller populates the collection using the span. For instance, given the following method for creating a `ImmutableArray`: ```csharp // Create ImmutableArray and return a span to the underlying storage. public static ImmutableArray CreateImmutableArray(int size, out Span span) { // ... } ``` The compiler could translate `ImmutableArray result = [x, ..e, y];` to: ```csharp Span __span; ImmutableArray result = CreateImmutableArray(e.Count + 2, out __span); int __i = 0; __span[__i++] = x; foreach (T __t in e) __span[__i++] = __t; __span[__i++] = y; ``` ### Runtime libraries The BCL could provide _construction methods_ for `ImmutableArray` and `List`, and perhaps a few additional collection types where the underlying storage can be represented as a span. The methods could be implemented in `System.Runtime.InteropServices` or `System.Runtime.CompilerServices` to indicate the intended use is from compilers or interop. ### Compiler use The compiler will use a _construction method_ for the corresponding collection type when creating a collection of _known length_. When creating collections of _unknown length_, using a _construction method_ may not provide an advantage over existing alternatives since either approach may require resizing and reallocating during construction. _What if the collection literal has a known length but the resulting collection instance is mutable? Is there an advantage to providing an initial length?_ _Construction methods_ could be registered with attributes on the _collection type_. For instance, an attribute could identify the construction method by containing type and method name. ```csharp [CollectionLiteralConstruction(typeof(ImmutableCollections), "AsImmutableArray")] public struct ImmutableArray { // ... } public static class ImmutableCollections { public static ImmutableArray AsImmutableArray(T[] array) { // ... } } ``` A collection type should have at most one _construction method_. Most collection types will have none. ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-04-28.md ================================================ # Collection literals working group meeting for April 28, 2023 ## Agenda * Should a collection literal have a *natural type*? If so, what type? * Infer the collection type from spread elements? * Should dictionaries use overwrite or add semantics? ## Discussion ### Should a collection literal have a natural type? If so, what type? [natural-type]: #natural-type Without a *natural type*, a collection literal can only be used when *target typed* to a *constructible collection type*. Given the *best common type* `T` of the elements, the choices for *natural type* include: * `T[]` * `List` * `Span` * `ReadOnlySpan` * `ImmutableArray` We ruled out `Span` and `ReadOnlySpan` because `ref struct` types cannot be used in `async` contexts. Also, `ref struct` types cannot implement interfaces, so spans cannot be used as `IEnumerable`. Of the remaining types, `List` is perhaps the most flexible and may satisfy more scenarios than `T[]` or `ImmutableArray`. Also, we speculate that `List` is commonly used today in method bodies, so having `List` as the *natural type* would be familiar and would align with existing code. Another consideration is consistency between the *natural types* for non-dictionary and dictionary collection literals. The most obvious choice for dictionaries is `Dictionary` which is consistent with `List`. However, different collection types are appropriate for different scenarios. If `List` is used as the *natural type* for a particular variable, but `Span` or `T[]` could have been used explicitly instead, the choice of `List` may be more expensive. `List` is mutable, even though it may be uncommon to mutate a collection that was created with an explicit initializer. That means consumers that want immutability will need to use an explicit type or wrap the instance. The alternative is *no natural type*. Collection literals are not a simple replacement for existing collection construction. Previously, constructing and populating a collection required handling details of capacity, allocations, indexing vs `Add()` vs `AddRange()`, etc., and using `List` may have been simpler or more familiar than alternative collection types. Collection literals address that by providing a common syntax that can be used for a range of collection types. Consumers can now choose the optimal collection type for each scenario, and requiring the developer to specify the collection type explicitly helps that. Some other alternatives: * Add support for *natural type* later. * Support *natural type* behind a *feature flag*. * Support *natural type* now and also include an analyzer that warns for `var` with collection literals for performance reasons. * Support `var` element types, as suggested in a recent LDM: `List`, `var[]`, `Dictionary`, etc. #### Conclusion No conclusion, although the most likely options for *natural type* may be `List` or *no natural type*. ### Infer the collection type from spread elements? [infer-from-spread]: #infer-from-spread Should the collection type of a collection literal be inferred from the collection types of any spread elements? ```csharp ImmutableArray a = [1, 2, 3]; var b = [..a, 4]; // ImmutableArray b ``` What if spread elements have distinct types, such as `HashSet` and `HashSet`, or distinct comparers? What if the spread element type is an interface, such as `IEnumerable` or `IReadOnlyDictionary`, or not a *constructible collection type*? There is the potential for breaking changes to `var` collections when the types of spread elements change: * between an interface and a concrete collection type, or * between a constructible and non-constructible collection, by adding or removing a public constructor. #### Conclusion Requires further discussion. ### Should dictionaries use overwrite or add semantics? [overwrite-or-add]: #overwrite-or-add Should construction of a dictionary from a collection with duplicates use overwrite or add (and throw) semantics? There are valid scenarios for both. The concern is when combining collections where the collection types use distinct semantics. There is a similar issue around comparers. Consider the non-dictionary case when combining two `HashSet` instances that use different comparers. There is potential for data loss in the resulting collection. #### Conclusion Requires further discussion. ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-05-10.md ================================================ # Collection literals working group meeting for May 10, 2023 ## Agenda * Discuss new options for natural typing of collection literals ## Discussion Coming out of the previous full LDM on collection literals ([notes](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#collection-literal-natural-type)), we wanted to explore more options for attaining both flexible usage and ideal performance with a natural type. Two alternatives were presented which had been fleshed out with potential designs. ### Option 1: anonymous/unspeakable 'collection builder' type This approach gives the natural type _builder_ semantics with a set of APIs such as `Add` and `AddRange`: ```cs var collection = [1, 2, 3]; collection.Remove(2); TakesImmutableArray(collection); ``` The natural type would be anonymous. The compiler would realize the collection by directly initializing an `ImmutableArray` in the example above, skipping any intermediate bookkeeping that is statically possible to skip, so that the best performance is achieved. The extra builder concept changes how users would think about naturally-typed collection literals. This could make the mental model more complex. An explicit 'build' called out somehow in syntax might even be preferable over passing the variable implicitly as the targeted collection type. We briefly considered whether there would be merits to making the type speakable, a new framework collection builder type that could be passed around. It would be yet another collection/builder type to teach people about. If the new builder type was itself capable of doing the building on the stack, it wouldn't be able to be captured or used across awaits. It would also be an advanced type which we wouldn't want normal users to be working with every time they use a naturally-typed collection literal. #### Conclusion The working group had some hesitations with this approach. ### Option 2: optimize away `List` Under this option, the natural type of collection literals would be `List` as previously proposed. A `List` instance would not actually be created if a different backing storage performs better and the difference is not observable by the code. These performance benefits would extend to all existing code that creates a new `List` instance using syntax. No `List` would be created in code such as the following; instead, this would be lowered to `stackalloc` or similar: ```cs List list = new List { 1, 2 }; foreach (var item in list) // Only usage of 'list' { } ``` Tuples are one example of prior art, where the compiler assumes implementations of System.ValueTuple to be well-behaved and skips creating them altogether in some cases when tuple syntax is used. #### Conclusion The working group was interested in pursuing this approach. One hesitation is that it might be hard to reason about when the optimizations are happening, and when you're actually getting a `List` at runtime. Are hidden diagnostics and analyzers enough? We will think about this further. ### Forward inference of the element type We considered whether it would be advantageous to match the builder pattern seen in other languages without explicitly typing the collection variable: ```cs // Infers the element type to be 'int' by looking forward // to the Add call. var collection = []; collection.Add(1); ``` Building a collection by starting without items is common, and this would enable users to use `var` instead of `List` or creating some other builder. Choosing the element type by examining calls and usages of the collection itself could result in errors which are hard for users to figure out. It would be similar to lambda return type inference. Lambda return type inference usually works well, but inference errors while editing can sometimes create really confusing diagnostics far away. Would we be making this poor kind of experience more common? #### Conclusion The working group was reluctant to embrace forward inference of element types unless forward inference became available for variables in a more general way, either by inferring generic type arguments as in `Service<_> s = ...` or even embracing forward inference completely by allowing `var s;`. ### Conversions from `List` to other collections One of the things that the builder option would have done is allow a naturally-typed collection to be passed as a specific collection type later. If we go with option 2 instead, would we want to retain this ability by allowing `List` to implicitly or maybe explicitly convert to any other collection type? ```cs var collection = [1, 2]; TakesImmutableArray(collection); // Or Span ``` #### Conclusion The working group leaned toward doing nothing special for implicit or explicit conversions from `List`, and having the above code fail to compile. ## Next steps We will delve into option 2. ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-05-26.md ================================================ # Collection literals working group meeting for May 26, 2023 ## Agenda * Codegen patterns and APIs for efficient construction * Open questions on dictionary literals * Cut line for C# 12 ## Discussion ### Capacity of constructed collections If an exact length is able to be calculated for the constructed collection, the capacity could be set ahead of time to avoid resizing the backing storage of the collection. But if the constructed collection escapes outside the method body, adding a single item to the collection after that would trigger a resize to double the backing storage size. People will be able to write better code by hand than the compiler can generate if they have outside knowledge of the eventual state of the collection during its life past the construction of the collection literal. This would be a caveat to a statement about the collection literals feature generating optimal code. This is fine; the compiler can strike a good balance, and people can drop down to manual construction of the collection if they need more control over the initial capacity. When an exact count is known for a collection literal but the compiler can't prove that it is not potentially resized later, or when only a minimum count is known for a collection literal, the compiler could for example set the initial capacity to the best-fitting power of two. This would skip some early resizes while also amortizing resizes that could happen if the collection is modified after the collection literal is created. ### AddRange and boxing When a struct-typed collection is spread, codegen could call `AddRange(IEnumerable)` on the collection being constructed. This would cause the collection being spread to be boxed. To avoid boxing, codegen could loop over the spread collection and call `Add(T)` repeatedly on the collection being constructed. This would work fine when the constructed collection has a writeable Capacity property because codegen could set the capacity before calling `Add(T)` in a loop. However, if there's no writeable Capacity property, we're better off calling `AddRange` and boxing, because the `AddRange` implementation is the only way to intelligently avoid intermediate resizes. (In this situation, the thing being spread has a known length but the collection literal as a whole has an unknown length due to an earlier spread.) #### Conclusion (AddRange and boxing) Stick with building using Add and AddRange. This is good enough for 95% of customers. Allow the compiler to special-case codegen for widely-used collections to improve performance. The compiler should prefer calling `AddRange(ReadOnlySpan)` if such a method exists and the thing being spread is a span or exposes a span. ### Construction API pattern The ideal API for constructing collections with contiguous backing storage is to pass the capacity and receive a span to the backing storage. The collection could be passed back using an `out` parameter to enable overloading on collection type. The type parameter to use when calling the creation method could be the iteration type of the collection being constructed. ```cs [CollectionLiteralBuilder( typeof(CollectionsMarshal), nameof(CollectionsMarshal.CreateUnsafe))] public class MyCollection : IEnumerable { } public static class CollectionsMarshal { public static void CreateUnsafe( int capacity, out MyCollection collection, out Span storage); public static void CreateUnsafe( int capacity, out List collection, out Span storage); } ``` The reverse API pattern (passing a span to the collection rather than receiving one) would enable the construction of collections that have noncontiguous backing storage and do not have mutating Add/AddRange methods, such as ImmutableDictionary. This would allow the use of existing Create methods in the BCL: ```cs [CollectionLiteralBuilder( typeof(MyCollection), nameof(MyCollection.Create))] public class MyCollection : IEnumerable { } public static class MyCollection { public static MyCollection Create(ReadOnlySpan elements); } ``` #### Conclusion (construction API pattern) Use an attribute to point to a creation method similar to the code samples above. If this attribute is present, prefer it over generating Add/AddRange calls. This will need to go through API review. If we don't ship this general facility in C# 12, special-case access to the backing spans of List and ImmutableArray anyway for the sake of performance. ### Dictionary sigils In a previous full LDM, some folks didn't like the idea that `[..dict1, ..dict2]` would create a dictionary (unless the target type is a dictionary type). A merged dictionary could change the count and enumerated order of the key-value pairs. Similar concerns apply to `[kvp1, kvp2, kvp3]`. The working group doesn't expect these syntaxes to be commonly desired for producing dictionaries. We expect at least one `k: v` element to be present in literals where the user wants a dictionary and the target type is not already a dictionary type. Merging two dictionaries to create a new dictionary, and doing nothing further, is not a scenario we think comes up that often. So, is there a place for a sigil to override this, where syntax similar to `[:, ..dict1, ..dict2]` or `[:, kvp1, kvp2]` causes a dictionary to be created instead of a list of key-value pairs? An existing way to override this would be `[..dict1, ..dict2].ToDictionary()`. This is something which users may do anyway to override the natural type in other situations, such as `var x = [a, b].ToImmutableArray();`. There's an open question about whether the natural type is influenced by the collection types of spread collections. Depending on the resolution, `[..dict1, ..dict2]` may create a dictionary while `[..listOfKvps1, ..listOfKvps2]` does not. A dictionary sigil would then be addressing a rarer set of scenarios. #### Conclusion (dictionary sigils) The working group doesn't find the sigil use cases compelling, and it can be added later. ### Cut line for C# 12 The working group feels good about shipping these features in C# 12: * Target-typing to core collections and collections which support classic collection initializers * An opt-in API construction pattern to enable 1️⃣ target-typing to other collections and 2️⃣ improving performance over classic collection initializers * Spreads * Dictionary literals We'd like this in C# 12, but we'd rather let this slip to C# 13 than anything in the previous list: * Natural type for non-empty literals * We think performance concerns about `List` have a great resolution after input from the runtime on the "optimize away `List`" strategy. * Design discussion is still needed. For example, does the collection type of a spread collection affect the natural type of the collection literal? Not interested in pursuing for C# 12: * Natural type for empty literals * This requires forward-looking type inference. ## Next steps We will present takeaways of recent working group meetings in a full language design meeting and get feedback on those and on some open questions. ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-06-12.md ================================================ # Collection literals working group meeting for June 12, 2023 ## Agenda * API shape for BCL collections to opt-into collection literals support. ## Discussion LDM and Runtime members met and discussed the shapes we think we'll want in the runtime to allow a type to either opt into collection-literals support entirely, or to opt into an extremely efficient approach to constructing an instance of itself. Note: for the following discussion, the code shown is a rough approximation of what we think is needed. Exact naming, order of arguments, `return` vs `out` discussion will happen later. All the approaches require the following attribute to be defined: ```c# [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)) public sealed class CollectionBuilderAttribute(Type factoryType, string methodName) : System.Attribute { public readonly Type FactoryType; public readonly string MethodName; } ``` When present on a type `T`, this identifies a static factory method `MethodName` found on type `FactoryType` which can be used to construct an instance of `T`. Several factory shapes will be supported. Note: The type `T` and the `FactoryType` are allowed to be the same type. These shapes may also be expanded upon in the future if motivating scenarios exist for them. ### Shape 1: `ReadOnlySpan` as input The first shape we support is the following: ```c# [CollectionBuilder(typeof(ImmutableHashSet), "Create")] public class ImmutableHashSet { /*...*/ } public static class ImmutableHashSet { public static ImmutableHashSet Create(ReadOnlySpan values); } ``` Here, the factory method takes a `ReadOnlySpan` of values and returns an instance of the final collection. The compiler would then translate in the following fashion: ```c# ImmutableHashSet values = [1, 2, 3]; // translates to: ReadOnlySpan __values = [1, 2, 3]; ImmutableHashSet values = ImmutableHashSet.Create(__values); ``` This pattern will allow pointing at methods that already exist for this purpose, as well as allowing the runtime to add new methods to existing types to allow them to be created from spans. A similar shape that we want to support is practically the same as the above, except that the factory method can be a constructor of the type. Continuing the example above, that would be: ```c# [CollectionBuilder(typeof(ImmutableHashSet<>), ".ctor")] public class ImmutableHashSet { public ImmutableHashSet(ReadOnlySpan values); } // code: ImmutableHashSet values = [1, 2, 3]; // translates to: ReadOnlySpan __values = [1, 2, 3]; ImmutableHashSet values = new ImmutableHashSet(__values); ``` #### Shape 1 consequences The above approach is desired by the runtime so they can add overloads to their existing construction methods that allow the more efficient use of spans over more expensive collection types (arrays, IEnumerables, etc.). However, this raises a problem given that these types normally have existing construction methods that take an `IEnumerable`. Consider, for example: ```c# public class HashSet { // existing constructor public HashSet(IEnumerable values); // newly added public HashSet(ReadOnlySpan values); } // code: new HashSet(new int[] { 1, 2, 3 }); ``` With the rules of the language today, the above is ambiguous. This is unfortunate as a prime reason to create Span overloads of methods is to allow for efficient options for callers that do not need to use the heap. We believe this should be fixed, and the WG will present a proposal to the LDM to add overload resolution rules that consider Spans better than items in the inheritance chain for arrays. ### Shape 2: Writable `Span` or `T[]` storage. While the above pattern is good for collection types that own their own storage arrangement, it still involves producing the sequence one end, and then processing it to produce the final result within the factory method. This is unnecessary overhead for a couple of very important, widely used, collections in the .NET BCL. Specifically, `List` and `ImmutableArray`. Both of these types are thin wrappers around a backing array, and it is highly desirable to just be able to write directly into that array without any overhead at all. To that end, the above attribute can be used to point to a factory method with the following shape: ```c# [CollectionBuilder(typeof(CollectionsMarshall), "Create")] public struct ImmutableArray { /*...*/ } // In System.Runtime.CompilerServices public static class CollectionsMarshall { public static Span Create(int capacity, out ImmutableArray result); // or: public static void Create(int capacity, out ImmutableArray result, out Span storage); } // code: ImmutableArray values = ["a", "b", "c"]; // translation: Span __storage = CollectionsMarshal.Create(capacity: 3, out ImmutableArray values); __storage[0] = "a"; __storage[1] = "b"; __storage[2] = "c"; ``` Depending on how the compiler encodes constant information, this could also be a case where the data is stored in the read-only section of the dll and memcpy'ed to the destination. Note: the runtime already has methods that expose this data for high performance scenarios. So this does not introduce any new safety concerns. The methods are intentionally kept out of the way in the `CompilerServices` namespace to help indicate they are not for normal use, and should be treated very carefully. #### Shape 2 consequences The above pattern will not work as efficiently in the case where the Span cannot be kept on the stack while evaluating the elements of the collection literal (for example, if there are `await` calls in the literal. An alternative/complimentary approach that could be offered would be to have the following: ```c# public static class CollectionsMarshall { public static void Create(int capacity, out ImmutableArray result, out Span storage); // or: public static void Create(int capacity, out ImmutableArray result, out T[] storage); } // code: ImmutableArray values = [await a, await b, await c]; // translation: CollectionsMarshal.Create(capacity: 3, out ImmutableArray values, out string[] __storage); __storage[0] = await a; __storage[1] = await b; __storage[2] = await c; ``` During discussion, there was mixed feeling on this. Sentiment indicated that it somehow felt worse to be handing out the array directly, where any consumer could then hold onto it forever. While exposing the Span still gave access to all the data, it felt slightly more palatable as the Span was limited to the stack. However, not having the array option means the compiler would have to translate like so: ```c# // code: ImmutableArray values = [await a, await b, await c]; // translation: string[] __temp = new string[] { await a, await b, await c }; CollectionsMarshal.Create(capacity: 3, out ImmutableArray values, out Span __storage); __temp.CopyTo(__storage); ``` This array seems wasteful, and could occur reasonably often in normal coding patterns. ### Builders WG discussed if we should support a more flexible 'builder' pattern. Both WG and Runtime felt this was unnecessary. As per earlier decisions, it can also come in the future if there is a compelling case where they are needed. ## Next steps 1. LDM and Runtime to continue to drive the final shape for these APIs, and the work to get them into the runtime. 2. WG to go back to LDM to request overload resolution tweaks for `ReadOnlySpan` and `IEnumerable`. ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-06-26.md ================================================ # Collection literals working group meeting for June 26, 2023 ## Agenda * Collection expression lifetimes * BCL apis * Breaking changes * Extension method lookup * Syntactic disambiguation ## Discussion ### Collection expression lifetimes Relevant link: https://gist.github.com/jaredpar/a784f8963ce73613a0a696ef89a00d9a Discussed the rules about spans produced by collection literals, and whether those are 'safe to return' from the 'current method'. Conclusion. We will accept the rules in the provided link. Specifically, spans from collection literals are *not* "Safe to return from current method". This has the benefit of allowing the compiler to potentially stackalloc these spans for very high efficiency code (including cases like `G([1, 2, 3])`). While the compiler is free to use stackallocs for these spans, it is not required to do so. However, as some consumers *may* only want to allow collection literals when they will be stackalloc'ed we will ensure that the compiler emits a hidden-diagnostic if it heap allocs. These consumers can set that diagnostic to be an error, blocking unwanted span heap allocations. In those cases, they can manually stackalloc. Users who want to be able to return those spans can do so by explicit conversion to an array. e.g. `Span s = (int[])[1, 2, 3];`. ### BCL Apis BCL gave a status update on the work they have done. Specifically: https://github.com/dotnet/runtime/pull/87945 This (merged) PR adds the pattern of an immutable collection type `C` having a factory method `public static C C.Create(ReadOnlySpan values);` to create the collection. A soon to be added attribute (`CollectionBuilderAttribute`) will be added that will allow the language/compiler to find this factory method from a given collection type. We do not plan any more BCL apis beyond this. However, there are two key changes we will make in the compiler in terms of emit, to get even better codegen than what those above methods allow. Specifically: 1. while there is an `public static ImmutableArray ImmutableArray.Create(ReadOnlySpan values);` method, the compiler will not call it. Instead, it will sidestep it and use `ImmutableArray ImmutableCollectionsMarshal.ToImmutableArray(T[] values);` method. This method passes ownership of the array *directly* to `ImmutableArray` ensuring no overhead (no excess allocations *or* copying). 2. while `List` will already work with collection literals (due to it supporting collection-initializers) the compiler will emit new collection literal `List's` as: ```c# List list = [a, b, c]; // translates to: List list = new List(capacity: 3); CollectionsMarshal.SetCount(list, 3); Span __storage = CollectionsMarshal.AsSpan(list); __storage[0] = a; __storage[1] = b; __storage[2] = c; ``` Rule '1' will always apply for immutable arrays. Rule '2' can apply depending on how complex it is for the compiler. For example, if the code were `List list] = [await a, await b, await c];` the compiler might choose to just emit this equivalently to `new List { await a, await b, await c }` (i.e. equivalently to a collection initializer expression). ### Breaking changes We would like to update the language so that `(A)[B]` is *always* a cast of a collection literal, not an indexing expression of a trivial identifier expression. This would apply to `(A)[B]` (trivial identifier), or `(A.B.C)[D]` (dotted identifier). In both of these cases, if the user wanted indexing, it would be far more idiomatic to write this out as `A[B]` or `A.B.C[D]` (the parentheses are superfluous). THe compelling case to want to make this a cast of a collection literal is: ```c# using CrazyArray = ImmutableArray>>; // i.e. some complex collection type // ... var v1 = (CrazyArray)[a, b, c]; // really don't want to have to spell out the type in each location var v2 = (CrazyArray)[x, y, z]; ``` Currently the prototype impl makes no breaks here and keeps parsing as it exists prior to this feature. As this is a breaking change, we might want to combine it with the work on "safe breaking changes" being looked into. However, we also think the chance of this impacting someone would be super small (though investigations would be necessary). We want to get a read from LDM on if we should explore this fruther. ### Extension method lookup WG believes it would be valuble for customers to be able to write: ```c# static class Extensions { public static ImmutableArray AsImmutableArray(this ImmutableArray array) => array; } void M() { var v = [complex, type, examples].AsImmutableArray(); } ``` This is because the alternative would be to have to write something like: ```c# (ImmutableArray>)[complex, type, examples]; ``` In other words, the extension method approach would allow for type inference to make this much simpler and cleaner than having to fully specify all the types. Working group believes (but needs to verify) that a simple extension (no pun intended) to 'extension method lookup' would make this work. Specifically, while the collection type has no natural type currently (like `null`, a `lambda` or a `method group`) it could still trivially participate in extension method lookup and would work in cases like this. We do, however, believe the value here is diminished if, say, the language adopts features like "postfix cast" and "partial generic type inference". If those features make progress, you could write the above as: ```[complex, type, examples].(ImmutableArray<_>)```. This would be simple and clear. WG also thinks that if we did add extension method support for the above, that we could consider doing the same for lambdas and method-groups. ### Syntactic disambiguation update The feature has some small syntactic ambiguities with both existing features, and features coming around at the same time. Specifically: Case 1: ```c# a ? [b] ... // is that: (a?[b]) // a conditional access expression, or: a ? [b] : ... // a conditional expression ``` Conclusion: we will preserve existing parsing for these. So if it is a case where it would successfully parse as a conditional-access-expression, it will remain that way. *However*, if trying that approach would end up failing, we will try again as a conditional-expression and accept that if it succeeds. This means the following will work: ```c# var v1 = a ? [b] : c; var v2 = [a ? [b] : c]; ``` While this involves complex lookahead, we only need to apply it when parsing fails *and* we can see that there was a `?[` involved. So costs should be minimal for mainline cases. A PR for this is here: https://github.com/dotnet/roslyn/pull/68756 Case2: The collection literals feature *also* introduces its own ambiguity with: `[a ? [b] : c]` is that `[(a?[b]): c]` (a dictionary literal?), or `[a ? ([b]) : (c)]` a collection literal with a conditional-access in it. Unlike the above case, there is no back compat issue here as none of this code was legal before. In this case, we choose an interpretation that prefers list-literals first, then dictionary literals. So the above is a list-literal containing a conditional-access-expression. If a dictionary-literal is preferred, the initial expression must be parenthesized. Case3: In `[range1, ..x, range2]`, `..x` could be a spread-element or a range-expression. WG affirms this is always a spread-element. This should be an extremely unlikely event. And, if the user wants a range-expression they can write `0..x` or `(..x)`. ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-07-26.md ================================================ # Collection literals working group meeting for July 26, 2023 ## Agenda * `[CollectionBuilder]` Create methods returning a derived/implementing type * Considering extension `GetEnumerator()` when determining element type * Inline array sizes to synthesize ## Discussion ### `[CollectionBuilder]` Create methods returning a derived/implementing type For the BCL, this mainly concerns the `IImmutableXyz` interfaces. The compiler will have its own rules for which concrete types to use when a collection expression targets certain interfaces such as `IReadOnlyList`. If we don't allow `[CollectionBuilder]` to be placed on the `IImmutableXyz` interfaces, they won't be able to be targeted by collection expressions. We're lukewarm on the importance of the `IImmutableXyz` interfaces themselves, but we don't feel great about blocking people from making their own interface type constructible using `[CollectionBuilder]`. It feels like a toe-stubbing situation, and the cost to the compiler to allow this is very low. Then if we do allow `[CollectionBuilder]` to be placed interface types, the runtime would go ahead with adding `[CollectionBuilder]` to the `IImmutableXyz` interfaces so long as it would be valid for the attribute to point to the existing Create methods that return concrete types. For instance, `[CollectionBuilder]` on `IImmutableList` would point to the Create method whose return type is the concrete `ImmutableList` rather than the interface type. We would thus want to do both things together: allow `[CollectionBuilder]` on interface types, and allow the Create method to return a type which derives from or implements the attributed type. **Conclusion:** Ask at a language design meeting whether to allow `[CollectionBuilder]` on interface types. If the answer is yes, the runtime is only willing to make use of this if the attribute can point to Create methods whose return types are concrete implementing types rather than interface types. ### Considering extension `GetEnumerator()` when determining element type The spec currently says that the element type of a collection literal is the iteration type of the collection type. However, there's a downside to considering extension `GetEnumerator` methods: you can no longer tell whether the collection type is a valid target for a collection expression by looking at the collection type declaration itself. The iteration type of that collection type is dependent on the `using` directives where the collection expression is written because the `using` directives may or may not bring in a `GetEnumerator` extension method. This makes it impossible for the compiler to provide a diagnostic for an obvious mistake where `[CollectionBuilder]` is applied to a type that declares no `GetEnumerator` method of its own. After all, a usage site could just happen to import an extension method which makes collection expressions compile when targeted to this collection type. If we rule this out by disallowing extension `GetEnumerator`, the situation becomes unequivocally invalid, and all diagnostics can be provided on the collection type declaration rather than the usages of collection expressions with that collection type. **Conclusion:** Do not consider extension `GetEnumerator` methods when determining the element type of a collection expression. Provide an error diagnostic if `[CollectionBuilder]` is applied to a type that doesn't declare its own `GetEnumerator` method. ### Inline array sizes to synthesize The current plan is to use [inline arrays](https://github.com/dotnet/csharplang/blob/main/proposals/inline-arrays.md) in the compiler-generated code. (`stackalloc` complicates codegen because the operation should be hoisted outside of loops. It also only works for unmanaged element types.) An inline array requires a different type to be declared for each array length. If the BCL declares some well-known inline array types for certain array lengths, the compiler can use those, but array lengths not declared by the BCL require the compiler to generate types in the private implementation details of the assembly being compiled. A lot of inline array types could end up being generated per assembly which uses collection expressions. Do we want to try to cut down on the number of inline array types, for instance by only generating in powers of two and overallocating on the stack but slicing to the needed size? Also, should there be a cutoff where above a certain size, the heap is always used? The performance downside of overallocating (using oversized inline arrays) is time spent zeroing memory. We don't want to leave performance on the table. The number of types being generated is not a concern. The compiler already generates large numbers of types of various fixed sizes for other features. If there are too many after all, the BCL will declare some of these. We're not going to get it right on the first release. We can start simple, react to performance feedback from C# 12, and change the strategy as needed. **Conclusion:** When the element count is known at compile time, always use an exactly-sized inline array and never use the heap. When the element count can vary at runtime, always allocate on the heap. ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-08-03.md ================================================ # Collection literals working group meeting for August 3, 2023 ## Agenda * Collection expression escape scope for ref-structs (like `ReadOnlySpan`). * Confirm behavior of collection expressions and `ReadOnlySpan`s of blittable data. ## Discussion The purpose of the meeting was to discuss what semantics we would have in the language for a collection expression target-typed to a ref-struct. First, for simplicity and to aid by giving examples, we'll use `ReadOnlySpan` to demonstrate the general problem space, the varying options, and the pros/cons of each option. Second, all these cases deal with collection expression elements of *non-blittable-constant data* (e.g. `[a, b, c]` vs `[1, 2, 3]`). Rules around the latter are covered at the end. Today, a user can write code like so: ```c# ReadOnlySpan x1 = stackalloc int[] { a, b, c }; // stack allocated, local scope. ReadOnlySpan x2 = new int[] { a, b, c }; // heap allocated, global scope. ``` Based on how the variable is initialized, the language then dictates rules about how it can be used. For example, with the above code it would be illegal to `return x1;` from the method (since it has local scope), while it would be fine to `return x2;`. Variables without an initializer are assumed to have global scope. So the following, for example, would be illegal: ```c# ReadOnlySpan x3; // global scope x3 = stackalloc int[] { a, b, c }; // illegal. global scope now points at local data. ``` Unlike existing constructs, which clearly delineate whether they are on the stack or heap, a decision needs to be made as to what the following means: ```c# ReadOnlySpan x4 = [a, b, c]; ``` The working group considered several options. ### Option 1. Stack-alloc'ed only if target has local-scope. The first option considered was that a collection expression used with a `ReadOnlySpan` would itself only be stack-alloc'ed if the span itself was known to be local scoped based on its declaration. In other words, the following: ```c# ReadOnlySpan x1 = [a, b, c]; // heap allocated, global scope. scoped ReadonlySpan x2 = [a, b, c]; // stack allocated, local scope. // Less likely, but shown for completeness: ReadOnlySpan x3 = stackalloc int[] { d, e, f }; // stack allocated, local scope. x3 = [g, h, i]; // stack allocated. ``` This has the positive that both heap and local allocations are possible, and choosing between them is as simple as adding `scoped` or not. Unfortunately it has several major negatives the working group was quite dissatisfied with: 1. We believe the *common* case for using spans with collection expressions will be for people wanting stack allocated data. Having the default (non-`scoped`) syntax basically flips that and makes the common case more cluttered and less pleasant to work with. 2. We believe the standard intuition for nearly all users (including experts in the low-level space) would be that the first line produces stack-allocated data. After all, that's the most common reason to create locals of span types. Their belief would be that if they initialize with a collection expression, it will do the right thing. 3. We believe that the first line would actually be a *large* "pit of fail" for users. It would be very easy and attractive for customers to just take existing code, hear about collection expressions (including that it works for spans) and translate `stackalloc` code over to just using the collection expression, resulting in allocations that didn't exist before. 4. We believe if we had this behavior, we would have to have a diagnostic (likely on by default) that warned the user of the heap allocation on 'x1' and to either: 1. suppress the diagnostic if they really wanted that, or 2. put an explicit cast (e.g. `(int[])[a, b, c]`) to indicate that they indeed did want this behavior, or 3. add the `scoped` modifier. All in all, while this allowed for both cases to easily be distinguished with a simple modifier, everything about the defaults and behavior was flipped from where we wanted it to be. This then led us to option 2: ### Option 2. Determine what scoping a target has based on usage. A discussion has been going on about allowing the user to just write: ```c# ReadOnlySpan x1 = [a, b, c]; // Could be local or global scoped scoped ReadOnlySpan x2 = [a, b, c]; // Always local scoped. ``` and then determining the scoping based on how the variable is used. For example, if 'x1' does not escape, then it could be stack allocated. If it does end up escaping though (for example with `return x1;`), the collection expression would be heap allocated. 'x2' would always be local scoped of course and would be prevented from escaping. The benefit of this approach would be allowing the user to always write the simple form with a collection expression, and then get the semantics they needed to make the code legal. This was attractive because of the simplicity of the user technically writing simple code, and then not having any stumbling blocks. However, we felt this was actually a net negative. A significant problem we foresee with this approach is that users will have code that is fast and nicely stack-allocating as they expect, and then some innocuous looking change ends up causing the compiler to conservatively think it may escape, causing the collection to now be heap allocated. This could happen silently, easily tanking program performance. Or, it could come with a warning. But in the latter case, users would then have to suppress or introduce explicit casts. Similar to option 1, it felt very strange for us to introduce behavior where the compiler would heap allocate for the user only to warn, since in the majority of times it was believed users would not want that. After all, in the case of heap allocating, one might as well just choose a heap allocated type *to begin with*. These concerns led us to our final option: ### Option 3. Always stack-alloc for ref-struct target type. We then moved to a discussion around our belief that when using ref-structs, and collection expressions, users would expect that data to be put on the stack. Exploring this led to the following semantics: ```c# ReadOnlySpan x1 = [a, b, c]; // stack allocated, local scope. scoped ReadonlySpan x2 = [a, b, c]; // stack allocated, local scope. ``` In other words, the above two have the same meaning. With the latter just being explicit about the local scoping. Returning either of those from a method would be illegal. This has the benefit that the simple form matches the intuition nearly all users will want this to have, and will provide the best performance by default. This heavily aligns with the stated goal of collection expressions (which we will continue to evangelize it with) that it makes the right default choices and by using it you should always (*ideally*) be getting the best performance results. This does have some downsides though. First, if the user did want to return the ref struct, they would now be blocked. The workaround for this would be to instead be explicit that you wanted heap allocation for this span (e.g. `ReadOnlySpan x1 = (int[])[1, 2, 3];`). However, we see this explicitness as a *good* thing. When working with spans and collection-expressions, we feel that having code be explicit when it is on the heap is actually a desirable documentary property for it to have. And, while the cast syntax is not attractive, we could consider a simpler way of expressing that in the future (e.g. `new [1, 2, 3]`), where the syntax was light, but clear about intent. Second, the aforementioned redundancy if you actually include `scoped`. We could consider warning or blocking that to prevent confusion and to help convey that the data is always scoped. That said, we already have this redundancy with `scoped ReadOnlySpan x = stackalloc int[] { ... };` and we give no warning. So we likely can just do the same here. This approach also has the benefit that no diagnostic need ever be created for a span-targeted collection expression actually being heap allocated. As we will always stack-allocate that simply is never a concern. ## Decision: Working group goes with option 3. Collection expressions targeted to ref-struct types are always stack allocated. Note that this covers more than just `Span/ReadOnlySpan`. For example, if one had the following: ```c# [CollectionBuilder(FrugalListBuilder)] ref struct FrugalList { } static class FrugalListBuilder { public static FrugalList Create(ReadOnlySpan values); } // ... FrugalList f = [a, b, c]; // Stack-allocated, local scope. ``` We believe that this is the semantics ref-struct-collections will want. And, similar to spans, the semantics users would expect to get *without* having to annotate with `scoped` on the local variable. ## Blittable Data The above discussion applied to collection expressions, target typed to ref-structs, *without* constant, blittable data in it. Blittable data is effectively defined as C# builtin of 1-8 bytes in size. Intuitively, the primitive integers, floating points, and characters. Today, the language already allows optimizing the following: ```c# private static ReadOnlySpan s_chars = new char[] { 'a', 'b', 'c' }; ``` Despite this explicitly having a `new` in the code, and explicitly referencing the `char[]` type, the above will be compiled without any allocations or array types. Instead, the data will be embedded directly into the data segment of the compiled assembly, and the span will be created pointing directly at that raw data. We will have those rules apply when using a collection expression as well. This means that the following will be legal and will have the same behavior at runtime: ```c# private static ReadOnlySpan s_chars = ['a', 'b', 'c']; ``` In other words, if a collection literal has nothing but constant blittable data, and it is target typed to a `ReadOnlySpan` *only*, then it will actually be considered to have `global` not `local` scope. This means that the following would be allowed: ```c# ReadOnlySpan x = [1, 2, 3]; // pointer to data segment, global scope. return x; ``` No decisions were needed on this. Working group just affirmed these are the desired semantics and these rules and optimizations will be maintained with collection expressions. ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-08-10.md ================================================ # Collection literals working group meeting for August 10, 2023 ## Agenda * Determine behavior when target runtime does not support Inline-Arrays. ## Discussion The purpose of the meeting was to go over the decisions made in the last WG meeting (https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-08-03.md) and consider their impact in the context of users targeting runtimes which do not support inline-arrays. As a reminder, the final conclusion from that meeting was: > Working group goes with option 3. Collection expressions targeted to ref-struct types are always stack allocated. Currently, the approach we are intending to take for targeting something like `Span/ReadOnlySpan` is to use the [inline-arrays](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/inline-arrays.md) feature to synthesize an inline-array to place the data to, and then obtain a span from it. However, inline-arrays are only available on Net8.0 and up. Further discussion on this topic raised concerns from several participants that this approach was too onerous in practice. Specifically, that a core use case in modern .NET is for users to multi-target their code. This multi-targeting appears in practice with users targeting frameworks like netstandard2.0 and netX.0, or just multiple versions of netX.0 (like net6.0/net7.0/net8.0/etc.). This raises a dilemma where users who are multi-targeting are then shut out from being able to use collection expressions with spans. Stack allocating, though, is not a mandatory requirement. It is possible for the compiler to still generate code in these cases. Specifically, by allocating an array (on the heap) and storing it in the span. Note: this would not change the local-versus-global scope aspect of the span. Whether the span has local or global scope would use the same rules decided from the last working group meeting. It would just be the case that a local-scoped span would be stack-allocated on targets that supported inline-arrays, but heap allocated on targets that did not. This is necessary so that a user moving from a prior runtime (without inline-array support) to a current runtime would not find their scopes become more restrictive, certainly breaking user code that had come to depend on the global scope from before. Vigorous debate about this followed, with concerns both about shutting people out from being able to use collection expressions in these real-world cases, while also not wanting usage of collection expressions to silently cause users to go over invisible performance cliffs. Ultimately, we concluded that shutting people off from using collection literals in multi-targeting scenarios was not acceptable. Rather, users should be in control and should be able to make an informed choice about what best strategy to take in these circumstances. To that end though, we felt strongly that users not run into a silent performance cliff. As such, our final determination was: 1. No change in global/local scoping rules for spans and collection expresssions 1. No change in rules around blittable collections assigned to `ReadOnlySpan`` (they remain global scoped, and will be read from the data segment of a dll) 1. On target frameworks that support inline-arrays, we will use them and target the stack as the location where the data is actually stored. 1. On target frameworks that do not support inline-arrays, we will *fall back* to allocating an array on the heap and having the span point at that array. We will *also* issue a warning in this case to make the user aware of the allocation. The final two rules apply to the spans created as temporary storage for the `collection builder` pattern as well. Examples: ```c# ReadOnlySpan A() { ReadOnlySpan x = [GetInt(), GetInt(), GetInt()]; // Always illegal on any framework. 'x' has local scope. return x; } ReadOnlySpan B() { ReadOnlySpan x = [1, 2, 3]; // Always legal. 'x' has global scope due to being a ReadOnlySpan of constant blittable data. return x; } void C() { // Stack allocated (using Inline-Array on Net8 and up) // // Heap allocated (using an int[] on Net7 and below). Will warn in that event. ReadOnlySpan x = [Get(), Get(), Get()]; } void D() { // Temporary ReadOnlySpan is stack allocated (using Inline-Array on Net8 and up) // // Temporary ReadOnlySpan is heap allocated (using an int[] on Net7 and below). Will warn in that event. ImmutableList x = [Get(), Get(), Get()]; } ``` ================================================ FILE: meetings/working-groups/collection-literals/CL-2023-08-11.md ================================================ # Collection literals working group meeting for August 11, 2023 ## Agenda * Implementations used when targeting interface types ## Discussion Following up from the Aug 8th language design meeting, the working group considered specific concrete implementations to use for each interface that collection expressions can target. We reviewed the [Compiler-synthesized types](https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/Compiler-synthesized-types.md) proposal in preparation for upcoming language design meetings which will get into more of the details of the collection expressions feature. The language design meeting on Aug 8th concluded that we can later change the concrete type created for interfaces even after people start using the feature. For now, we'll decide on a starting point to recommend which we feel comfortable with. ### Targeting mutable interfaces When a collection expression targets `IList` or `ICollection`, we'll construct a `List`. Even if people start depending on it, we could still change it in the future, similar to how .NET Core changed the concrete type returned from `Enumerable.Empty()`. ### Targeting readonly interfaces These are `IEnumerable`, `IReadOnlyCollection`, and `IReadOnlyList`. Empty collections will get whatever instance the framework returns from `Array.Empty()`. For non-empty collections targeting readonly interfaces, we considered three alternatives: A: Synthesized type with a field of type `IList` or `List` B: Just use the BCL's `ReadOnlyCollection` type C: Use `List` for unknown-length construction and `T[]` for known-length. Alternative C is great for performance but loses the safety that is a selling point to a significant part of our audience. Alternative B adds in the possibility for folks to cast and depend on the exact type (even though we do reserve the right to change the concrete type anyway), but it also doesn't have some of the benefits of alternative A; there are things we could do better in private implementations such as sealing it and wrapping a `List` field rather than `IList`, gaining better codegen with devirtualization and inlining. With the extra indirection, indexing through a readonly wrapper is measurably worse than on an unwrapped array, twice as slow in a benchmark. On the other hand, wrappers add no cost to enumeration via `foreach` and `IEnumerable` over the wrapped collection because the wrapped collection's enumerator can be returned directly through the wrapper. For known-length collections, it may be worthwhile to construct and wrap a `T[]` rather than a `List`, or even synthesize interface implementations with hardcoded counts and hardcoded fields for elements for certain sizes. `T[]` wouldn't be great for unknown-length collections since arrays must be exactly sized, often requiring a final copy from a larger-sized buffer that was used to hold the items while the length wasn't yet known. We could wrap an `IList` field to handle both, or we could generate different wrappers to contain a `List` field or a `T[]` field for the two situations as needed. Even if the target type is only `IEnumerable`, the synthesized types should implement `IReadOnlyList`, `IList`, and nongeneric `IList`. This is in case of subsequent usage against runtime checks, such as data binding or LINQ optimizations. It could result in bigger generated code size that isn't trimmable. Method bodies could be shared though, since most of the `IList` and nongeneric `IList` members are mutating members which contractually should just throw `NotSupportedException`. ## Conclusion For `ICollection` or `IList`, we will build a new `List`. For (non-empty) `IEnumerable`, `IReadOnlyCollection`, or `IReadOnlyList`, initially we will synthesize a private type within the assembly which is similar to the framework's `System.Collections.ObjectModel.ReadOnlyCollection`. This is a straightforward starting point which prevents casting to the framework class. The design space is open and the compiler can create specialized classes later in a data-driven fashion. In C# 13 when we get to dictionaries, `IDictionary` will be a new `Dictionary`, and `IReadOnlyDictionary` will follow a strategy consistent with the strategy for `IReadOnlyList`, such as synthesizing a private type similar to `System.Collections.ObjectModel.ReadOnlyDictionary`. ================================================ FILE: meetings/working-groups/collection-literals/CL-2024-01-23.md ================================================ # Collection expressions: design restart meeting 01/23/24 The collection expressions group met again for the first time after shipping C# 12 and going through the holidays. The agenda was to discuss and outline all the interesting areas we want to consider in C# 13 (along with a loose prioritization). ## C# 13 Collection expression design areas (loosely prioritized) ### Dictionary expressions Broadly speaking, we consider dictionary expressions (tentatively `[k1:v1, k2:v2]`) to be the most important design space for us to front load. Specifically, because it both seems like the area of collections most commonly used in the BCL and in APIs, but which has no support today with collection expressions. We also believe that the decisions made here will impact any decisions we make in the [Natural Type](#natural-type) space. Broken down, we see important design needing to be made in the following areas: 1. Pattern correspondence. Collection literals started with a strong emphasis on the construction side needing to have parity with the pattern-matching side. e.g. `x = [a, b, .. c]` and `x is [var x, var b, .. var c]`. We strongly think that this should be kept in mind when it comes to dictionary-expressions as well. That said, dictionary-patterns will be quite interesting in their own right and have several design challenges for them. For example: - We have to cognizant that we likely should only offer patterns that have a sensible translation. For example: `x is [var key: 0, ..]` is not likely something that could be implemented (as it would need a linear walk of most dictionaries to find all the elements). - Similarly, `x is [key1: 0, .. var rest]` would likely be extremely expensive, as it would require somehow cloning everything, and removing elements that matched. - Similarly, certain types of constructs would likely not make sense, like `x is [key1: 0, .., key2: 1]`. Specifically, as dictionaries are not ordered, allowing `slice` patterns in the middle is somewhat nonsensical as there is no before/after concept. All of the above likely means that the pattern form that dictionaries should support would be limited to: `[(constant-pattern: pattern)+, (..)?]`. In other words, some amount of key/value patterns, where the keys are all constants, and the values associated with those keys are then matched against a pattern, followed by an optional spread, to indicate if the dictionary can have more elements or not beyond those which are matched. 2. The overlap/duality between lists and dictionaries, and what should `k:v` mean in that world. For example, we could consider things in the following ways: - `k:v` is simply a shorthand for `KeyValuePair`. And as such, the following would be legal: `List> nameToAgeList = ["mads": 21]`. This allows for the possibility that `((Collection)[k:v])[k]` may not yield `v`. - `k:v` represents a key-value *association*, and thus should only be usable with dictionary-esque (tbd) types, not sequence-esque types. This enforces the intuition that `((Collection)[k:v])[k]` yields `v`. As part of this, we want to figure out what expressions elements can mix/match with what target types. For example (as shown above) can a `k:v` element be used with a sequence-type? Can a normal expression element be used with a dictionary type (e.g. `Dictionary x = [kvp1, kvp2]`)? 3. The semantics of dictionary expressions wrt 'adding' vs 'overwriting' key/value associations. We could go with the simple approach of adding each association one at a time, left to right. This would align both with how collection-expressions work today for sequence collections, as well as how dictionary-initializers work (e.g. `new D { { "mads": 21 }, { "cyrus": 22 } }`). However, there is a definite feeling that this approach is limiting and cuts out certain valuable scenarios, all specifically around `spread` elements. For example: ``` // Making a copy, but overwriting some elements Dictionary dest = [.. source, "mads": 21]; // Having some default values, but allowing them to be supplanted. Dictionary dest = ["mads": 21, .. source]; // Merging two dictionaries, with the later ones winning. Dictionary dest = [.. source1, .. source2]; ``` Design group feels that they've run into all these situations, and they're generally useful. Having 'Add+Throw' semantics here would be painfully limiting. If we do decide on 'overwrite' semantics, we could consider having the compiler warn though in the case of multiple `k:v` elements, with the same constant value for `k`. 4. Adopting `JSON` syntax here. Specifically, allowing `{ "key": value }` to work as legal dictionary-expression syntax. Working group leans no, but we definitely want to run by LDM for thoughts. - Pros: Great parity with a very popular data format. This would allow users to also instantiate Newtonsoft J-Etcs or System.Text.JXXX types just with real JSON literals. Copy/pasting to/from C# becomes very nice. - Cons: - Moves us away from `[...]` being the lingua franca for all collection types. - Adds a lot of parsing/ambiguity complexity around `{...}`. - Impacts our future design space around `{...}` (for example, expression blocks). - Is very difficult to have pattern-parity. `{ k: ... }` is already legal as a property pattern. Needing that to work as a dictionary-pattern is non-trivial (and potentially very confusing for users). 5. Mechanisms to specify a `comparer` or `capacity` for a dictionary. Discussions with the community have already indicated a strong desire to be able to specify these values (esp. the `comparer`). We think this is common enough to want to be able to have support, ideally in a way that doesn't feel like one is taking a big step back wrt brevity and simplicity of the dictionary-expression. Importantly, it would be a shame if the collection-expression form weren't better than a user just using a dictionary-initializer today (e.g. `new(myComparer) { { "mads": 21 } }`). Strawman proposals include: - `[comparer: ..., "mads": 21]` (where `comparer` was now a keyword in this context, and would be stylized as such by the ide). If a user actually wanted to reference a variable called `comparer` they'd then do `@comparer`, like how we normally separate out keywords vs identifiers. - `[new: (comparer, capacity), "mads": 21]`. A special construct allowing one to state what arguments to pass to the constructor. Note: this would likely be beneficial for normal sequence-collections as well. As `new` is already a keyword and `new:` is not legal in the language today, this would have no concerns around ambiguity. 6. Targeting interfaces. Like with sequence-expressions, we believe that dictionary-expressions should be able to at least target the well known type `IDictionary` as well as `IReadOnlyDictionary`. We expect to take a similar approach to what we did with sequence-expressions, where we choose `Dictionary` for the former, and allow the compiler to synthesize a read-only type for the latter. 7. Immutable collections. Like with sequence-expressions, we believe that dictionary-expressions should be able to target types like `ImmutableDictionary`. Our hope is that `CollectionBuilderAttribute` can be used for this purpose, likely pointing at signatures like: `ImmutableDictionary.CreateRange(ReadOnlySpan> items)`. This will tie into the decisions on '5' though wrt to how to pass items like the comparers along. ### Natural type We consider natural types the next biggest area we would want to tackle. Specifically, we think it would be so influenced by the decisions on dictionary-expressions that it would not be sensible to design this first without seeing how dictionaries play out. For example, in the absence of dictionaries, we might consider the natural type of a sequence expression to be `ReadOnlySpan`. However, would such a decision have a sensible glow-up story to tackle natural types for dictionary expressions? Having a sensible story for both will very likely influence our decisions here. For natural types, we see two broad areas we would like to examine: 1. The natural type of a collection expression in the absence of *any* *element-target-typing* whatsoever. This would apply when a collection expression was targeted to something like `object` or `var`, where all information about the final type would have to come from the collection expression itself. e.g. `object o = [1, 2, 3]`. 2. The natural type of a collection expression with some contextual *element-target-typing*. This would apply to cases like so: - `foreach (byte b in [1, 2, 3])`. Here, we believe that the `byte` type should help influence the collection type being created (so that this case is legal). - `List list = [a, b, .. c ? [_ => true] : []]`. Similarly, the use of `Predicate` here in the target should help influence the type of `[_ => true]` such that this code succeeds. Overall though, this space is enormous and will need a lot of future design. Open areas include, but are not limited to: 1. Could we envision a world where `var v = [];` is ever allowed? This would involve flow analysis to determine the element type based on how `v` was used. 2. Should we align on a well known type for the natural type, or should we consider it just a special language type (like anonymous-types) that the compiler makes its own determinations about. For example, we could pick a well known type like `List` or we could consider the language having a special ``builtin-list`` type with special properties. 3. Should we try to mandate a very efficient type (like Span/InlineArrays) for perf? How would that work with async/await? 4. Would it be possible to pick an inefficient type (like `List`) but then give the compiler broad leeway to optimize in the common case where it would not be observable (for example, actually emitting on the stack when safe). ### Extension methods We are still interesting in enabling `[a, b, c].ExtensionMethod()`. However, we think this is not something that should be limited to collection expressions. Broadly speaking, the language is very limited wrt to the interaction of extension methods and target-typed constructs. For example, you cannot do this `(x => true).ExtensionOnStringPredicate()`. The language requires the item being invoked have a type *prior* to lookup of the extension method. To make cases like this work, we'd need to allow them to not have a type, and then say that lookup should still find the extensions, which are then tested for applicability in the rewritten form (e.g. `ExtensionMethod([a, b, c])`). As we are investing heavily in extensions in C# 13, we may want to roll the exploration of this area into that work. ### Addressing existing types that should work with collection expressions, but don't. There are several types we've found that annoying don't work with collection expressions. For example: - `ArraySegment` - `ReadOnlyMemory`. Commonly used as the analog of `ReadOnlySpan` when you're working with async methods. - InlineArray We want to continue collecting these types to see what would be a good approach to expanding out support in the future. Specifically, if the set of types is very small, it might be acceptable to just hardcode support for them. However, if the set grows large, we may need to identify patterns to allow them to participate. For example, we might say that if a type has a `public static implicit operator ThisType(T[] array)` conversion operator, that it would then be constructible with a collection expression. It's worth noting that making any type now be collection expression constructible would always be a potential breaking change with overload resolution, unless we made these always have a lower priority than the core rules we shipped with. ### Supporting non-generic interfaces There is an open question on what the (currently illegal) semantics should be for `IEnumerable x = [1, 2, 3];` There are a few paths we could conceivably take here. - Have no special behavior for this, and allow 'natural type' to take care of it. If, for example, the natural type of `[1, 2, 3]` was `List` then this would be equivalent to: `IEnumerable x = (List)[1, 2, 3];`. Note that this would then be making a mutable collection under the covers, strongly typed to 'int'. - Treat the non-generic interfaces as the generic versions with 'object'. In this case, that would mean the above would be equivalent to: `IEnumerable x = (IEnumerable)[1, 2, 3];`. This would then mean the compiler would synthesize the read-only collection, but also that every element would be boxed as object. - A hybrid approach where the element type might be picked using whatever natural-type system we had, but the collection type itself was the generic interface. In this case, that would mean the above would be equivalent to: `IEnumerable x = (IEnumerable)[1, 2, 3];`, This would then mean the compiler would synthesize the read-only collection, and also that every element would be non-boxed in the underlying storage. Regardless of what we decide, we have to keep this in mind when doing the [Natural type](#natural-type) work. This will start working (with the first version above) once we have natural types. If we do not like the code that would happen there, we would have to either block or come up with an alternate set of semantics when we do natural types. ### Relaxing restrictions. Recent feedback has presented real-world examples where our restrictions around api shapes can be annoying. As an example, the Roslyn team themselves have identified a case of a type that they'd like to be collection-expression constructible, which they cannot use. Specifically, while they can add a CollectionBuilderAttribute to it, the type itself is not `IEnumerable`, and so it fails that restriction we have. This restriction seems onerous, especially as the user is stating explicitly that they think their type is a collection through the user of that attribute. Similarly, we have recently added a restriction that a collection-initializer type used with a collection-expression must have an instance `Add` method that works with the iteration type of the collection. This is more restrictive than what collection-initializers themselves require (simply that they can find a suitable 'Add' method (including extensions)). We may want to relax this if we see worthwhile examples provided. ================================================ FILE: meetings/working-groups/collection-literals/CL-LDM-2023-05-31.md ================================================ # C# LDM Collection Literals Agenda Core spec: https://github.com/dotnet/csharplang/blob/main/proposals/collection-literals.md ## Core set of features for C# 12 1. We are not discussing any 'natural typing' scenarios today. We're continuing to make progress in that space and would like to have a dedicated meeting on that topic. 2. This is the set of features we believe has a very intuitive and solid design around them, and would hit a good 'sweet spot' of capabilities and user satisfaction for the initial release. 3. We intend to have future meetings that go through the spec *in detail* to ensure LDM satisfaction with the exact changes being proposed (Chuck to drive). ### Grammar: ```diff primary_no_array_creation_expression ... + | collection_literal_expression ; + collection_literal_expression : '[' ']' | '[' collection_literal_element ( ',' collection_literal_element )* ']' ; + collection_literal_element : expression_element | dictionary_element | spread_element ; + expression_element : expression ; + dictionary_element : expression ':' expression ; + spread_element : '..' expression ; ``` Common examples: ```c# int[] a = [1, 2, 3]; List list = [start, .. middle, end]; IDictionary d = [k1:v1, k2:v2, k3:v3]; ``` ### Target-typing to core collections and collections with classic collection initializers The following are constructible collection types that can be used to construct collection literals. These are the valid target types for collection literals. The list is in priority order. If a collection type belongs in multiple categories, the first is used. 1. Single dimensional arrays (e.g. `T[]`) ```c# int[] a = [1, 2, 3]; // equivalent to: int[] a = new int[] { 1, 2, 3 }; ``` 2. Span types (`Span` and `ReadOnlySpan`) ```c# Span values = [1, 2, 3]; // equivalent to either: Span values = new int[] { 1, 2, 3 }; // or Span values = stackalloc int[] { 1, 2, 3 }; ``` Which one is picked depends on compiler heuristics around stack space (and is part of the 'params span' WG outcomes), and the ref-safety rules around `values`. 3. Types with a suitable `Construct` method. This will be discussed below, but relates heavily to immutable collections, and how to efficiently construct them. It will also be the preferred way to create a `List` as it is the most efficient form with lowest overhead. 4. Concrete collection types that implement `System.Collections.IDictionary` ```c# Dictionary nameToAge = [ "Dustin": 42, "Cyrus": 43 ]; // possibly equivalent to: Dictionary nameToAge = new Dictionary { { "Dustin", 42 }, { "Cyrus", 43 } }; // Open question below about update vs throw semantics for duplicate keys. Will cover when we discuss dictionaries. ``` 5. Interface types `I` implemented by `System.Collections.Generic.Dictionary` (e.g. `IDictionary`, `IReadOnlyDictionary`) ```c# IReadOnlyDictionary nameToAge = [ "Dustin": 42, "Cyrus": 43 ]; // possibly equivalent to: IReadOnlyDictionary nameToAge = new Dictionary { { "Dustin", 42 }, { "Cyrus", 43 } }; // Open questions on if there is a guarantee about concrete type instantiated. ``` 6. Types that implement `System.Collections.IEnumerable`. e.g. normal C# 3.0 collection initializer types ```c# HashSet set = [1, 2, 3]; // equivalent to: HashSet set = new HashSet { 1, 2, 3 }; // We pass the capacity to the constructor if it takes one. ``` 7. Interface types `I` implemented by `System.Collections.Generic.List` (e.g. `IEnumerable`, `IList`, `IReadOnlyList`, etc.) ```c# IEnumerable values = [1, 2, 3]; // could be: IEnumerable values = new List { 1, 2, 3 }; // or could be: IEnumerable valuies = new <>UnspeakableName { 1, 2, 3 }; // Open question below about this. ``` Open Questions: 1. Does LDM agree with the priority order and totality of the support constructible collection types? Of note: this means that any type that is not in that list, and is not being updated (e.g. netfx 4.7.2 types) might not work with collection literals. Should we have some way (e.g. an extension-method pathway) to be able to opt those legacy types in? 2. What is the behavior when users attempt to initialize an `IEnumerable` (or any interface type)? Do you get a `List` or an undefined type? ### Target-typing with BCL attribute Create method Main examples: ```c# List list = [1, 2, 3]; // equivalent to: CollectionsMarshal.Create(capacity: 3, out List list, out Span __storage); __storage[0] = 1; __storage[1] = 2; __storage[2] = 3; // Works identically for ImmutableArray. // However, some types cannot give you the storage to write sequentially into. For those, the pattern is: ImmutableDictionary namesToAge = [ "Dustin": 42, "Cyrus": 43 ]; // equivalent to: // Storage is initialized (ideally on stack when safe), and passed to type to own creating its internal structure ReadOnlySpan> storage = [ new("Dustin", 42), new("Cyrus", 43) ]; // could be heap or stack. CollectionsMarshal.Create(out ImmutableDictionary namesToAge, storage); ``` The pattern is: ```cs // Attribute placed on collection target type, stating where to find the factory method. // Specifies the type where it is found, and the factory method name. Up to BCL to include. [CollectionLiteralBuilder( typeof(CollectionsMarshal), nameof(CollectionsMarshal.Create))] public class List : IEnumerable { } // or [CollectionLiteralBuilder( typeof(CollectionsMarshal), nameof(CollectionsMarshal.CreateRange))] public sealed class ImmutableDictionary : IDictionary { } // Factory method shape: public static class CollectionsMarshal { // For cases where the instance can share out its storage as a sequential buffer for caller to place the values in. // Storage span is passed out for caller to populate with no overhead. public static void Create( int capacity, out List collection, out Span storage); public static void Create( int capacity, out ImmutableArray collection, out Span storage); // For cases where the final instance has to manage non-sequential structure. Storage is passed in and copied over to // internal structure. public static void CreateRange( out ImmutableHashSet collection, ReadOnlySpan storage); // Example of the shape for things like dictionaries. public static void CreateRange( out ImmutableDictionary dictionary, ReadOnlySpan> storage); } ``` Open Questions: 1. Does LDM agree with introducing a new pattern for collection construction? 1. If this API can't come in time, is it OK for the compiler to temporarily special case `List` (and potentially `ImmutableArray`)? ### Dictionaries Created as a natural syntactic extension on collection literals: ```c# List list = [1, 2, 3]; Dictionary nameToAge = ["Dustin": 42, "Cyrus": 43]; ``` Syntax picked for two reasons: 1. Very brief. A core goal of this feature is to prevent excessive syntax for simple ideas. 2. Very familiar to many languages that use colon here (JS, Python, Swift, Dart, Go, etc.). Want to put best foot forward. Downsides: 3. Does introduce syntactic ambiguity ```csharp Dictionary d = [a ? [b] : c]; // [a ? ([b]) : c)] or [(a ? [b]) : c]? ``` WG feeling is that this ambiguity though would be extremely rare to hit. Picking 'expression' here (not k:v) seems totally fine. If you do want K:V, parenthesize the key. Spreading is also supported for dictionaries, allowing for nice things like: ```c# Dictionary nameToAge = ["Dustin": 42, "Cyrus": 43]; Dictionary newNameToAge = [.. nameToAge, "Mads": 25]; ``` Dictionaries (without 'Construct' methods) are rewritten as: ```cs Dictionary nameToAge = ["Dustin": 42, "Cyrus": 43]; // rewritten as: Dictionary nameToAge = new Dictionary(capacity: 2); nameToAge["Dustin"] = 42; nameToAge["Cyrus"] = 43; // But it could have been (throw vs update semantics): nameToAge.Add("Dustin", 42); nameToAge.Add("Cyrus", 43); ``` Open Questions: 1. Does LDM agree with supporting dictionary literals for C#12? 2. When target-typed to interface, do we guarantee a particular concrete type? 3. Does dictionary creation use _update_ or _add_ semantics? (Are duplicate keys allowed?) Consider the `CollectionsMarshal.CreateRange()` factory method above. The BCL already has similar construction methods for dictionaries, either as constructors or static helpers, that take an `IEnumerable>`. Those existing methods typically don't allow duplicated keys with distinct values, and this method will probably need to work similarly. In short, the construct method may have _add (and throw)_ semantics. 4. Allow `k:v` expression inside a non-dictionary collection literal? e.g.: ```c# List> pairs = ["Dustin": 42, "Cyrus": 43]; // Is this ok? Or would we require you say: List> pairs = [new("Dustin", 42), new("Cyrus", 43)]; ``` Proposal: `dictionary_element` is supported for _non-dictionary_ collection literals if the _element type of the collection_ is some `KeyValuePair<,>`. 5. Allow `expression_element` and `spread_element` in dictionary? This requires effectively that constructing dictionaries understands how to incorporate a KVP *value* from an external source. For example: ```c# KeyValuePair GetMads() => new("Mads", 24); Dictionary nameToAge = ["Dustin": 42, "Cyrus": 43, GetMads()]; // otherwise, you'd have to write: var mads = GetMads(); Dictionary nameToAge = ["Dustin": 42, "Cyrus": 43, mads.Key: mads.Value]; ``` Note: supporting spreads for dictionaries strongly implies that this should "just work". But we want LDM to be ok here. Proposal: - `expression_element` is supported in a dictionary if the _element type_ is some `KeyValuePair<,>` or `dynamic`. - `spread_element` is supported in a dictionary if the _enumerated element type_ is some `KeyValuePair<,>` or `dynamic`. 6. Collection literal syntax does not allow for a custom comparer. Is it sufficient to rely on extension methods such as the following? Will type inference infer `TKey`, `TValue` from `k:v`? ```C# d = ["Alice": 42, "Bob": 43].AsDictionary(comparer); static class Extensions { public static Dictionary AsDictionary( this List> list, IEqualityComparer comparer = null); } ``` 7. Syntax ambiguity (shown above) ### Spreads Corresponding 'construction' form of the 'slice' pattern. ```C# if (x is [var start, .. var middle, var end]) // pattern form: List values = ["start", .. middle, "end"]; // translates to potentially: List values = new List(capacityIfKnown); values.Add("start"); values.AddRange(middle); values.Add("end"); // or potentially: List values = new List(capacityIfKnown); values.Add(start); foreach (var v in middle) values.Add(v); values.Add(end); ``` 1. Should we use AddRange if available? What if it boxes? We could leave it underspecified and utilized compiler heuristics. Fits into the overall discussion of the presumption that "construction" semantics are well-behaved (like pattern matching semantics). Pros: This will commonly be quite fast for many collections passed to AddRange. AddRange often uses TryGetNonEnumeratedCount and CopyTo to update internal capacity, and then write directly into it. Cons: There are definitely cases where this will allocate more than direct foreach (for example, if the AddRange impl cannot use CopyTo, and must instead create a heap allocated IEnumerator). This will box in the case where 'middle' is a struct. We could detect that and `foreach` in that case. 2. Should evaluation of spread elements be lazy? For instance: ```C# static IEnumerable GetIntegers() { for (int i = 0; ; i++) yield return i; } IEnumerable e = [..GetIntegers()]; int first = e.First(); ``` ================================================ FILE: meetings/working-groups/collection-literals/CL-LDM-2023-08-14.md ================================================ # Collection expressions: LDM proposals 2023-08-14 ## Overload resolution *See proposal: [Overload resolution](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution)* Collection expressions can be implicitly converted to multiple target types, which increases the chance of overload resolution ambiguities. We may not be able to choose between two arbitrary collection types when neither collection type is implicitly convertible to the other. For performance though, we could consider choosing spans over arrays or interfaces. The rule could apply to *collection expression arguments only*, to avoid a breaking change to existing code. ```c# SpanAndArray([1, 2, 3]); // (proposed) uses Span overload SpanAndInterface([4, 5]); // (proposed) uses Span overload static void SpanAndArray(Span args) { } static void SpanAndArray(T[] args) { } static void SpanAndInterface(Span args) { } static void SpanAndInterface(IEnumerable args) { } ``` For instance, an additional rule could be added to [*better conversion from expression*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11644-better-conversion-from-expression). > Given an implicit conversion `C₁` that converts from an expression `E` to a type `T₁`, and an implicit conversion `C₂` that converts from an expression `E` to a type `T₂`, `C₁` is a ***better conversion*** than `C₂` if one of the following holds: > > - ... > - `C₁` and `C₂` are collection expression conversions and the following hold: > - `T₁` is a *span type* `Span` or `ReadOnlySpan`. > - `T₂` is a single-dimensional *array type* `E[]` or an interface `IEnumerable`, `IReadOnlyCollection`, `IReadOnlyList`, `ICollection`, `IList`. > - `S` is implicitly convertible to `E`. In practice though, pairs of overloads with spans and either arrays or interfaces may be uncommon, at least in the BCL, because overload resolution prefers the array and interface overloads when passing array arguments. ```c# SpanAndArray(new[] { 1, 2, 3 }); // uses T[] overload SpanAndInterface(new[] { 4, 5 }); // uses IEnumerable overload ``` *See also: [Prefer spans over interfaces in overload resolution](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-06-19.md#prefer-spans-over-interfaces-in-overload-resolution)* ## Type inference *See proposal: [Type inference](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference)* Method type inference involving collection expressions allows inferring the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of a collection type. There is no support for inferring the containing *collection type*. The *iteration type* is defined by `foreach` — it is the type of the `foreach`-able item. If a *collection initializer type* implements `IEnumerable` only and does not otherwise implement the `GetEnumerator()` pattern, the iteration type is `object`. Inferences are made to the iteration type, independently for each element. ```c# AsArray([null]); // error: cannot infer T AsArray([1, 2, 3]); // AsArray(int[]) static T[] AsArray(T[] arg) => arg; ``` ```c# byte b = 1; int i = 2; ArrayAndValue(new[] { b }, i); // error: cannot infer T ArrayAndValue([b], i); // ArrayAndValue() static void ArrayAndValue(T[] x, T y) { } ``` The [*type inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1163-type-inference) change is handled with two rules — one rule for *input type inference* and a matching rule for *output type inference*: see [proposal](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference). *Input type inference* is necessary for walking into the element expressions to infer from explicitly-typed lambda parameters, from tuple element types, or from nested collection expressions. ```c# InputTypeInference([(int y) => { }]); // InputTypeInference() static void InputTypeInference(List> x) { } ``` *Output type inference* is necessary for inferring from the return type of lambdas and method groups in particular. ```c# OutputTypeInference([() => 1]); // OutputTypeInference() static void OutputTypeInference(List> x) { } ``` ## Conversions *See proposal: [Conversions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions)* There is a *collection expression conversion* from a collection expression to the following types. These represent the valid *target types* for a collection expression. * single-dimensional arrays * span types * types with a [*builder method*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#create-methods) * generic interfaces implemented by `List` * *collection initializer* types ```c# int[] a = [1]; // array ReadOnlySpan b = [2]; // span ImmutableArray c = [3]; // builder IReadOnlyList d = [4]; // generic list interface List e = [5]; // collection initializer ``` For all but collection initializer types, there must be an *implicit conversion* to the iteration type `T` for each *expression element* in the collection expression, and an *implicit conversion* to the`T` for the *iteration type* of each *spread element*. ```c# string[] a = [x, y, z]; int[] b = [1, null]; // error: cannot convert 'null' to 'int' ImmutableArray c = [..a]; // error: cannot convert 'string' to 'int' ``` For collection initializer types, there must be an applicable instance or extension `Add` method for each *expression element* in the collection expression, and an applicable `Add` method for an argument of the *iteration type* of each *spread element*. ```c# MyCollection m = [1, "2", (object)3]; // error: no 'Add(object)' method found class MyCollection : IEnumerable { public void Add(int i) { ... } public void Add(string s) { ... } public IEnumerator GetEnumerator() { ... } } ``` Collection initializer types that implement `IEnumerable` only, are the one case of a target type without a strongly-typed *iteration type*. Adding target types is a *breaking change* for overload resolution and type inference. **Question**: Should "generic interfaces implemented by `List`" be an explicit set instead? **Question**: Support conversions to `Memory` and `ReadOnlyMemory`? **Question**: Support conversions to *inline array* types? We'd need to validate the collection length at compile for *known length* collections. ## Ref safety *See proposal: [Ref safety](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#ref-safety)* For collection expressions of `ref struct` target types, the compiler *may* allocate the storage for the collection on the callstack when the following hold: * The collection expression has *local scope* * The collection has a *known length* and is not empty * The runtime supports *inline array* types An empty collection requires no allocation for storage and has *global scope*. A `ReadOnlySpan`, where `T` is one of several *primitive types*, and where the collection expression contains constant values only, is stored in the assembly data section and does not allocate at the use-site, and therefore has *global scope*. The compiler will use heap allocation if the runtime *does not support* inline array types. The working group considered using `stackalloc` instead on older runtimes but managing `stackalloc` buffers would require unnecessary effort for what is otherwise an unsupported scenario. ```c# Print([]); // empty collection, no allocation Print([1, 2, 3]); // primitive constants, assembly data, global scope Print([x, y, z]); // stack allocation, local scope Print((int[])[1, 2, 3]); // heap allocation, global scope static void Print(ReadOnlySpan values) { ... } // argument is implicitly scoped ``` The span argument to a [*builder method*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#create-methods) may be allocated on the callstack. ```c# ImmutableArray ia = [x, y, z]; // stack allocate Create() argument [CollectionBuilder(typeof(ImmutableArray), "Create")] public struct ImmutableArray { ... } public static class ImmutableArray { public static ImmutableArray Create(ReadOnlySpan values) { ... } } ``` What about other uses of collection expressions as `ref struct` instances where the scope is not clear from the immediate context? Should those collection expressions be considered local or global scope? ```c# static ReadOnlySpan AsSpan2(T x, T y) { Span s = [x, y]; // local scope or global? return s; // error if local scope } ``` For that question, we considered three options - see [meeting notes](https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/CL-2023-08-03.md): 1. Use scope of target 1. Determine scope of target based on usage 1. Use local scope always Option 1 may go against user intuition that a collection expression represented as a span can be allocated on the stack, and as a result, it may be a *pit of failure*. To mitigate that, the compiler should report a diagnostic if heap allocation is required. Option 2 seems fragile since minor changes in the method (or external method signatures) may result in a collection expression being allocated on the heap. We'd need the diagnostic from option 1 to mitigate this. This option would also require the compiler to include flow analysis when checking ref safety. Option 3 ensures collection expressions that directly target spans can be allocated on the stack. However, this means such spans are not returnable unless the user explicitly converts to a heap-allocated type. ```c# static ReadOnlySpan Option3_AsSpan2(T x, T y) { return [x, y]; // error: span may refer to stack data } static ReadOnlySpan Option3_AsSpan3(T x, T y, T z) { return (T[])[x, y, z]; // ok: span refers to T[] on heap } ``` The working group recommendation is *option 3*: treat the collection expression as local scope, unless empty or cached in assembly data section. **Question**: When stack allocation cannot be used for a collection expression with span target type, should we fall back to a diagnostic and/or heap allocation? ================================================ FILE: meetings/working-groups/collection-literals/Compiler-synthesized-types.md ================================================ ## Collection expressions - compiler synthesized types Followup to https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/Core-interface-target-type-proposal.md. when the user has one of the following types: 1. `IEnumerable` 1. `IReadOnlyCollection` 1. `IReadOnlyList` 1. `ICollection` 1. `IList` and they target that type with a collection-expression, we will choose a type on their behalf that satisfies the target interface. ### Simple C# 12 approach Simplest solution for the short term (C# 12) would likely be: 1. For `IList` and `ICollection` just use `List`. - Pros: The type is always available, tuned for the ecosystem and receives updates. We likely could not do much better than it, and doing so would involve owning a large surface area of code to generate in the compiler. - Cons: If we do this, it is likely some code out there will take a dependency on this. As such, changing to another type in the future may risk breaks. Those breaks would be around code doing undocumented and unsupported things, but would still likely happen and could cause customer ire. 1. For `IEnumerable`, `IReadOnlyCollection` and `IReadOnlyList`, synthesize a type for the user. The type should implement all those interfaces *and* `IList` and `IList`. These two interfaces help ensure the collections we produce are good citizens in the .Net ecosystem. Lots of code checks for these two interfaces for varying reasons (perf optimizations, compat with winforms checks, etc.). This also matches what the most common list-like BCL collections (`T[]`, `List` and `ImmutableArray`) do as well. So this makes our type as usable as they are in practice. Note: if the collection is empty, we can use just `Array.Empty()`. - Pros: We have broad power in the future to change our implementations, producing more specialized types, or deferring to potential future BCL apis. - Cons: The BCL cannot specialize *as much* with an external synthesized type as they can with the types they know about. ### Simple synthesized type for read-only interfaces For C# 12, the simplest synthesized type we should likely go with when the user target-types to `IEnumerable`, `IReadOnlyCollection` or `IReadOnlyList` is: ```c# // TODO: Attributes? // [DebuggerDisplay("Count = {Count}")] // [Serializable] internal sealed class SimpleCompilerSynthesizedList : IReadOnlyList, IList, IList { private readonly T[] _values; public CompilerSynthesizedList(T[] values) => _values = values; // IEnumerable/IEnumerable public IEnumerator GetEnumerator() => ((IEnumerable)_values).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); // ICollection/ICollection/IReadOnlyCollection public bool IsReadOnly => true; public bool IsSynchronized => false; public int Count => _values.Length; public bool Contains(T item) => _values.Contains(item); public void CopyTo(T[] array, int arrayIndex) => _values.CopyTo(array, arrayIndex); public void CopyTo(Array array, int index) => _values.CopyTo(array, index); public void Add(T item) => throw new NotSupportedException(); public void Clear() => throw new NotSupportedException(); public bool Remove(T item) => throw new NotSupportedException(); // Not sure what this should return: public object SyncRoot => throw new NotSupportedException(); // IList/IList/IReadOnlyList public bool IsFixedSize => true; public T this[int index] { get => _values[index]; set => throw new NotSupportedException(); } object? IList.this[int index] { get => _values[index]; set => throw new NotSupportedException(); } public int IndexOf(T item) => ((IList)_values).IndexOf(item); public int IndexOf(object? value) => ((IList)_values).IndexOf(value); public bool Contains(object? value) => ((IList)_values).Contains(value); public int Add(object? value) => throw new NotSupportedException(); public void Insert(int index, T item) => throw new NotSupportedException(); public void Insert(int index, object? value) => throw new NotSupportedException(); public void Remove(object? value) => throw new NotSupportedException(); public void RemoveAt(int index) => throw new NotSupportedException(); } ``` This is just a wrapper around an array. Non-mutating methods delegate directly to the array. Mutating ones throw (as allowed by the documentation for these apis). `ICollection.IsReadOnly` properly indicates the expected behavior here. ### Future dictionary support When dictionary support is added to collection expressions, we will likely want to follow a complimentary path. Fortunately, dictionaries are likely simpler to support. Namely: When when the user has one of the following types: 1. `IReadOnlyDictionary` 1. `IDictionary` and they target that type with a collection-expression, we will choose a type on their behalf that satisfies the target interface. 1. For `IDictionary` just use `Dictionary`. The pros/cons are the same as for using `List` as the type chosed for `IList/ICollection`. 1. For `IReadOnlyDictionary` synthesize a type for the user. Unlike the list-like collection space, there is no need for the synthesized type to implement anything beyond this surface area (as we are unaware of code that tries down-casting to `IDictionary<,>` to perform optimizations). The types generated here can be heavily optimized. For example: - We can synthesize specialized types for empty/small dictionaries (with no need for actual complex hashing strategies). - The implementations could return themselves for `Keys`. e.g. `IEnumerable Keys => this;` ### Future read-only list optimization ideas 1. Optimize for small, or fixed, collection expression counts. While we will already optimize the empty collection expression case, we can also optimize small counts like so: ```c# internal sealed class SmallCompilerSynthesizedList2 : IReadOnlyList, IList, IList { private readonly T _v1; private readonly T _v2; public SmallCompilerSynthesizedList2(T v1, T v2) => (_v1 = v1, _v2 = v2); public int Count => 2; // remainder of methods optimized to just access fields directly. } ``` This would avoid the array indirection, as well as the extra space taken up by the array overhead itself. 1. Optimize the synthesized collection for the common case where it is consumed only to be iterated one. Specifically, for when the collection was passed *to* a consumer like: ```C# void DoWork(IEnumerable values) { ... } DoWork([a, b, c]); ``` In this case, we could synthesize the type like so: ```c# internal sealed class FastEnumerateCompilerSynthesizedList : IReadOnlyList, IList, IList, /*new*/ IEnumerator { private int _enumeratorIndex = -2; private int _initialThreadId = Environment.CurrentManagedThreadId; // actual underlying data, using whatever strategy we choose. public IEnumerator GetEnumerator() { if (_enumeratorIndex == -2 && _initialThreadId == Environment.CurrentManagedThreadId) { _enumeratorIndex = -1; return this; } return new HeapAllocatedEnumerator(...); } public bool MoveNext() => ++_enumeratorIndex < this.Count; public T Current => this[_enumeratorIndex]; } ``` This follows the same strategy we take when creating `IEnumerable` instances with `yield` iterators. There we also produce an `IEnumerable` which is its own non-allocating `IEnumerator` for the first consumer that tries to enumerate it on the same thread. We would likely not do this for collections stored into a location (like into a field). In that case, it seems more likely that the value might be enumerated many times. So this would really only be beneficial for the collection-expressions passed to r-values. 1. Optimize patterns found in arguments to produce collections that do not actually need storage for those values. For example: ```c# IEnumerable values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // Can be emitted as: IEnumerable values = Enumerable.Range(1, 10); // Or with a specialized compiler synthesized impl that does the same. ``` Similar types of optimizations may be possible depending on how deep we want to go with analysis of the argument values. 1. Optimize collection expressions of blittable constants. For example: ```c# IEnumerable values = ['a', 'b', 'c', 'd', 'e', 'f']; // Can be emitted as: internal sealed class BlittableWrapperX : IReadOnlyList, IList, IList { // Has no-alloc optimization private static ReadOnlySpan __values => new char[] { 'a', 'b', 'c', 'd', 'e', 'f' }; public IEnumerator GetEnumerator() { for (int i, n = 6; i < n> i++) yield return __values[i]; } } ``` ================================================ FILE: meetings/working-groups/collection-literals/Core-interface-target-type-proposal.md ================================================ ## Collection expression conversion to specific interface types. ### The problem we are trying to solve What does it mean when the user has one of the following five types (the types implemented by our core list-like-types like `T[]`, `List`, `ImmutableArray`): 1. `IEnumerable` 2. `IReadOnlyCollection` 3. `IReadOnlyList` 4. `ICollection` 5. `IList` and they try to convert a collection-expression to it. For example: ```c# // Passing to something that can only read data. void DoSomething(IEnumerable values) { ... } DoSomething([1, 2, 3]); ``` // or: ```c# class Order { // Intended to be mutated. public IList ProductIds { get; } = []; } ``` Because these are interfaces, unless the BCL chooses to put the `[CollectionBuilder]` attribute on them to bless a particular way of creating them, then the language itself needs to have understanding here to do the right thing. Note: these interfaces are already well known to the language. '1' is well known because of the built-in support for creating iterators. 1-5 are well known as we understand and respect that any array instance is [implicitly convertible](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1028-implicit-reference-conversions) to any of the above: > From a single-dimensional array type `S[]` to `IList`, `IReadOnlyList`, and their base interfaces, provided that there is an implicit identity or reference conversion from S to T. ### Context Collection expressions aim to be a substantively 'complete' replacement for the myriad of ways people create collections today. They are intended to be used for the *vast majority* of cases where people use collections, and default (as much as possible) to choices that fit the needs of the majority of the user base. A strong desire here is to not add "just another way to create a collection" alongside everything else, but to provide a singular *replacement* that is then suitable to move almost all code wholesale to. It aims to replace *all* of the following forms: 1. `new T[] { ... }` 1. `new[] { ... }` 1. `stackalloc T[] { ... }` 1. `stackalloc[] { ... }` 1. `new CollectionType> { ... }` 1. `new() { ... }` 1. `ImmutableCollectionType.Create(...)` 1. `ImmutableCollection.CreateBuilder(); builder.Add(...); ...; builder.ToImmutable()`. (Wasteful, and doesn't work for base cases like a field initializer). 1. And more... including things like: `ImmutableArray.Empty.Add(x).AddRange(y).etc()` An examination of the BCL and the top 20 NuGet packages (Newtonsoft, EF, Azure, Castle, AWS, AutoMapper, and more), all of which are >400m downloads, reveals very relevant data here. Methods taking those interface collections account for roughly 28% of all methods taking some collection type (arrays, spans, other BCL collections), and `IEnumerable` alone accounts for 25% of the collection-taking methods. This is not surprising as our own practices, and general design guidance we give the community, are simply that: > Methods should be permissive in what they accept, and be precise in what they return. `IEnumerable` (and our other collection interfaces) act as that permissive type that we and our ecosystem have broadly adopted. Indeed, if it were not for `(params) T[]` (a full 50% of all collection-taking methods), `IEnumerable` would be the most commonly taken collection by far for the ecosystem. Ideally, we would ship with support for everything, but we've currently made judicious moves from C#12 to C#13 based on complexity, but also based on impact. For example, `Dictionary expressions` were moved to C#13 to lighten our load, and because data indicates that APIs that consume those dictionary types are only <3% of all apis that take collections in the first place. `Natural type` support has also been pushed out because the complexity is felt to be substantive enough to warrant more time. With how important these interface types are though, we do not believe pushing out from C# 12 will allow us to ship a viable and coherent story to customers. ### What factors are at play? In any interesting design, there are many factors that must be assessed and considered as a whole. For collection expressions, these include but are not limited to: 1. Simplicity. Ideally the feature works in a fashion that is both simple to explain and simple for users to understand. Using some of our modern terminology, we'd like to avoid 'decoder rings' when people use it. 2. Universality. This restates the background context. We want to meet the literal 97% case at launch, not the 69% which would be the case if interfaces can't be targeted. This means needing good stories for arrays, spans, BCL concrete collections *and* BCL core interfaces. 3. Brevity. It is a strong goal of this feature that users be able to just write the simple, idiomatic, collection expression form without the need to do things like add coercive casts to commonly appease the compiler. Specifically, for casts, once you add them (e.g. `(List)[1, 2, 3]`) then the benefit of the feature as a whole is vastly diminished. In these cases, the new form isn't substantively better than the existing form (just 2 characters saved over `new List {1, 2, 3}`). Unlike other features, the cliff here is very steep, often fundamentally negating the idea that this is a valuable feature in the first place. Many parts of the design (especially broad adoption of target-typing) have been entirely around ensuring users can just write the simple expression form and almost never have to do things to appease the language. 4. Performance. A core pillar of collection expressions that we are both evangelizing it by, and which we are seeing customers resonate with, is the idea of > Absent external information unavailable to the compiler, the compiler should almost always do as well or better than the user could. Often much more so. And almost certainly with clearer code for the user to write. Because the user can write a simple `[a, b, c, d, e]` expression, without having to explicitly state what is going on, and because we can provide so much smart understanding to each situation, we can heavily optimize. For example: - If the above 5-element collection were converted to an `ImmutableArray`, our emitted code could would practically always be better than users using normal construction patterns. We can also greatly leverage extremely fast and efficient systems under the covers (like synthesizing `Inline-Arrays` types) that would generally be extremely ugly and painful for users to do themselves. - What we emit can adopt ecosystem best practices around performance. For instance, `[]` can emit as efficient singletons, not causing undesirable allocations. While many customers will not care about performance to this level, we still want customers that do to have confidence in this feature (and we definitely do not want to see the feature immediately banned). It would also be highly unfortunate if we shipped and our own analyzers immediately flagged the usage as being a problem. Finally, part of performance means being a good .NET citizen. So the collections we produce should be able to pick up the optimizations the BCL has today for collection types. 5. Safety. Specifically, keeping data safe from undesirable mutation. We broadly think of users as being in two categories. The first category generally doesn't consider it to be a safety concern to return mutable instances via read-only interfaces, and thus would suffice with any solution on our part. However, the second group (which is likely smaller, but present, vocal, and influential) absolutely wants a roadblock at runtime to keep their exposed data safe from mutation. Similar to the perf concerns, we believe we need a solution for this group that fits their expectations, puts them at ease, and isn't immediately banned (or flagged by analyzers) for doing unsafe things. ## Options Based on feedback from LDM and the working group meetings, we tried to whittle a large number of options down to a reasonable few that we feel warrant discussion and comparison. ### Option 1: Disallow target typing to these interface types. This is the simplest and cheapest option we have at our disposal. We could just say that these assignments would be an error, and force the user to specify the type they want to generate. However, we believe this produces a negative result for every factor at play above. Specifically: ```c# void DoSomething(IEnumerable values) { ... } // Not allowed. DoSomething([1, 2, 3]); // Write this instead: DoSomething((List)[1, 2, 3]); ``` and ```c# class Order : ITaggable { // Not allowed: public IReadOnlyList Tags { get; } = []; // Write this instead: public IReadOnlyList Tags { get; } = (ImmutableArray)[]; // Which is worse than just: public IReadOnlyList Tags { get; } = Array.Empty(); } ``` First, this immediately fails the simplicity and universality concerns. From our own experience, and the data about the ecosystem, we know that users will need to interface with, well, interfaces :). This will be an immediate hurdle that will deeply undercut the value of this feature space, potentially even deeply tainting it for the future. This also undercuts the core design principle we give to people of "be permissive in what you accept". It would now be: > Be permissive in what you accept, but also accept a concrete type so that it can be called using collection expressions Second, collection expressions would not provide any sort of actual useful syntax or brevity for users in this case. Because an explicit type would have to be provided, users would be left with syntax with nearly the same complexity and verbosity as what they would have to write today. Users seeing this would rightfully ask: > Why would I pick this new form, that is really just the same as the old form, just with some tokens tweaked? Performance and safety though would be mostly neutral here. Users who did not care about either would likely just use `List`, and users who did would pick something appropriate for their domain. However, this would still be a small tick in the 'negative' category as our claims about us making the "right, smart, best choices" for users would be undercut by then forcing the user to have to make those choices themselves. ### Option 2: Specify explicit concrete types to use for the possible interface targets. The idea here is to pick either a single concrete type, or potentially a few distinct types, to support constructing any of the above interfaces. Examples of types that could work would be: 1. `List` 2. `T[]` 3. `ImmutableArray` For the purposes of discussion, we'll use `List` as the example of the type to pick. If we were to go this route, it would likely need to be at least the choice picked for `ICollection/IList` as otherwise you could create those mutable-types, and then find yourself unable to mutate them. In practice this would look like: ```c# void DoSomething(IEnumerable values) { ... } // Legal. Is equivalent to `DoSomething(new List { 1, 2, 3 })` DoSomething([1, 2, 3]); ``` and ```c# class Order : ITaggable { // Legal. Is equivalent to: `Tags { get; } = new List(); public IReadOnlyList Tags { get; } = []; } ``` This option *nails* the "simplicity", "universality, and "brevity" aspects we are trying to solve. Explaining to users what happens in these cases is trivial: > It makes a `List`. The part-and-parcel type of .NET that you've known about and have used for nearly 20 years now. Similarly, it can be used for *all* these APIs that take in one of these interfaces. Finally, it always allows the nice short syntactic form that really sells people on why collection expressions are a superior choice to use over practically every other collection creation construct they can use today. However, this also falls short in both the 'performance' and 'safety' domains. Indeed, it does so so egregiously, that our own analyzers will flag this as inappropriate and push both the users who run these analyzers, and the users who just care about these facets of development, away from feeling they can trust this feature to live up to its recommendation as making the "right, smart, best choices" for them. It will also likely lead to negative-evangelization, where voices in the community steer people away from the feature as a whole, proclaiming it as harmful. #### Option 2 - Performance For "performance", `List` has particular issues: 1. It *already* comes with two allocations for itself. 2. Getting an iterator for it (through the `IEnumerable.GetEnumerator` path) will produce another allocation. 3. It has excess overhead internally to support both being able to grow, and has overhead internally to ensure it is not mutated while it is being iterated. 4. It allocates for the incredibly common case of an empty collection. While `List` is a great type when you need flexibility and permissiveness, it is not a good choice when you know precisely what you are producing, and you have no need to access the flexible, mutation-oriented, capabilities it provides. #### Option 2 - Safety For "safety" `List` is also an unacceptable choice for many (and our own analyzers will push you away from it). Users who expose data safely today through the readonly interfaces `IEnumerable/IReadOnlyCollection/IReadOnlyList` today will find that they cannot effectively move to collection expressions. They will have to keep the highly verbose and clunky code they have today, to use types like `ImmutableArray`, or things like `new List { a, b, c }>.AsReadOnly()`. This will make collection-expressions feel half-baked for these users, again undercutting the story that this new feature makes the "right, smart, best choices" for the user. It will also force them to lose the brevity and consistency of being able to use collection-expressions with confidence everywhere. Note: we could potentially choose *different* types depending in the end interface we were assigning to. For example, `List` for the mutable ones, and `ImmutableArray` for the non-mutable ones. However, this would certainly now start getting less simple, with the need for a 'decoder ring' rising. It also likely would not play nicely with when we get to dictionaries. While `Dictionary` would be a natural analog to `List`, with great familiarity to the ecosystem, `ImmutableDictionary` would be a disaster in a great number of cases as the read-only dictionary analog. ### Option 3: Do *not* specify concrete types to use for the possible interface targets. Deep discussion around the problems with Option 2 led the WG and partners to come up with a variant of '2', leveraging the parts it does well at, while curtailing its drawbacks. First, it's important to look at the surface area of the read-only interfaces `IEnumerable/IReadOnlyCollection/IReadOnlyList` and see that it is only the following *three* members: ```c# { public IEnumerator GetEnumerator(); // and the non-generic equivalent. public int Count { get; } public T this[int index] { get; } } ``` Basically, exactly the same as `IEnuemrable` with *just* enough to think of it as an indexable sequence. With that in mind the rules for target-typing an interface would be as follows: 1. We take a page from what we do today *already* for `IEnumerable` and `yield` iterators, and we say that if you have a target-type of `IEnumerable/IReadOnlyCollection/IReadOnlyList`, the language will state that you have no guarantee on any particular type being used at all. But you will get an instance that efficiently implements the API surface area requested. For example, the type that is used could be: - An unnameable, compiler synthesized type. This could be something like what we do with arrays today (and Inline-Arrays for collection-expressions) where a specific type is generated for each specific size (including using Inline-Array tricks where available), producing values with practically no size overhead. Or it could potentially be a generalized unnameable type that has contiguous storage (like an array) for the elements, but wraps it safely. - An unknown type provided by the runtime. For example, through a method like `IEnumerable Create(ReadOnlySpan values)` (i.e. the builder-pattern applied to these interfaces). This would allow the runtime itself to choose the most optimal internal representation for the data, and would also allow for it to do things like implement internal APIs it can query for specialized scenarios, or directly cast to the underlying type to do things like grab the contiguous data directly as a span. - Importantly, both of the above are possible, and we could even do one, then migrate to the other over different releases. Because all that user could could ever know about is the target-interface type, there would be no source, binary, or runtime breaking changes here. This means that in C# 12 we could use a compiler-synthesized type. But also then migrate over to a BCL api if the BCL would like to own a canonical API for this purpose. - Also, we have enormous flexibility here in terms of what does happen. For example, if the compiler synthesizes types, it could choose a handful to make extremely efficient (for example, for collections expression of less then eight elements), and then fallback to a single type that handled the rest of the cases. These approaches could change over time based on data (similar to how we've adapted how we emit switch statements/expressions, and hashing). 2. For a target-type of `ICollection/IList`, we would state the same thing. That no type was guaranteed, but you would be certain to get an good implementation that supported mutation. In practice though, we would be *highly* likely to just default this to `List` as it would satisfy these requirements, while being an excellent implementation that is already heavily optimized for the flexible cases these interfaces would need. This would also lower the burden on the compiler side in terms of codegen and complexity. Effectively, this *intentionally* bifurcates the interfaces into two important categories. The 'read only' interfaces, which support no mutation, and the 'mutable interfaces' which do. Our feeling is that for the latter, there is no real better choice than `List`. It is the 'bread and butter' type the BCL has for this purpose that the entire ecosystem understands and feels comfortable with. For the domain of mutable-sequences, it is extremely good and does its job well. Specifically: ```c# void DoSomething(IEnumerable values) { ... } // Allowed, an efficient type is used here, but you will not know what it is, or be able // to take a dependency on it. DoSomething([1, 2, 3]); ``` and ```c# class Order : ITaggable { // Legal. An efficient 'empty', read-only singleton will be used. public IReadOnlyList Tags { get; } = []; // Legal. Equivalent to: `{ get; } = new List()`, // though we technically would not guarantee `List` public IList ProductIds { get; } = []; } ``` With respect to the factors we care about, here's how the above falls out: For 'brevity/universality', this still strongly satisfies our goals. Users will be able to use the succinct collection-expression form for all these APIs with ease. However, compared to option 2, we now see wins in both safety and performance. #### Option 3 - Safety Starting with safety, we now have a good option for everyone exposing data through any of those three read-only interfaces. Data is safe by default, which is good for the users that care, and non-harmful for the users that do not care. Users who care are then satisfied that they can trust this feature and that it lives up to the stated goal that it will make the smart choices they can depend on and they do not have to ban this feature, or recommend others steer away from it. Importantly, analyzers do not trigger, preventing our own tooling from pushing people away from using our cohesive story here. This approach also naturally extends out to when we do dictionary-literals. There, it will also be the case that for `IReadOnlyDictionary` we will not want to expose a mutable type (like `Dictionary`) for safety reasons. #### Option 3 - Performance While the wins now with 'safety' are definitely welcome, the largest benefits come in the "performance" category. Specifically, not having to name a particular type for the read-only interfaces opens up many areas of optimization that would simply be unavailable when having to pick an existing generalized type. This would also live up to the promise that we will do an excellent job with perf, and that it would be *very* hard for a user to do the same, and practically impossible for them to do so with simple syntax. Specifically, all of the following are available as things we could do to heavily optimize here when target-typing `IEnumerable/IReadOnlyCollection/IReadOnlyList`. 1. Empty collection expressions can always be collapsed down to a single empty singleton (per `T` element type) across the entire program. 1. We can use features like inline-arrays to generate instances that inline all their elements and are no larger than space for the elements themselves. A count would not even need to be stored as it could just be hard-coded as the value returned by `int Count { get; }`. While this might generate many types in the assembly (one type per unique count), it's worth noting that this is what we *already* do for arrays *today*, and what we will be doing for the inline-array types we generate for spans/params-spans. 1. We can borrow a trick from `yield iterators` where the iterator returned from `IEnumerator GetEnumerator()` *reuses* the `this` instance to be its own iterator *if* it has never been iterated yet and the calling thread is the same one that generated the `IEnumerable`. This means that if you have: ```c# DoSomething([1, 2, 3]); ``` Then this may be a *single* allocation, *including* for when it is immediately iterated. 1. We do not need any code anywhere that sits in service of supporting variable sizes, or supporting mutation. This means no overheads of checks, or invalidation of iterators, etc. 1. Collections full of constants, with well-known patterns, could potentially be implemented without allocating contiguous storage for them. For example `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]` could be generated as `Enumerable.Range(1, 10)`, and so on. Overall, by not specifying explicit types, we actually open up a wealth of optimizations that both our own compiler or the BCL can choose to do. While synthesizing types may initially seem concerning, we do already do it for many cases today, including when we already generate `IEnumerables` with `yield`, and also for every array creation that has an initializer (like `new int[] { 1, 2, 3 }`). The surface area here is also exceptionally small. There are only three extremely basic operations to support (`GetEnumerator(),Count,this[]`). So the burden on the compiler to generate types for this feels completely acceptable, especially for the large wins we can achieve. There are still benefits though with the BCL taking this over. Specifically, if they do so, they can then implement internal interfaces they may query for. Or they can use their knowledge of the exact internal types they use to get to the underlying data in even more efficient ways (including potentially being able to grab spans to it). That said, if there was such an API to grab a span from an collection type, it would be good for that to be public so that the compiler could just implement that itself. This approach also naturally extends out to when we do dictionary-literals. There, it will also be the case that for `IReadOnlyDictionary` we (and the BCL) could likely heavily optimize. For example, it will often be the case that read-only dictionaries may be small (or empty). Having specialized implementations that eschew complex wasted-space strategies for contiguous linear lookup may be highly beneficial. Reserving that read-only implementations may be unknown, specialized, types allows for these sorts of wins to happen automatically. Ultimately, by being an internal implementation detail, we also have the flexibility to pick and choose any of these optimizations we feel worthwhile at any point. We can, for example, leave these (or more) to C# 13, 14 and beyond. And data can be used to help identify potential optimizations and indicate how worthwhile they would be. #### Option 3 - Simplicity Finally, we come to the one area where the above proposal ticks downward: "simplicity". It is certainly the case that "Use `List` for everything" approach is much simpler to explain. However, we believe the above to be *acceptably* complex to explain, with enough benefits to point at to convince users that this was the right choice for them. Specifically, we believe the best way to explain it is: 1. Are you using one of the mutation-supporting interfaces (`ICollection/IList`)? If so, you'll get something great. A high quality mutable type that supports that API and will work great with the rest of the ecosystem. 2. Are you using the read-only interfaces (`IEnumerable/IReadOnlyCollection/IReadOnlyList`)? If so, you get a *safe*, *high performance* impl that supports the API shape needed. This is right in line with what we already do for you today when you use `IEnumerable` with `yield`. Your instance will be safe, with no concern about data changing out from you unexpectedly. And, by switching to collection-expressions you will just get performance wins *for free*. In other words, compared to virtually any other approach you're taking today (outside of hand-writing custom types for everything, and initializing them all with exceptionally ugly code), this system will work better. And, if you are the user that was hand-writing everything before, this system is also so much better because now you get the same perf, with exceptional clarity and brevity. We feel there is nothing particularly strange or difficult to explain here to users. C# and .Net is replete with APIs and patterns that do not let you know the specific type being constructed. For example, from C# 3 onwards, `Linq` very much put forth the idea of: > You will just get an `IEnumerable`, but you won't know what the actual concrete type is. This abstraction also allows the underlying system to heavily optimize. `Linq` itself uses this to great effect, using many different specialized implementations under the covers to provide better performance. Similarly, with several of our latest releases, we've started more heavily pushing the idea that the language has smart semantics that will do what you ask it to do, very efficiently, without overspecifying exactly how that must be done. Patterns in general, and List-patterns (the analog to list-construction) heavily push this idea, stating explicitly that assumptions about well-behavedness will be made and that the compiler will pick the best way to do things. So we feel that collection-expressions fit well into both the historical, and modern, way that C# and .Net works. Where you can declaratively tell us what you want (like with `Linq`), and the systems coordinate to produce great results by default. ## Conclusion Based on all of this, the working group strongly thinks that we should go with option 3 and that we should do so in C#12. The other options come with enormously painful caveats that heavily undercut the core messaging of this feature entirely. Missing C# 12 on this also dramatically limits the usefulness of this feature space, and will immediately cause customer confusion and frustration that such a core scenario feels unaddressed by us in the initial release. ================================================ FILE: meetings/working-groups/collection-literals/LDM-questions-2023-08-15.md ================================================ ## Collection expression working group questions and suggestions ### Ref safety Discussion and examples do not apply to ReadOnlySpan of constant, blittable data. That always has global-scope. Q: What should the ref-safety scope be for: ```c# Span s = [x, y, z]; ``` There are two primary directions we can go here: 1. Analyze how the variable is use that to inform the scenario. Specifically, we could use flow analysis to determine what scope a variable should have. For example: ```c# void M() { // Local scoped, s does not escape. Span s = [x, y, z]; foreach (var v in s) Console.WriteLine(v); } ``` ```c# ReadOnlySpan M() { // Global scoped, since 's' escapes out of 'M'. Span s = [x, y, z]; UseSpan(s); return s; } ``` Pros: Users can write spans + collection-expressions in a very simple fashion, and have the code "just work". Cons: Unclear about what's actually happening. Minor changes could lead to very different outcomes. Analysis is likely also very non trivial (on the order of major flow analysis work). Scoping constraints would have to flow backwards from escape points back to the origination point. For example: ```c# ReadOnlySpan M() { Span s1 = [x, y, z]; Span s2 = X(s1); return s2; } Span X(Span s) => s; ``` Here, 's2' must have global scope in order to escape 'M'. But that then must flow that constraint into the invocation of 'X'. This will then have to flow the constraint into 's1' to finally choose that s1 is globally scoped. Complexity rises greatly with all flow-analysis constructs. 2. Decide on the scope directly from the declaration, instead of depending on how it is used. - Option A: Ref-struct local has global scope by default. To have local-scope, add the `scoped` keyword: ```c# void M() { // 's' has 'global scope' but does not escape. Will allocate. If they want stack-allocation, add `scoped` Span s = [x, y, z]; foreach (var v in s) Console.WriteLine(v); } ``` Ideally compiler tells the user to make these variables `scoped` when it can be. Pros: User gets the scoping they explicitly request. When scope can be narrower, they are informed. Cons: Analysis is similarly complex due to requisite reverse flow analysis. Also, WG believes the default position of users is to want a span + collection-expression to have local scope and be stack allocated. However, to get that, users will have to write: ```c# void M() { // 's' has 'local scope' scoped Span s = [x, y, z]; foreach (var v in s) Console.WriteLine(v); } ``` This feels punishing to the common/default case that users will want. Importantly, it means that if a user wants to switch off from `stackalloc` to collection-expressions they get less pleasant code. - Option B: Collection expressions of `ref struct` type have local scope. ```c# void M() { // 's' has 'local scope'. Will get stack allocated. Span s = [x, y, z]; foreach (var v in s) Console.WriteLine(v); } ``` Pros: A very simple and easy rule to explain to users. Default, simple, syntax gives best performance. No need for any sort of complex flow analysis. Existing analysis "just works" and lets the user know if there are any problems. When code needs to allocate on the heap it is clear in the code that this is happening. Cons: if the user *does* want the span to have global scope, then they have to be explicit about that: ```c# Span M() { // Either: Span s1 = (int[])[x, y, z]; Span s1 = new[] { x, y, z }; return x ? s1 : s2; } ``` However, WG believe this is a good thing. This will be the rare case, and having *it* be slightly more verbose is unlikely to be a burden. In other words, we want the common, fast, case to sing with collection-expressions, while the less common, slower, case is ok to pay a small price. Working group suggestion: Option 2B. Collection expressions of `ref struct` type have local scope. ### Overload resolution priority We know we want a `Span` overload to be preferred over a `T[]` overload or an interface overload ( `IEnumerable/IReadOnlyCollection/IReadOnlyList/ICollection/IList`). However, we have options on how to specify things to get the above outcome. 1. Option A: Span/ReadOnlySpan is preferred over arrays and those specific interfaces when the iteration type of the span is implicitly convertible to the iteration type of the array or interface. The general intuition here is Spans/Arrays/Interfaces have a natural ordering we can make an airtight case around. Specifically: Spans are better than arrays as they're the fast form that can subsume arrays and also be on the stack. Arrays are better than all those interfaces because it's a more specific type that implements the interface. Spans are better than interfaces both because they're already better than arrays, and because they can be thought of as morally implementing those interfaces (which may also be literally true in the future). However, this has oddities: ```c# void M(List list); void M(ReadOnlySpan list); M([1, 2, 3]); // Ambiguous. ``` This seems odd. If there are collection overloads, and one is concrete, and one is a span, we would say it was ambiguous, when it seems clear that one exists for perf and should be preferred. 1. Option B: Same as option A, except that it applies to all ref-structs, not just Span/ReadOnlySpan. The general intuition is that anything in the ref-struct realm should be considered fast/span-like and then fit into this bucket. With this approach the following would also hold: ```c# ref struct ValueList { ... } void M(int[] list); void M(ValueList list); M([1, 2, 3]); // calls the ValueList version. ``` However, like Option A, this would become ambiguous with other concrete overloads: ```c# ref struct ValueList { ... } void M(List list); void M(ValueList list); M([1, 2, 3]); // Ambiguous. ``` 1. Option C: `ref struct` collection types are preferred over non-`ref struct` collection types when there is an implicit conversion from the iteration type of the `ref struct` type to the iteration type of the non-`ref struct` type. The general intuition here is that if the user provides a ref-struct overload that that is the 'fast' option, and thus would be preferred over other types. This seems like the 'more likely than not' case that extends beyond just Span/ReadOnlySpan. Also seems to align with things like interpolated string handlers over System.String. For example: ```c# ref struct ValueSet { ... } void M(ValueSet list); void M(HashSet list); M([x, y, z]); // calls the ValueList version. ``` The general intuition here is that if the user provides a ref-struct overload that that is the 'fast' option, and thus would be preferred over other types. This seems like the 'more likely than not' case that extends beyond just Span/ReadOnlySpan. WG opinion: Leaning towards Option C, prefer ref-structs over all other types. ### Add support for `Memory` Should we add support for `Memory` in C# 12. For example: ```c# Memory m = [1, 2, 3]; ``` Based on last LDM meeting, we are deciding "We will do if we have time, but it is low pri and can be cut if we do not have the time/resources" 1. We made a similar decision for inline-array types. These types are not in the core majority of apis/use-cases, and can come later. 1. Adding support for a new type *is* a breaking change. However, this is unavoidable in this space as any type can become a collection type in the future (through usage of the `[CollectionBuilder]` attribute). As such, we do not think it critical to front load support to avoid this problem. 1. In practice `Memory` is for async versions of `Span` methods. These are not generally overloads as one method is `Foo(Span)` while the other is `FooAsync`. So the concern about overload ambiguity in the future is low. If we do support this in C# 12 we will do so through special-casing this exact type. However, in the future we will likely add support for `CollectionBuilderAttribute` to point at the existing construction member like so: ```c# [CollectionBuilder(typeof(Memory<>, "op_Implicit"))] public readonly struct Memory { public static implicit operator Memory(T[]? array) { ... } } ``` We would then define the Collection-Builder pattern as doing array-ownership-transfer (where the compiler would create the array and then give it to the final collection to own). This would also likely apply to `ImmutableArray` in the future as well, as it would point to `ImmutableCollectionMarshal.AsImmutable(T[])` ================================================ FILE: meetings/working-groups/collection-literals/collection-expressions-inferred-type.md ================================================ # Collection expressions: inferred type ## Summary Inferring an element type for collection expressions based on the elements in the collection, and choosing a containing collection type, would allow using collection expressions in locations that are implicitly-typed. ## Motivation Inferring an element type would allow collection expressions to be used in `foreach` and in spread elements. In these cases, the inferred *element type* is observable, but the containing *collection type* is not observable. ```csharp foreach (var i in [x, y, z]) { } int[] items = [x, y, .. b ? [z] : []]; ``` If the compiler chooses a specific containing *collection type*, collection expressions could be used in other implicitly-typed locations where the collection type is observable. ```csharp var a = [x, y]; // var var b = [x, y].Where(e => e != null); // extension methods var c = Identity([x, y]); // type inference: T Identity(T) ``` ## Inferred element type The *element type* `E` inferred for a collection expressions is the [*best common type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) of the elements, where for each element `Eᵢ`: * If `Eᵢ` is an *expression element*, the contribution is the *type* of `Eᵢ`. If `Eᵢ` does not have a type, there is no contribution. * If `Eᵢ` is a *spread element* `..Sᵢ`, the contribution is the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) of `Sᵢ`. If `Sᵢ` does not have a type, there is no contribution. If there is no *best common type*, the collection expression has no type. Inferring the element type is sufficient for scenarios such as `foreach` and spreads where the *collection type* is *not observable*. For those cases, the compiler may use any conforming representation for the collection, including eliding the collection instance altogether. ```csharp foreach (var i in [1, .. b ? [2, 3] : []]) { } // ok: collection of int foreach (var i in []) { } // error: cannot determine element type foreach (var i in [1, null]) { } // error: no common type for int, ``` ## Natural collection type For implicitly-typed scenarios where the *collection type* is observable, the compiler needs to choose a specific collection type in addition to inferring the element type. The choice of collection type has a few implications: - **Mutability**: Can the collection instance or the elements be modified? - **Allocations**: How many allocations are required to create the instance? - **`IEnumerable`**: Is the collection instance implicitly convertible to `IEnumerable`, and perhaps other collection interfaces? - **Non-type arguments**: Does the collection type support elements that are not valid as type arguments, such as pointers or `ref struct`? - **Async code**: Can the collection be used in `async` code or an iterator? The table below includes some possible collection types, and implications for each type. |Collection type|Mutable|Allocs|`IEnumerable`|Non-type args|Async|Details| |:---:|:---:|:---:|:---:|:---:|:---:|:---:| |`T[]`|elements only|1|Yes|pointers|Yes| | |`List`|Yes|2|Yes|No|Yes| | |`ReadOnlySpan`|No|0/1|No|No|No|stack/heap allocated buffer| |`ReadOnlyMemory`|No|1|Yes|No|Yes|heap allocated buffer| |`IEnumerable`|No|1+|Yes|No|Yes|context-dependent implementation| |*Anonymous type*|?|1+|Yes|Yes|Yes|compiler-generated type| ## Breaking changes Previously, collection expressions [*conversions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) relied on *conversion from expression* to a target type. And previously, collection expression [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) relied on input and output type inference for the elements. For scenarios where the natural type of the collection could be used instead, there is a potential breaking change. If the natural type is `IEnumerable`, the following is a breaking change due to the **conversion** from `List` to `IEnumerable` ```csharp bool b = true; List x = [1, 2, 3]; var y = b ? x : [4]; // y: previously List, now IEnumerable ``` The following is a breaking change in overload resolution due to **type inference** from the natural type to `T`: ```csharp Log([1, 2, 3]); // previously Log(IEnumerable), now ambiguous void Log(T item) { ... } void Log(IEnumerable items) { ... } ``` ## Open questions ### Support `foreach` and spread over typeless collection expressions? Should we support collection expressions in `foreach` expressions and in spread elements, even if we decide not to support a natural *collection type* in general? What are the set of locations where the collection type is not observable that should be supported? ### Collection type? If we do support a natural *collection type*, which collection type should we use? See the table above for some considerations. ### Conversions from type? Is the natural type considered for *conversion from type*? For instance, can a collection expression with natural type be assigned to `object`? ```csharp object obj = [1, 2, 3]; // convert col to object? [Value([1, 2, 3])] // convert col to object? static void F() { } class ValueAttribute : Attribute { public ValueAttribute(object value) { } } ``` ### Target-type `foreach` collection? Should the collection expression be target-typed when used in `foreach` with an *explicitly typed iteration variable*? In short, if the iteration variable type is explicitly typed as `E`, should we use `col` as the target type of the `foreach` expression? ```csharp foreach (bool? b in [false, true, null]) { } // target type: col? foreach (byte b in [1, 2, 3]) { } // target type: col? ``` ### Target-type spread collection? If a spread element is contained in a *target-typed* collection expression, should the spread element expression be target-typed? In short, if the containing collection expression has a target type with *element type* `E`, should we use `col` as the target type for any spread element expressions? ```csharp int[] x = [1, ..[]]; // spread target type: col? object[] y = [2, ..[default]]; // spread target type: col? ``` ================================================ FILE: meetings/working-groups/collection-literals/collection-expressions-next.md ================================================ # Collection expressions - next ## Summary [summary]: #summary Additions to [*collection expressions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md). ## Motivation [motivation]: #motivation A form for dictionary-like collections is also supported where the elements of the literal are written as `k: v` like `[k1: v1, ..d1]`. A future pattern form that has a corresponding syntax (like `x is [k1: var v1]`) would be desirable. ## Detailed design [design]: #detailed-design ```diff collection_literal_element : expression_element + | dictionary_element | spread_element ; + dictionary_element : expression ':' expression ; ``` ### Spec clarifications [spec-clarifications]: #spec-clarifications * `dictionary_element` instances will commonly be referred to as `k1: v1`, `k_n: v_n`, etc. * While a collection literal has a *natural type* of `List`, it is permissible to avoid such an allocation if the result would not be observable. For example, `foreach (var toggle in [true, false])`. Because the elements are all that the user's code can refer to, the above could be optimized away into a direct stack allocation. ## Conversions [conversions]: #conversions The following implicit *collection literal conversions* exist from a collection literal expression: * ... * To a *type* that implements `System.Collections.IDictionary` where: * The *type* contains an applicable instance constructor that can be invoked with no arguments or invoked with a single argument for the 0-th parameter where the parameter has type `System.Int32` and name `capacity`. * For each *expression element* `Ei`: * the type of `Ei` is `dynamic` and there is an [applicable](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation) indexer setter that can be invoked with two `dynamic` arguments, or * the type of `Ei` is a type `System.Collections.Generic.KeyValuePair` and there is an applicable indexer setter that can be invoked with two arguments of types `Ki` and `Vi`. * For each *dictionary element* `Ki:Vi`, there is an applicable indexer setter that can be invoked with two arguments of types `Ki` and `Vi`. * For each *spread element* `Si`: * the *iteration type* of `Si` is `dynamic` and there is an [applicable](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation) indexer setter that can be invoked with two `dynamic` arguments, or * the *iteration type* is `System.Collections.Generic.KeyValuePair` and there is an applicable indexer setter that can be invoked with two arguments of types `Ki` and `Vi`. * To an *interface type* `I` where `System.Collections.Generic.Dictionary` implements `I` and where: * For each *expression element* `Ei`, the type of `Ei` is `dynamic`, or the type of `Ei` is a type `System.Collections.Generic.KeyValuePair` and there is an implicit conversion from `Ki` to `K` and from `Vi` to `V`. * For each *dictionary element* `Ki:Vi` there is an implicit conversion from `Ki` to `K` and from `Vi` to `V`. * For each *spread element* `Si`, the *iteration type* of `Si` is `dynamic`, or the *iteration type* is `System.Collections.Generic.KeyValuePair` and there is an implicit conversion from `Ki` to `K` and from `Vi` to `V`. ## Natural type [natural-type]: #natural-type In the absence of a *constructible collection target type*, a non-empty literal can have a *natural type*. The *natural type* is determined from the [*natural element type*](#natural-element-type). If the *natural element type* `T` cannot be determined, the literal has no *natural type*. If `T` can be determined, the *natural type* of the collection is `List`. The choice of `List` rather than `T[]` or `ImmutableArray` is to allow mutation of `var` locals after initialization. `List` is preferred over `Span` because `Span` cannot be used in `async` methods. ```c# var values = [1, 2, 3]; values.Add(4); // ok ``` The *natural element type* may be inferred from `spread_element` enumerated element type. ```c# var c = [..[1, 2, 3]]; // List ``` Should `IEnumerable` contribute an *iteration type* of `object` or no contribution? ```c# IEnumerable e1 = [1, 2, 3]; var e2 = [..e1]; // List or error? List e3 = [..e1]; // error? ``` The *natural type* should not prevent conversions to other collection types in *best common type* or *type inference* scenarios. ```c# var x = new[] { new int[0], [1, 2, 3] }; // ok: int[][] var y = First(new int[0], [1, 2, 3]); // ok: int[] static T First(T x, T y) => x; ``` --- * For example, given: ```c# string s = ...; object[] objects = ...; var x = [s, ..objects]; // List ``` The *natural type* of `x` is `List` where `T` is the *best common type* of `s` and the *iteration type* of `objects`. Respectively, that would be the *best common type* between `string` and `object`, which would be `object`. As such, the type of `x` would be `List`. * Given: ```c# var values = x ? [1, 2, 3] : []; // List ``` The *best common type* between `[1, 2, 3]` and `[]` causes `[]` to take on the type `[1, 2, 3]`, which is `List` as per the existing *natural type* rules. As this is a constructible collection type, `[]` is treated as target-typed to that collection type. ## Natural element type [natural-element-type]: #natural-element-type Computing the *natural element type* starts with three sets of types and expressions called *dictionary key set*, *dictionary value set*, and *remainder set*. The *dictionary key/value sets* will either both be empty or both be non-empty. Each element of the literal is examined in the following fashion: * An element `e_n` has its *type* determined. If that type is some `KeyValuePair`, then `TKey` is added to *dictionary key set* and `TValue` is added to *dictionary value set*. Otherwise, the `e_n` *expression* is added to *remainder set*. * An element `..s_n` has its *iteration type* determined. If that type is some `KeyValuePair`, then `TKey` is added to *dictionary key set* and `TValue` is added to *dictionary value set*. Otherwise, the *iteration type* is added to *remainder set*. * An element `k_n: v_n` adds the `k_n` and `v_n` *expressions* to *dictionary key set* and *dictionary value set* respectively. * If the *dictionary key/value sets* are empty, then there were definitely no `k_n: v_n` elements. In that case, the *fallback case* runs below. * If *dictionary key/value sets* are non-empty, then a first round of the *best common type* algorithm in performed on those sets to determine `BCT_Key` and `BCT_Value` respectively. * If the first round fails for either set, the *fallback case* runs below. * If the first round succeeds for both sets, there is a `KeyValuePair` type produced. This type is added to *remainder set*. A second round of the *best common type* algorithm is performed on *remainder set* set to determine `BCT_Final`. * If the second round fails, the *fallback* case runs below. * Otherwise `BCT_Final` is the *natural element type* and the algorithm ends. * The *fallback case*: * All `e_n` *expressions* are added to *remainder set* * All `..s_n` *iteration types* are added to *remainder set* * The *natural element type* is the *best common type* of the *remainder set* and the algorithm ends. --- * Given: ```c# Dictionary d1 = ...; Dictionary d2 = ...; var d3 = [..d1, ..d2]; ``` The *natural type* of `d3` is `Dictionary`. This is because the `..d1` will have a *iteration type* of `KeyValuePair` and `..d2` will have a *iteration type* of `KeyValuePair`. These will contribute `{string, object}` to the determination of the `TKey` type and `{object, string}` to the determination of the `TValue` type. In both cases, the *best common type* of each of these sets is `object`. * Given: ```c# var d = [null: null, "a": "b"]; ``` The *natural type* of `d` is `Dictionary`. This is because the `k_n: v_n` elements will construct the set `{null, "a"}` for the determination of the `TKey` type and `{null, "b"}` to the determination of the `TValue` type. In both cases, the *best common type* of each of these sets is `string`. * Given: ```c# string s1, s2; object o1, o2; var d = [s1: o1, o2: s2]; ``` The *natural type* of `d3` is `Dictionary`. This is because the `k_n: v_n` elements will construct the set `{s1, o1}` for the determination of the `TKey` type and `{o2, s2}` to the determination of the `TValue` type. In both cases, the *best common type* of each of these sets is `object`. * Given: ```c# string s1, s2; object o1, o2; var d = [KeyValuePair.Create(s1, o1), KeyValuePair.Create(o2, s2)]; ``` The *natural type* of `d3` is `Dictionary`. This is because the `e_n` elements are `KeyValuePair` and `KeyValuePair` respectively. These will construct the set `{string, object}`for the determination of the `TKey` type and `{object, string}` to the determination of the `TValue` type. In both cases, the *best common type* of each of these sets is `object`. ### Interface translation [interface-translation]: #interface-translation Given a target type `T` for a literal: * If `T` is some interface `I` where that interface is implemented by `Dictionary`, then the literal is translated as: ```c# Dictionary __temp = [...]; /* standard translation */ I __result = __temp; ``` * If `T` is a dictionary collection initializer with key `K1` and value `V1`, the literal is translated as: ```c# T __result = new T(capacity: __len); __result[__e1.Key] = __e1.Value; __result[__k1] = __v1; foreach (var __t in __s1) __result[__t.Key] = __t.Value; // further additions of the remaining elements ``` * In this translation, `expression_element` is only supported if the element type is some `KeyValuePair<,>` or `dynamic`, and `spread_element` is only supported if the enumerated element type is some `KeyValuePair<,>` or `dynamic`. ## Syntax ambiguities [syntax-ambiguities]: #syntax-ambiguities * `dictionary_element` can be ambiguous with a [`conditional_expression`](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator). For example: ```c# var v = [ a ? [b] : c ]; ``` This could be interpreted as `expression_element` where the `expression` is a `conditional_expression` (e.g. `[ (a ? [b] : c) ]`). Or it could be interpreted as a `dictionary_element` `"k: v"` where `a?[b]` is `k`, and `c` is `v`. ## Resolved questions [resolved]: #resolved-questions * Should a `collection_literal_expression` have a *natural type*? In other words, should it be legal to write the following: ```c# var x = [1, 2, 3]; ``` Resolution: Yes, the *natural type* will be an appropriate instantiation of `List`. The following text exists to record the original discussion of this topic.
It is virtually certain that users will want to do this. However, there is much less certainty both on what users would want this mean and if there is even any sort of broad majority on some default. There are numerous types we could pick, all of which have varying pros and cons. Specifically, our options are *at least* any of the following: * Array types * Span types * `ImmutableArray` * `List` * [`ValueArray`](https://github.com/dotnet/roslyn/pull/57286) Each of those options has varying benefits with respect to the following questions: * Will the literal cause a heap allocation (and, if so, how many), or can it live on the stack? * Are the values of the literal mutable after creation or are they fixed? * Is the resultant value itself mutable (e.g. can it be cleared, or can new elements be added to it)? * Can the value be used in all contexts (for example, async/non-async)? * Can be used for *all* literal forms (for example, a `spread_element` of an *unknown length*)? Note: for whatever type we pick as a *natural type*, the user can always target-type to the type they want with a simple cast, though that won't be pleasant. With all of that, we have a matrix like so: | type | heap allocs | mutable elements | mutable collection | async | all literal forms | |-|-|-|-|-|-| | `T[]` | 1 | Yes | No | Yes | No* | | `Span` | 0 | Yes | No | No | No* | | `ReadOnlySpan` | 0 | No | No | No | No* | | `List` | 2 | Yes | Yes | Yes | Yes | | `ImmutableArray` | 1 | No | No | Yes | No* | | `ValueArray` | ? | ? | ? | ? | ? | \* `T[]`, `Span` and `ImmutableArray` might potentially work for 'all literal forms' if we extend this spec greatly with some sort of builder mechanism that allows us to tell it about all the pieces, with a final `T[]` or `Span` obtained from the builder which can also then be passed to the `Construct` method used by *known length* translation in order to support `ImmutableArray` and any other collection. Only `List` gives us a `Yes` for all columns. However, getting `Yes` for everything is not necessarily what we desire. For example, if we believe the future is one where immutable is the most desirable, the types like `T[]`, `Span`, or `List` may not complement that well. Similarly if we believe that people will want to use these without paying for allocations, then `Span` and `ReadOnlySpan` seem the most viable. However, the likely crux of this is the following: * Mutation is part and parcel of .NET * `List` is already heavily the lingua franca of lists. * `List` is a viable final form for any potential list literal (including those with spreads of *unknown length*) * Span types and ValueArray are too esoteric, and the inability to use ref structs within async-contexts is likely a deal breaker for broad acceptance. As such, while it unfortunate that it has two allocations, `List` seems be the most broadly applicable. This is likely what we would want from the *natural type*. I believe the only other reasonable alternative would be `ImmutableArray`, but either with the caveat that that it cannot support `spread_elements` of *unknown length*, or that we will have to add a fair amount of complexity to this specification to allow for some API pattern to allow it to participate. That said, we should strongly consider adding that complexity if we believe this will be the recommended collection type that we and the BCL will be encouraging people to use. Finally, we could consider having different *natural types* in different contexts (like in an async context, pick a type that isn't a ref struct), but that seems rather confusing and distasteful.
================================================ FILE: meetings/working-groups/discriminated-unions/Case Classes.md ================================================ This proposal has moved to: https://github.com/dotnet/csharplang/blob/main/proposals/case-declarations.md ================================================ FILE: meetings/working-groups/discriminated-unions/Closed Enums.md ================================================ This proposal has moved to: https://github.com/dotnet/csharplang/blob/main/proposals/closed-enums.md ================================================ FILE: meetings/working-groups/discriminated-unions/Closed Hierarchies.md ================================================ This proposal has moved to: https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md ================================================ FILE: meetings/working-groups/discriminated-unions/DU-2022-10-19.md ================================================ ## Notes for October 19th, 2022 The theme of our first meeting was collecting scenarios that we think can be improved by discriminated unions, so we have an idea of what problems we're trying to solve. ### PageState ```cs public enum PageState { NotLoaded, Loading, Loaded, Errored } public class Page { PageState state; // used when state is Loaded PageData data; // used when state is Errored string errorMessage; public View Render() { switch (this.state) { case PageState.NotLoaded: return ShowLoadButton(); case PageState.Loading: return ShowLoadingSpinner(); case PageState.Loaded: return ShowData(this.data!); case PageState.Errored: return ShowErrorMessage(this.errorMessage); default: throw Unreachable; } } } class PageData { } class View { } ``` * UI App scenario * Different page states - state machine transition * Has some state fields that are only applicable when in a particular state * Could be represented as a type hierarchy as well * Should be able to remove `throw Unreachable;` * Would you be able to define a `PageState.Show()`? * This is the OO fashion, we're really talking more functional. ### Web APIs ```cs [HttpGet(Name = "GetWeatherForecast")] public IActionResult Get(int id) { return id % 2 == 0 ? Ok(Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }).ToArray()) : NotFound(); } app.MapGet("/minimal-dus/{id}", Results, ProblemHttpResult> (int id) => { return id % 2 == 0 ? TypedResults.Ok("Valid ID") : TypedResults.Problem("Invalid Id"); }); ``` * `IActionResult` is very common in MVC * Worked around in minimal APIs by using `Results` (lots of versions of this, like ValueTuple) * All Results are `IResult`s * Does it matter if it's anonymous in some fashion or explicitly declared by the user? * What about subsets of the full union? * Just Ok or NotFound? * Error recovery: `Optional` or `Result` ### IDE APIs * IDE: message passing where something could be a subset of a larger group of possibilities * Heterogeneous data - not a subset of a larger type hierarchy. * Painful and not checked today: can only be one of the tuple elements, but we have to pass around and check these in a non-straightforward fashion ### Optional * Could we have semantics such that we can have a `Optional` in the BCL * Maybe an syntactic shortcut for this? * Result type would be useful for type metadata decoding: currently we throw exceptions. * Some sort of syntax help for short-circuiting these would be nice, a-la `?` in Rust. * Would hopefully be able to apply not just to Ok/Result monads, but other kinds as well. ### Type Hierarchy subsets https://github.com/dotnet/roslyn/blob/7b6fc9baa013ceb935973144543bcc422b8234d2/src/Compilers/CSharp/Portable/Binder/LockOrUsingBinder.cs#L44-L45 * More of an anonymous scenario * Subset of a larger union - homogenous data * Large DUs, such as SyntaxNode or BoundNode, are almost never useful as those large subsets. Smaller subsets, often defined ad-hoc, are usually what we want to work with. * Large unions might be hard to add to existing projects without large refactorings. Some projects might be able to do this, but many won't. The BCL won't for their public APIs. ### Generalized semantics * Are there advantages to targeting existing scenarios, and then having syntax that can make it easier to do? * Likely yes: will have wide compatibility with existing code. ### Next meeting For next time, we will look at the commonalities in these scenarios and start looking at strategies for representing them. ================================================ FILE: meetings/working-groups/discriminated-unions/DU-2022-10-24.md ================================================ ## Notes for October 24th, 2022 This meeting distilled the previous use case discussions to a few broad concepts of unions, and we talked about some basic features and tenets that we want to see in unions. ### Concepts * `Result`/`Option` * Very interested in syntactic shortcuts here. * Monads over the `T` inside * Forces users handle errors up front * Is short declaration of these types of unions necessary? * Maybe not? Maybe if the BCL ships Result/Option itself, people may not actually need to declare many of their own versions of these. * Internal State with associated data * UI States * `BoundNode` * Subsets of these are often useful: State machines only transitioning from X -> Y, not Z -> Y, or only accepting certain types of expressions. * Anonymous definition of these might be useful. `(BoundLiteral | BoundMethodGroup)` inline, for example * Simplifies handling of state and state transitions, potentially enforces explicit handling of cases. * `OneOf` * Passing heterogeneous data around * Anonymous definition might also be useful * How would this reconcile with a homogeneous subset? * Potentially easier to integrate into existing codebases ### Union versioning and compat * What's our compat strategy for public APIs with this type of union in them? * Matters particularly in the same areas as variance: what happens when public apis start returning a new result? * Or stop accepting a result variant? * Maybe this one doesn't matter as much? * Source compatibility in an return position is not guaranteed when revving a union type * Binary compatibility in a input position? * Historically, we've cared about this * If my public function accepts a new case, all existing code should still work * On the other hand, if I add a new input to some interface a consumer is implementing, they may get a case they weren't expecting * In general, we're not worried about maintaining binary compatibility. We just want to make sure that API authors can be intentional about when they want to make a breaking change and make it obvious that they did. * Do we want to _ensure_ that binary breaks happen when users encounter new cases? * No. If it falls out then it falls out, similar to required members. ### Opaque unions * F# has these: ```fs type C = private // or internal | Case1 of int ``` These create an interchange type that appears as a union to the file or assembly that created them, but is opaque to consumers. Do we need this? * We think this is probably just existing base types/interfaces in C# ### Exhaustiveness * List all valid subtypes in the base type? * Compiler error when adding a new type? * This is Java's approach in JEP 409 - Sealed Classes * For sufficiently large hierarchies, maybe exhaustiveness isn't always as useful as hoped? * May often be working on subsets, and forcing handling of all unexpected values might erode desired errors when adding new expected cases. ### Conclusions * Subsets of larger unions keep coming up in our discussions, though we need to make sure we're not being overly colored by roslyn. * Subsets of unions may call for an anonymous union syntax. * Having different implementation details for sets of homogeneous data and sets of heterogeneous data would likely be confusing for users. A singular lowering for subsets is likely needed. * Compatibility with existing forms is important. Users should not have to perform large refactorings to take advantage of any improvements we come up with here. * Importantly, we're not looking to deprecate existing forms of creating hierarchies. * Experiencing source breaks when adding or removing new cases to unions is likely both unavoidable and desirable. We're ok with binary breaks as well, but won't be attempting to _ensure_ that they occur. ================================================ FILE: meetings/working-groups/discriminated-unions/DU-2022-10-31.md ================================================ ## Notes for October 31st, 2022 ### Pseudo-syntax This meeting starting by defining a pseudo-syntax to help us understand what everyone refers to when talking about examples. This syntax is: ```cs union [Name] { A, B, C } // tagged, internal (members declared within type) union [Name] ( A | B | C ) // untagged, external (members declared outside type) union [Name] { A(int x), B } // tagged with values (mixed) union [Name] { A = 10 } // tagged with values union [Name] { 10 | 20 } // untagged, external, with values union [Name] { ..Base } // all known derived types of Base (splatting). Covered by `union Name { Base }` ``` We arrived at these from looking at a matrix of all the different possible dimensions of union types: * Named vs unnamed union type * Tagged vs untagged cases * Values vs no values * Internal vs external members The `[Name]` sections are optional, which deals with the first bullet. The rest of the bullets are explicitly covered, with some exceptions for scenarios we didn't think were realistic (such as internal untagged value unions). ### Equivalence We next turned our thoughts to untagged external unions and equivalence: should `union (A | B) == union (B | A)`, ignoring implementation details as much as we can. We considered multiple levels of this type of conversion: * No conversion * Implicit (non-identity conversion) * Identity conversion Implementation details keep rearing their heads here, though we tried to avoid talking about them. We generally said we wanted some amount of conversion here, and which conversion can impact scenarios such as: ```cs // Overloading void M(union (A|B) x); void M(union (B|A) x); // Type unification between ternary branches static (T | U) F(bool b, T t, U u) => b ? t : u; var x = true ? F(cond1, 1, "hello") : F(cond2, "world", 2); // type of var? union (int | string) or union(string | int) ``` Another way of wording this is, do people think that ordering matters when they type something like this? After some debate, we settled on thinking that it doesn't matter. Adding order effectively implies adding a tag, and we're specifically looking at untagged, anonymous unions of external types in this case. We also looked at equivalence in unions in switching: ```cs union (A | B) u = ...; switch ((object)u) { case union(B | A): Console.WriteLine("Union matched"); // Should this work? break; } ``` We feel like this should work, which raises the question of whether the runtime needs to be involved in the scenario: if we went with a `OneOf` implementation, then the conversion would likely need to unbox to just `A` or `B`. We then need to consider whether we even want a union type to be allowed as a type pattern or not. Generics make all of this more difficult; the unboxing is entirely implementable by the compiler when we know about the types ahead of time, but in generic methods the precise types won't be known until runtime. We need to think more about this, so we'll revisit these rules again in a later meeting. ================================================ FILE: meetings/working-groups/discriminated-unions/DU-2022-11-07.md ================================================ ## Notes for November 7th, 2022 As a reminder, we use [this syntax](DU-2022-10-31.md#pseudo-syntax) for describing unions forms. ### Conversions Pickup up from last week, we talked about equivalency again. This time, we started by focusing on the differences between `union (A | B)` (an anonymous union of types) and `union NamedAOrB (A | B)` (a named union of types). We do generally think that `union (A | B)` and `union (B | A)` should at least be implicitly convertible, if not identical, and that has implications on the grow up story. Much like with tuples, we don't think anonymous union types belong in public APIs, except in cases like `Zip` where the implementation detail of the tuple is the _purpose_ of the API (in other words, when the likelihood of adding or removing a new element is nonexistent). Tuples that need to be in public API generally want to grow up to be positional records, and we might need something similar here for untagged named unions. In thinking about it more, though, we're not certain of the need for exporting this type of union. It would have limitations, such as the inability to have two different cases with the same type (a string result or string error, for example, couldn't be represented without a separate tag), so it's possible that the roles proposal is enough for us here. Something like `role NamedAOrB : (A | B);` would give a name to the case, give us equivalency with the underlying type, and maintain discouragement of putting untagged unions in public type hierarchies. ### Switching on unions We considered a few different ways that users might want to pattern match against anonymous unions of types: ```cs void M((int|string) x) { object o = x; _ = o switch { (int|string) => 0, _ => 0 }; _ = o switch { int or string => 1, _ => 1 }; _ = o switch { int => 2, string => 2, _ => 2, }; // Switch on x _ = x switch { int => 2, // Could use x.IsTypeOf string => 2, _ => 2, }; } ``` There are a few ways this could work: 1. Erase the union, and have it be object. Type tests are emitted as today. 2. Check for an IsOneOf marker interface. Would have to always check, behavior change on recompile. 3. When switching on the union itself, call an `IsType` method. 4. Have the runtime participate in boxing, throwing away a wrapper type. We don't have any decisions yet, we're still exploring the space. We'll keep exploring further. ================================================ FILE: meetings/working-groups/discriminated-unions/Nominal Type Unions.md ================================================ This proposal has moved to: https://github.com/dotnet/csharplang/blob/main/proposals/case-declarations.md ================================================ FILE: meetings/working-groups/discriminated-unions/Runtime Type Unions.md ================================================ # Runtime Type Unions ## Summary Runtime type unions are C# type unions built on top of support for type unions in the runtime. Each union has a closed "list" of case types. They can be used without declaration via an anonymous type expression syntax: ```csharp (int or string) x = 10; ``` Or declared with a name: ```csharp union IntOrString(int, string); ``` Union specific case types can be succinctly declared if we adopt the "Case Classes" proposal: ```csharp union Pet { case Cat(...); case Dog(...); } ``` Case types can be implicitly converted to the union type: ```csharp (int or string) value = 10; Pet pet = new Dog(...); ``` Or explicitly converted: ```csharp Pet pet = (Pet)obj; // convert from unknown to union Dog dog = (Dog)pet; // convert from union to case type ``` Patterns apply to the contents of a union. Switches on all cases types are exhaustive: ```csharp var name = pet switch { Cat cat => ..., Dog dog => ... } ``` While there is no inheritance relationship between unions and case types, matching to a runtime union type succeeds when the value on the left matches any of the case types. ```csharp _ = obj is Pet; // True if obj is Cat or Dog ``` ## Motivation To provide a canonical type union in C# that is also fully supported in the runtime and reflection, so it works automatically in all contexts. ## Runtime Support The runtime is aware of a special union type that is used only descriptively in metadata to describe the union, but is actually represented at runtime as a simple object reference. Certain IL operations are handled specially for values with this encoding to help guarantee that those values are compatible with one of the case types of the union. ### The Union Type ```csharp System.Union ``` The runtime is aware of a special abstract generic class `System.Union`. This type can never be allocated, but it can be used in signatures, fields and local variables as a way to provide more information for a value that is ultimately just an object reference. This type has two generic type parameters, both of which can either describe a case type or another union type when constructed. For example, ```csharp Union // int or string Union> // int or string or double Union, Union> // int or float or string or double ``` In a sense, the runtime unravels the constructed union type to determine its set of case types. It uses this knowledge of the case types to implement specific IL instructions. ### Type Non-Equivalence Union types constructed differently are different types. ```csharp Union =/= Union ``` However, there may be a conversion relationship between them. ### Type Conversion When a union type is used as the target type of an `ISINST` or `CASTCLASS` IL instruction, these instructions succeed when the source runtime type would succeed on any one of the target union's case types, and fail when no case type would succeed. When a union type is used as the static source type of an `ISINST` or `CASTCLASS` IL instruction, the union type is ignored and the runtime type is used always. (This is already how the runtime functions.) The runtime is free to optimize away the actual type checks of the conversion when it knows that the source and target static types are compatible. *Note: CASTCLASS succeeds when the source is a null value and target is a reference type or Nullable type.* ### Named Sub Types The union type `Union` is allowed to have sub-types. ```csharp public sealed abstract class MyUnion : Union; ``` These types must be abstract classes that declare no members, except for nested type declarations, and no type my derived from them (sealed abstract?). These sub-types serve only as a means to give a union a name and to set it apart from other union types so they will be considered unique in signatures. However, the same assignment and conversion rules still apply. #### Type Parameter Constraints A type union type can be used in a type parameter constraint. However, it does not mean anything other than a constraint on normal sub typing. If you constrain a type parameter to a union type `Union` then the type parameter must be that type or a sub-type of that type. ```csharp public abstract class IntOrString : Union; public void M() where T : Union {...} M>(); M(); M(); // Error! M(); // Error! M<(string, int)>(); // Error! ``` ### Reflection Reflection API assigning values to a field or parameter that is a union type will allow case types to be assigned (with runtime checks), as there is no way to actually have an instance of a union type. ```csharp void M(Union value) {...}; ... MethodInfo methodM = ...; method.Invoke(instance, new object []{ 10 }); // boxed int assignable to union ``` The methods `Type.IsAssignableFrom` and `Type.IsAssignableTo` will return true if a value of the source type is assignable to any of a target union's case types. If both source and target types are union types, the methods will return true if all the source union's case types are assignable to one of the target union's case types. The runtime `Type` class will add the following members to help tools and libraries using reflection understand when a type is actually a union. ```csharp public class Type { public bool IsUnion { get; } public Type[] GetUnionCaseTypes(); } ``` ## Language Support ### Anonymous Type Unions A type expression exists to refer to a runtime union type without specifying a name. These anonymous unions unify across assemblies since they are all translated to the same generic system type. #### Grammar ```csharp anonymousUnionType := '(' ('or' )* ')'; ``` #### Lowering An anonymous type union is a synonym for an equivalent System.Union. |Union Syntax|Lowered Type| |------|-------| |`(A or B)` |`Union` | |`(A or B or C)` |`Union>` | |`(A or B or C or D)` |`Union>>`| ### Nominal Type Unions A nominal type union exists to give a type union a name, since repeatedly writing anonymous union expressions for unions with multiple cases gets tedious after about three. You declare one minimally using the `union` keyword, a name and a list of type references. ```csharp union Pet (Cat, Dog, Bird, Fish, Turtle, Rabbit); ``` The nominal type union has no members other than those inherited from object, yet it may declared nested types. Because of this, a nominal type union can declared its case types as part of the overall declaration. ```csharp union Pet(Cat, Dog) { public record Cat(...); public record Dog(...); ... } ``` If using the [Case Classes](#) proposal, there is no need to also specify the nested type in the type reference list, allowing the declaration to become simpler: ```csharp union Pet { case Cat(...); case Dog(...); ... } ``` #### Grammar ```csharp nominalUnionType := * ? 'union' ()? ()? ()* ('{' '}' | ';') ``` #### Lowering Nominal type unions are lowered to a declaration of a sub-type of the `System.Union` type. ```csharp public union Result(Success, Failure); ``` is lowered to: ```csharp public sealed abstract class Result : System.Union, Failure>; ``` #### Assignment When the target of an assignment is a type union, and the source is assignable to one of the case types, the assignment can be made without an explicit conversion (cast). When the source of an assignment is also a type union, and each of the source's case types can be assigned to any one of the target's case types, the assignment can be made without an explicit conversion. ```csharp (int or string) x = 10; (int or string or double) y = x; int z = x; // error! (int or double) = x; // error! ``` Regardless, the assignment is encoded as a conversion generating a `CASTCLASS` instruction, unless the [Runtime Assignable Conversion](#runtime-assignable-conversions) feature is implemented. #### Conversion C# is aware of type unions. Casts and pattern matching allow union types in both the source and target position, and all are handled by the same runtime IL as used today. However, normally the C# compiler will report an error when the source of a cast or pattern match will never match the target type. This rule is modified for type unions. When a union is the source of a cast or pattern match, no error is given if the target type is compatible with at least one of the source's case types. ```csharp (int or string) x = ...; var r = x switch { int value => ..., string value => ..., double value => ... // error! this will never match } ``` When a union is the target of a cast or pattern match, no error is given if the source type is compatible with at least one of the target's case types. ```csharp int x = ...; var r = x switch { int value => ..., (int or string) => ..., (string or double) => ... // error! this will never match } ``` When both the source and target types are union types, no error is given if the target and source case types have at least on compatible combination. ```csharp (int or string) x = ...; var r = x switch { (string or int) => ..., (int or string or double) => ..., (string or double) => ..., (double or float) => ... // error! will never match } ``` #### Exhaustiveness A `switch` with a source value that statically has a type union type is exhausted if all case types are handled in the switch. If the switch is exhausted, no default case need be specified. ```csharp (int or string or double) x = ... var r = x switch { int value => ..., string value => ..., double value => ... } ``` #### Nullability When one of a type union's case types is a type that can be null, a nullable reference type or nullable value, the type union is considered nullable or may be null by the null type checker and may be assigned a null value. ```csharp (int or string?) x = null; // legal ``` In addition, a type union without a nullable case type may still be considered nullable if the type is referred to using the question-mark syntax or is inferred to be possibly null using the existing null type checker rules. ```csharp (int or string)? x = null; // legal ``` Specifying both is unnecessary, but also allowed. ```csharp (int or string?)? x = null; // legal ``` However, even when a case type is declared as nullable, you cannot use a nullable type in a type pattern match. Yet, as with any reference type, when a type union is nullable it may be matched with a null pattern match. ```csharp (int or string)? x = ...; if (x is null) {...} ``` A switch with a nullable type union must include a null pattern match in order to be considered exhausted. ```csharp (int or string?) x = ...; var r = switch { int value => ..., string value => ..., null => ... } ``` **Question:** Can you assign a value of a nullable variation of a case type to a nullable type union when the case type itself is not nullable? ```csharp int? v = ...; (int or string?) x = v; // Error? ``` How about now? ```csharp int? v = ...; (int or string)? x = v; // Error? ``` There is no actual violation of the type system. A value with this type may be null or not. However, the intuition may be that this is not possible for one or both. Could maybe the first case be a warning, since you went out of your way to describe the case types and either being nullable or not? But the second sort of implies that any value could be nullable. ```csharp (int or string)? <===> (int? or string?) ``` **Question:** If type parameters are used as case types, without constraints that would imply nullability of the case type, should the type union be considered nullable? ```csharp void Method() { (T1, T2) x = null; // allowed? var r = x switch { T1 value => ..., T2 value => ..., null => ... // necessary? } } ``` ## Drawbacks - No back-compat: Only works in newer runtimes and cannot be reasonably used via older language versions. - Delay: Getting these features into a new runtime may take a long time. - Boxing: No non-boxing solution. ## Optional Features Optional features are stretch goals. ### Runtime Assignable Conversions The runtime IL type checker is enhanced to understand the assignment rules without requiring a `CASTCLASS` instruction to convert between different (though compatible) union types. 1. A source value can be to a target with a union type if the source value's static type is assignable to at least one of the target union's case types. 2. A source value with a union type is assignable to a target with a different union type if each of the source's case types are assignable to at least one of the target's cast types. ### Type Parameter Union Constraints To have a more meaningful constraint for type unions, a new kind of constraint must exist that constrains against the union case membership. This needs to be represented specially in metadata and would require different syntax in C#. A new constraint operator exists C# that means union subtype. Instead of `:`, we use the `in` keyword. The type parameter `T` matches if it has a `:` relationship with any one of the constraint's case types, or if it is a union itself then each case type must have a `:` relationship with one of the constraint's case types. If the constraint's type is not a union, then the `in` operator has the same meaning as the `:` operator. ```csharp record Animal; record Dog : Animal; class AnimalOrInt : Union; void Method() where T in (int or Animal) {...} Method(); // okay Method(); // okay Method(); // okay Method(); // Error! Method>(); // okay Method>(); // okay Method>(); // okay Method(); // okay Method>(); // Error! ``` This means T is constrained to be either an int or Animal. If a generic type parameter is used as part of generic constraint of another type parameter, and that type parameter is constructed with a union type, the constraint checker sees that it is a union and applies the same rule (at whatever point this normally happens, loading, jit, e tc). ```csharp void Method() where T in U {...} Method(); // okay Method(); // okay Method(); // okay Method(); // Error! Method<(int or Animal), (int or Animal)>(); // okay Method<(Animal or int), (int or Animal)>(); // okay Method<(int or Dog), (int or Animal)>(); // okay Method(); // okay Method<(int or double), (int or Animal)>(); // Error! Method(); // okay Method<(int or Animal), AnimalOrInt>(); // okay ``` ================================================ FILE: meetings/working-groups/discriminated-unions/Trade Off Matrix.md ================================================ # Union Trade Offs ## Solution Space There are only three possible ways to represent a type that behaves like a discriminated union or type union in C# without a large runtime overhaul. 1) As a class Hierarchy ```csharp // example: a record hierarchy public abstract record MyUnion { public record Case1(...) : MyUnion; public record Case2(...) : MyUnion; public record Case3(...) : MyUnion; } MyUnion union = new MyUnion.Case1(...); ``` 2) As an object reference ```csharp object union; // only assign Case1, Case2 or Case3 here please. ... union = new Case1(...); ``` 3) As a wrapper type ```csharp // example: a struct wrapper with constructors and a value property public struct MyUnion { public MyUnion(Case1 value) {...} public MyUnion(Case2 value) {...} public MyUnion(Case3 value) {...} public object Value { get; } } MyUnion union = new MyUnion(new Case1(...)); ``` These are exactly what developers use when defining the equivalent of discriminated unions and type unions in C# code today. However, none of these user defined solutions allow for exhaustiveness in pattern matching, because they are either too open ended or not understood as being closed by the runtime and language. The following proposals offer solutions for exhaustiveness and more, with a matrix of trade-offs between them. - The `Closed Hierarchies` proposal offers an easy way to make class hierarchies closed. - The `Runtime Type Unions` proposal offers a special kind of object reference that is understood by the runtime. - The `Nominal Type Unions` proposal offers a wrapper type solution understood by the language. ## Trade Off Matrix | Feature | Runtime Type Unions | Nominal Type Unions | Closed Hierarchies | |---------------------------------------|----------------------|---------------------|--------------------| | Declared Cases | **Yes** | **Yes** | **Yes** | | Singleton Cases | **Yes** | **Yes** | **Yes** | | Existing Cases | **Yes** | **Yes** | | | Anonymous Syntax | **Yes** | | | | Pattern Matching | **Yes** | **Yes** | **Yes** | | Dynamic Pattern Matching | **Yes** | | **Yes** | | Subtype Relationship | | | **Yes** | | Conversion Relationship | **Yes** | | **Yes** | | Back-Compat | | **Yes** | **Yes** | | Non-ABI Breaking | *named only* | **Yes** | **Yes** | | Non-Allocating/Boxing | | *future* | | | Custom Unions | | *future* | | | Any Time Soon | | **Yes** | **Yes** | - *Declared Cases* - Case types can be declared as part of the union type declaration. - *Singleton Cases* - Singleton cases can be declared and used without additional allocations. - *Existing Cases* - Existing types can be used as cases w/o declaring and allocating case type wrappers. - *Anonymous Syntax* - A type expression syntax exists to refer to a union without declaring a named typed. - *Pattern Matching* - Pattern matching works directly on union instance. - *Dynamic Pattern Matching* - Pattern matching works when union types are not statically known. - *Subtype Relationship* - A subtype relationship exists between Union and Case types. - *Conversion Relationship* - A conversion relationship exists between Union and Case types. - *Back-Compat* - Union types can be used with older runtimes and older language versions. - *Non-ABI Breaking* - Adding or reordering cases does not cause binary breaks. - *Non-Allocating/Boxing* - Union and case values can be used without any kind of allocation or boxing. - *Custom Unions* - Possible to declare a type that behaves like a union type in the language but has a custom implementation. - *Any Time Soon* - Could appear in next few C# releases. ================================================ FILE: meetings/working-groups/discriminated-unions/TypeUnions.md ================================================ # Type Unions for C# Champion issue: ## Summary [summary]: #summary A proposal for type unions (aka discriminated unions) in C#. ## Motivation When developing software you may encounter situations where the values that you want to store in a variable are not always the same kind each time through. While you are usually not concerned about storing strings and numbers in the same spot, you may need to store one of a few related types depending on what that data is meant to represent at that moment. For example, your application may have both a customer and a supplier definition that share only some of the same properties and you may need to perform a similar operation on both in a fashion that depends on the differences. Typically, this is where you might choose to distribute those specialized implementations into the types themselves and expose them through common abstract methods or interfaces. However, this is only good practice when those types exist primarily for the purpose of the operation or it makes sense for the operation to appear as an intrinsic part of the type. If the types have a broader purpose, polluting them with methods like this can be undesirable. The alternative is to make the same logic handle both types, and if you do this, at some point you will need to declare a parameter or variable that can contain either. You might think you can still solve this through inheritance, by defining both `Customer` and `Supplier` as classes in a hierarchy with a common base type like `Contact`. However, if you are not able to define such a relationship, because either you don't own the definition of these types, or you have too many similar situations and can only solve one of them through inheritance or you choose to not leak the requirements of the specific operation into the definition of the data, the only easy choice you have is to declare the variable as object and let it be just anything. While this may work, it leaves you policing your code through documentation and comments. If you are brave, you can devise such things as special-case hierarchies of wrapper types to put around your values, or custom aggregate types that act as guardians around all the kinds of values you want to possibly store in the variable, which is time consuming and cumbersome, especially if you have many similar situations but they all involve different sets of types. It would be better if C# gave you a way to declare a type that allows you to store one of a limited number of other types in the same place, and let it do all the hard work guarding the variables for you. Many other languages already do this. They typically call these special types discriminated unions, tagged unions, sum types or type unions. All of them solve the problem of allowing a single variable to hold values of one or more limited forms. It is time C# had a feature that did this too. ### Solutions You might imagine that the most appropriate implementation for union types in C# is as a hierarchy of classes with an abstract base representing the union itself and all the specific cases of the union as derived classes, just like you or I might make to solve the problem on our own, because it fits really well with the concepts already in the language. This usually works well when you have a specific use case in mind as you design a specific set of classes to solve that specific problem. However, there are some drawbacks to implementing unions as class hierarchies. One is the inability to constrain the hierarchy, as object-oriented languages are usually open for inheritance. > I know there are only three possible subtypes, why does the compiler require me to have a default in my switch expression? Another is the inability to represent unions of unrelated types that exist outside a single hierarchy or even to restrict values to a subset of types from within the same hierarchy. > I want this parameter to be restricted to only Cats and Dogs, not all Animals. Because of the class hierarchy implementation, the only way to include a value from a type that already exists is to use a class that is part of the union hierarchy to wrap the value. > I either have to type these fields as object and trust myself and my team to always do the right thing, or wrap my values in new class instances each time I want to store them in the variable. And lastly, classes in C# require allocation to represent and cannot contain values such as ref types, which may be requirements for specific scenarios. > I wish I could use unions in my graphics pipeline, but they cause too much gen 0. For these reasons, it may be necessary to have more than one kind of union, as it may not be possible to satisfy some use cases without compromising others, and if there are multiple kinds it is best to strive to make them appear to look and work the same as as much as possible. This proposal attempts to provide solutions to all use cases by declaring four categories for them to fall into, listing some examples for each. - [Standard](#standard---union-classes) - Use cases where the union and its members can be defined together, because they have a predominant reason to belong together and you intend to use the members as classes on their own. Allocation is not a problem because you would have been allocating the classes regardless. - Protocols, serialization and data transfer types - UI data models (XAML) - Syntax trees - Infrequently changed state machine states - Other polymorphic data models - Values that last a while in union form (fields/properties) - [Specialized](#specialized---union-structs) - Use cases that need to avoid allocations or require use of special types and are willing to accept some limitations to achieve it. - Allocated in contiguous arrays - Mapped over blocks of memory (interop) - Frequently changed state machine states - Values that last briefly in union form (arguments/return values) - Library types with potentially specialized uses - [Ad Hoc](#ad-hoc---ad-hoc-unions) - Use cases that require unions to be formed from existing, possibly unrelated, types and where similarly declared unions with the same member types are interchangeable with one another. - Same examples as standard. - [Custom](#custom-unions) - Use cases that do not fit well with the other categories. - Already existing types and hierarchies that cannot easily be redefined. - Custom storage layouts. - Custom API shapes and behaviors. *Note: Pre-declared unions like Option and Result are proposed in the [Common Unions](#common-unions) section.* *Note: Many of the examples are written in a shorthand syntax made possible by related proposals briefly described in the [Related Proposals](#related-proposals) section.* ---- ## Standard - Union Classes A union class is a named type union that declares all its member types in a single self-contained declaration. ### Declaration A union class is declared similar to an enum, except each member is a type that itself can hold state in one or more state variables. union U { A(int x, string y); B(int z); C; } For each member, only the name and the list of state variables may be specified. ### Construction Union classes are constructed via allocation of the member type. U u = new A(10, "ten"); The type of the constructed member is the member type `A`. It is converted to type `U` when assigned to variable `u`. ### Deconstruction Union classes are deconstructed by type tests and pattern matching. if (u is A a) { ... } if (u is A(var x, var y)) { ... } if (u is A { y: var y }) { ... } ### Exhaustiveness Union classes are considered exhaustive. If all member types are accounted for in a switch expression or statement, no default case is needed. var x = u switch { A a => a.x, B b => b.z, C c => 0 }; ### Nullability Nulls can be included in a union class variable using the standard nullability notation. U? u = null; ### Implementation A union class is implemented as an abstract record class with the member types as nested derived record classes. [Closed] abstract record U { public record A(int x, string y) : U; public record B(int z) : U; public record C : U { public static C Singleton = new C(); }; } *Note: The `Closed` attribute allows the language to understand that the type hierarchy is closed to sub-types declared outside the base type's module. See the following section [Related Proposals](#related-proposals).* *Note: Nested member types may be referred to without qualification using related proposal.* ---- ## Specialized - Union Structs Similar to a union class, a union struct is also a named type union that declares all its member types in a single self-contained declaration, except the union and the member types are all structs and are able to be used without heap allocation. ### Declaration A union struct is declared similarly to a union class, with the addition of the `struct` keyword. union struct U { A(int x, string y); B(int z); C; } For each member, only the name and the list of state variables may be specified. ### Construction Union structs are constructed via allocation of the member type. U u = new A(10, "ten"); The type of the constructed member is the member type `A`. It is converted to type `U` when assigned to variable `u`. ### Deconstruction Union structs are deconstructed by type tests and pattern matching. if (u is A a) { ... } if (u is A(var x, var y)) { ... } if (u is A { y: var y }) { ... } ### Exhaustiveness Union structs are considered exhaustive. If all member types are accounted for in a switch expression or statement, no default case is needed. var x = u switch { A a => a.x, B b => b.z, C c => 0 }; ### Nullability Nulls can be included in a union struct variable using the standard nullability notation. U? u = null; ### Default Union structs can be in an undefined state due to being unassigned or assigned default. This state will not correspond to any declared member type, leading to a runtime exception in a switch that relies on exhaustiveness. U u = default; // switch throws, since not A, B or C var x = u switch { A a => a.x, B b => b.z, C c => 0 } To help avoid this, the compiler will produce a warning when a struct union is assigned default. // warning: default not a valid state U u = default; You may also avoid the warning by declaring a default state for the union struct, associating a member type as the default. union struct U { A(int x, string y); B(int z); C = default; } ### Implementation A union struct is implemented as a struct with nested record structs as member types and an API that converts the member types to and from the aggregate union struct. The interior layout of the union struct is chosen to allow for efficient storage of the data found within the different possible member types with tradeoffs between speed and size chosen by the compiler. [Union] struct U { public record struct A(int x, string y); public record struct B(int z); public record struct C { public static C Singleton = default; }; public static implicit operator U(A value) {...}; public static implicit operator U(B value) {...}; public static implicit operator U(C value) {...}; public static explicit operator A(U union) {...}; public static explicit operator B(U union) {...}; public static explicit operator C(U union) {...}; public bool TryGetA(out A value) {...}; public bool TryGetB(out B value) {...}; public bool TryGetC(out C value) {...}; public enum UnionKind { A = 1, B = 2, C = 3 }; public UnionKind Kind => {...}; } *Note: The `Union` attribute identifies this type as a union struct type.* *Note: A union struct with a default state has its corresponding `UnionKind` declared as 0.* *Note: this full generated API of the union struct is not shown.* ### Type Tests Whenever a type test is made against a known union struct, the union structs API is invoked to determine the outcome instead of testing the union struct's type itself. For example, the expression: u is A a is translated to: u.TryGetA(out var a) And the switch expression: u switch { A a => a.x, B b => b.z, C c => 0 } translates to: u.Kind switch { U.UnionKind.A when u.TryGetA(out var a) => a.x, U.UnionKind.B when u.TryGetB(out var b) => b.z, U.UnionKind.C when u.TryGetC(out var c) => 0, _ => throw ...; } ### Boxed Unions A union struct that is boxed is a boxed union struct, not the boxed value of one of its member types. You may never need to be concerned about this since the primary use case for a union struct is to avoid boxing. However, it may be necessary on occasion to box a union struct. Normally, type tests for two unrelated structs would never succeed when the boxed value is one type and the test is for the other. However, union struct types and their members are related to each other and so it is possible to type test and unbox a boxed union struct into one of its member types. U u = ...; object value = u; // will succeed since A is known to be a member type of U if (value is A a) {...} Translates to: if (value is A a || (value is U u && u.TryGetA(out a))) {...} Likewise, a boxed member type can be tested and unboxed into a union struct. A a = ...; object value = a; // will succeed since U is known to have member type A if (value is U u) {...} Translates to: if (value is U u || U.TryCreate(value, out u)) { ... } However, when neither type in the test is statically known to be related to a union struct, the type test will fail. bool IsType(object value) => value is T; U u = new A(...); // always fails if (IsType(u)) {...} This is because the language will not special case the type test when no union struct members are involved, since its not clear which type to check for. Checks for a common union interface could be made to work, but that would unduly impact the vast majority of type tests that do not involve union structs. ### Reflection You may be required to interact with union structs when using reflection. For example, you may need to pass a union struct to a method, but you have a boxed instance of the member type `A` and not the union struct type `U`. You will need to convert the boxed `A` value to a boxed `U` value. The struct union feature provides utility methods to help convert between boxed union structs and boxed member types at runtime. public static class TypeUnion { public bool TryConvert(Type unionType, object value, out object? boxedUnion); public bool TryConvert(object value, out TUnion union); public object? GetValue(object? boxedUnion); } *Note: Union classes and ad hoc unions do not require conversion since they are already in the correct form for reflection use.* ### Ref Union Structs A union struct with the `ref` modifier may contain state variables that are refs or ref structs. ref union struct U { A(ref int x); B(ReadOnlySpan y); C; } In this case, both the implementation of the union and the member types with ref struct values are translated to ref structs. ref struct U { public ref struct A { public ref int x; public A(ref int x) {...}; } public ref struct B { public ReadOnlySpan y; public B(ReadOnlySpan y) {...} } public record struct C { public static C Singleton = default; } ... } *Note: The impacted member types may be able to continue to be record structs if a ref record struct type is added to C#.* ---- ## Ad Hoc - Ad Hoc Unions Ad hoc unions are anonymous unions of types declared elsewhere. ### Syntax You refer to an ad hoc union using the `or` pattern syntax with parentheses. (A or B or C) ### Naming You may desire to refer to an ad hoc union using a common name. To do this, use a file or global using alias. global using U = (A or B or C); ### Construction Ad hoc unions are constructed by assigning an instance of one of the union's member types to a variable of the ad hoc union type. record A(int x, string y); record B(int z); record C() { public static C Singleton = new C(); }; (A or B or C) u = new A(10, "ten"); The type of the constructed member is the member type `A`. It is converted to type `(A or B or C)` when assigned to variable `u`. ### Deconstruction Ad hoc unions are deconstructed using type tests and pattern matching. if (u is A a) {...} if (u is A(var x, var y)) { ... } ### Exhaustiveness Ad hoc unions are considered exhaustive. If all member types are accounted for in a switch expression or statement, no default case is needed. var x = u switch { A a => a.x, B b => b.z, C c => 0 }; ### Nullability Nulls can be included in an ad hoc union using the standard nullability notation. (A or B)? x = null; ### Equivalence Ad hoc unions with the same member types (regardless of order) are understood by the compiler to be the same type. (A or B) x = new A(10, "ten"); (B or A) y = x; ### Assignability #### Super and subset assignability Ad hoc unions with the same or a subset of member types are assignable to ad hoc unions with a super set, without runtime checks. (A or B) x = new A(10, "ten"); (A or B or C) y = x; Ad hoc unions with a superset of member types are assignable to ad hoc unions with a subset, with explicit coercions and runtime checks. (A or B or C) x = new A(10, "ten"); var y = (A or B)x; #### Subtyping assignability A value of one ad hoc union type can be implicitly coerced to a value of another ad hoc union type without runtime checks if all member types of the source union are the same type or a sub type of at least one of the target union's member types. (Chihuahua or Siamese) pet = ...; (Cat or Dog) animal = pet; Otherwise an explicit coercion can be made involving runtime checks if at least one member type of the source union is a sub type of one of the member types of the target union. (Cat or Chihuahua) mostlyCats = ...; (Dog or Siamese) mostlyDogs = (Dog or Siamese)mostlyCats; For the purposes of assignability, you may consider a type that is not an ad hoc union to be an ad hoc union of a single type. Siamese pet = ...; (Cat or Chihuahua) mostlyCats = pet; Dog dog = (Dog)mostlyCats; *Note: This works for implemented interfaces too.* #### Generalized Coercions A value of a type can be implicitly coerced to a union type if an implicit coercion from that type to one of the union's member types exists. (string or double) value = 10; A value of an ad hoc union can be implicitly coerced to a type if all member type's of the union can be implicitly coerced to the type. (int or short) value = 10; double value2 = value; A value of an ad hoc union can be explicitly coerced to a type if one of the member types is coercible to the type. (string or double) value = 10.0; int value2 = (int)value; A value of an ad hoc union type can be implicitly coerced to another ad hoc union type if all member types of the source union type can be implicitly coerced to one of the member types of the target union type. (int or short) value = 10; (float or double) value2 = value; A value of an ad hoc union type can be explicitly coerced to another ad hoc union type if at least one member of the source union type can be explicitly coerced to one of the member types of the target union type. (float or double) value = 10.0; (int or short) value2 = (int or short)value; *Note: Need rule for which coercion if multiple are possible.* *Note: This assignability relationship is not intended to be a sub typing relationship. One ad hoc union is not a sub type of another ad hoc union.* ### Interchangeability Ad hoc unions with the same member types are interchangeable through generics and array elements. For example, constructing an array of ad hoc unions of generic type parameters, will return an array that is compatible with an array of ad hoc unions of concrete types. (T1 or T2)[] F(T1 v1, T2 v2) => new (T1 or T2)[] { v1, v2 }; (Dog or Cat)[] pets = F(rufus, petunia); Likewise, IReadOnlyList<(Cat or Dog)> pets = F(rufus, petunia); ### Covariance and Contravariance Ad hoc union types used as with generic type arguments can be used with covariance and contra-variance, if all member types of the two ad hoc unions involved have sub type relationships with members of the other. I'd tell you the specific rules, but it hurts my head to think about it. void Groom(IEnumerable<(Dog or Cat)> animals) => ...; List<(Chihuahua or Siamese)> pets = ...; Groom(pet); *Note: Have Mads write this part.* ### Patterns Ad hoc unions may be used in pattern matching and behave similarly to the `or` pattern, and may also have a variable declaration. if (u is Dog or Cat) { ... } // normal 'or' pattern if (u is (Dog or Cat)) { ... } // type test with ad hoc union if (u is (Dog or Cat) pet) {...} // type test with ad hoc union and variable *Note: assigning into an ad hoc union variable may cause boxing of value types* ### Inference Ad hoc unions can be inferred from context when that inference would not otherwise have been possible. The conditional and switch expressions can have result types inferred as ad hoc unions from the constituent expressions. Dog rufus = ...; Cat petunia = ...; Bird polly = ...; // u : (Dog or Cat or Bird) var u = x == 1 ? rufus : x == 2 ? petunia : polly; Likewise, the return type of a lambda expression can also be inferred using an ad hoc union of the return types of the lambda body. T M(F f) => f(2); (Dog or Cat or Bird) pet = M(x => { if (x == 1) return rufus; else if (x == 2) return petunia; return polly; }); *Note: this may cause boxing of value types* ### Implementation Ad hoc unions are implemented through erasure and runtime checks. (A or B) ab = new A(10, "ten"); translates to: object ab = new A(10, "ten"); #### Runtime checks Assignments that are not statically known to be correct require runtime checks. The compiler generates a custom method for each unique ad hoc union used in the module. object value = ...; var ab = (A or B)value; translates to: object value = ...; object ab = (value); object (object? value) => value is A or B ? value : throw ...; *note: Parameters are not checked at entry of a method.* #### Metadata encoding The type of the ad hoc union is encoded in metadata using custom attributes. void M((A or B) x); translates to: void M([AdHocUnion([typeof(A), typeof(B)])] object x); *note: The details of this attribute are not yet specified.* #### Overloading Since all ad hoc unions erase to the same type, true runtime overloading of methods with ad hoc union parameters is not possible. public void Wash((Cat or Dog) pet) { ... } public void Wash((Compact or Sedan) car) { ... } This is still an open area of discussion. ---- ## Custom Unions If you need to declare a union type that cannot be specified as a union class or a union struct, due to specific behaviors that cannot be specified via the union syntax or for other reasons, you may declare you own custom class or struct and have C# recognize it as a custom union type. For example, if your union is specified as a class hierarchy, you can give it the same exhaustiveness behavior as union classes using the `Closed` attribute. It will be functionally the same as a union class. [Closed] public class U { ... } public class A(int x, string y) : U { ... } public class B(int z) : U { ... } If your union is implemented as a struct wrapper with specialized storage rules, you can annotate your struct with the `Union` attribute and as long as you provide API's following the union pattern, your struct will be functionally the same as a union struct. [Union] public struct U { public record struct A(int x, string y); public record struct B(int z); public bool TryGetA(out var A a) { ... } public bool TryGetB(out var B b) { ... } } If your union does not include member types or uses a different API pattern you may provide the API the compiler is expecting via extensions. [Union] public struct U { public bool IsA { get; } public void GetA(out int x, out string y); public bool IsB { get; } public void GetB(out int z); } public implicit extension UX for U { public record struct A(int x, int y); public record struct B(int z); public bool TryGetA(out A a) { ... } public bool TryGetB(out B b) { ... } } *Note: The full union struct API pattern is not yet specified.* *Note: You cannot customize the behavior of an ad hoc union, other than your ability to modify the behaviors of the individual member types.* ---- ## Common Unions ### Option Option is a struct union, similar to the type of the same name or purpose found in other languages. It is used to represent a value that may exist or not. public union struct Option { Some(TValue value); None = default; } usage: Option x = new Some("text"); Option y = None; if (x is Some(var value)) {...} var v = x is Some(var value) ? value : 0; *Note: Option type not fully specified.* ### Result Result is a struct union, similar to the type of the same name or purpose found in other languages. It is used to return either a successful result or an error from a function. public union struct Result { Success(TValue value); Failure(TError error); } usage: Result x = Success("hurray!"); Result y = Failure("boo"); switch (x) { case Success(var value): ...; case Failure(var error): ...; } *Note: Result type not fully specified.* ---- ## Related Proposals These are proposed (or yet to be proposed) features that are presumed to exist by this proposal. ### Closed Hierarchies A `Closed` attribute applied to an abstract base type declares the closed set of sub-types to be all the sub-types in the declaring module. The compiler errors when sub types are declared outside the declaring module. A closed hierarchy is treated as exhaustive by the compiler. If all sub-types are accounted for in a switch expression or statement, no default case is needed. ### Singleton values Types that are singletons (with a static `Singleton` property) can be used as values in non-type contexts by implicitly accessing the property. Instead of: var x = U.C.Singleton; You can write: var x = U.C; ### Nested Member Shorthand Names that are otherwise not bound, can be bound to static members or nested types of the target type. Instead of: Color color = Color.Red; You can write: Color color = Red; Instead of: U u = new U.A(10, "ten"); You can write: U u = new A(10, "ten"); ---- ## Q & A Q: If I can easily declare my own nested hierarchy of records, are union classes needed? A: No, not really. However, it is nice to have a concise syntax that is easy to transition to union structs when necessary with the addition of a single modifier. Q: If union structs can use more kinds of types with little or no allocation why do union classes and ad hoc unions exist? A: While union structs are necessary in some scenarios, they do not work well in others. - They may not cause their own allocations but that does not mean they perform better. Union structs typically have a larger footprint on the stack that is normally copied when assigned, passed or returned. - Union structs do not work well as a solution for anonymous ad hoc unions, since they are not easily interchangeable. For example, a union of statically known generic type parameters is a different type at runtime that the same union with the statically known member types, and an array of one is not interchangeable with an array of the other. - Union structs have problems with type tests, casts and pattern matching when boxed or represented in code statically as a generic type parameter. Q: Why are there no tagged unions? A: Union structs are both tagged unions and type unions. Under the hood, a union struct is a tagged union, even exposing an enum property that is the tag to enable faster compiler generated code, but in the language it is presented as a type union to allow you to interact with it in familiar ways, like type tests, casts and pattern matching. Q: Can the compiler skip constructing a union struct's member type if I immediately assign it to union struct variable? A: Yes, this is an expected optimization. Q: Can the compiler skip copying my union struct state variables into a member type if I deconstruct my union directly to variables? A: Yes, this is an expected optimization. Q: Is the union struct, like the union class, the base type of its member types? A: No, struct types do not allow for actual inheritance. Logically, the union struct acts as the base type such that there are automatic conversions between them, but this illusion falls apart eventually as the relationship extends no further into the type system and runtime. Q: Can I declare ad hoc unions with names? A: Not at this time. If you need a name to help describe or avoid repeating a lengthy union, use a global using alias. Q: Can I have an ad hoc union that does not box value types? A: Ad hoc unions box value types. If you need to avoid boxing use a union struct. Q: Can I have an ad hoc union that includes ref types? A: Ad hoc unions cannot include ref types. If you need to include ref types use a union struct. Q: If variables typed as object are bad, why are ad hoc unions erased to object? A: Using `object` is the solution developers are most likely using today. The ad hoc union feature is an improvement. Represented as an object, the value is in the best form to be understood by the type system at runtime, it is only lacking static type safety at compile time. Using a wrapper type to enforce safety interferes with simple operations like type tests and casts. The Ad Hoc Union feature adds support for understanding ad hoc union types at compile time and generates validation checks to help keep your code correct at runtime. Q: Can I access common properties and methods of the ad hoc union without handling each type case? A: No, you can only access the values of an individual type by first successfully converting the union to that type. Q: Why don't you just use the same kinds of unions that are in F#? A: F# has union types that correspond to both the union classes and union structs definitions in this specification, with the difference of treating the members as types in the language instead of tag states with associated state variables. Ad hoc unions are similar to the kind of type unions found in Typescript. Q: Why do I need the `Option` type if I can do the same thing with nulls and nullable reference types? A: You many not need the `Option` type at all if you are comfortable with using nulls for the same purpose. Some developers prefer an option type as it has stronger enforcement than nullable types do in C#. Q: Will the `Option` and `Result` type also include the monadic behaviors that these types enable in F#? A: No, C# will not include any monadic behaviors in the language for these types at this time. Q: Why do I need the `Result` type when I already have exception handling? A: Many of the use cases for the `Result` type are solved using exception handling in C#. However, you may prefer to avoid exception handling and require the caller to deal with errors explicitly when errors are expected and occur commonly at runtime. Q: Types similar to the `Option` and `Result` types are already available in other 3rd party libraries. Why are you adding them to the runtime? A: Many developers have asked us to include these types in the runtime to standardize them for interoperability between libraries. ================================================ FILE: meetings/working-groups/discriminated-unions/Union implementation challenges.md ================================================ # Union implementation challenges This heavily references https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md. ## Proposal: start with union classes The four kinds of unions outlined in [TypeUnions.md](https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md) do not need to be viewed as alternatives - any or all of them could coexist nicely at the language level. However, some of them have significant challenges at the implementation level, which may need to some hard choices being made at the language level. Some of these challenges are outlined below. The one approach that seems pretty straightforward from an implementation view point is [Union classes](https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md#standard---union-classes). We also estimate that it has high value on its own, addressing a large chunk of scenarios. We therefore propose that we double down on language design and implementation for this direction, with the likely outcome of it arriving in C# before the others. Along the way we can still try to make progress on the others. ## Challenges with union structs [Union structs](https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md#specialized---union-structs) are an alternative to class structs where the union value doesn't need to be allocated, which can be a performance benefit. However, the way class unions represent the different cases is through inheritance, and structs do not have that. The most straightforward way for a union struct to be able to represent all its different cases is for it to have a field for each of them, as well as a `Kind` field indicating which of the cases the current value belongs to. This easily leads to large structs, with a lot of copying when values are passed around, and a lot of wasted memory, since all but one of the case fields is empty. There are ways of compacting the representation, e.g. by overlapping fields, but the more you do, the more time is spent packing and unpacking values. This starts to offset the benefits of avoiding allocation. If compaction uses unsafe techniques, the runtime might get confused and turn off its own optimizations. Runtime work to address this directly would likely be costly. Any representation also needs to deal gracefully with evolution of unions. If a new member is added, and that causes the representation to change materially, will existing compiled code continue to work correctly? The public representation of a union struct needs to be stable against recompilation. ## Challenges with ad-hoc unions [Ad-hoc unions](https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md#ad-hoc---ad-hoc-unions) are anonymous type expressions combining other types. The most obvious way to implement ad-hoc unions is via erasure: the compiler simply replaces occurrences of any ad-hoc union with `object` (or perhaps a common base type of the constituent types), and adds some metadata to public signatures to describe what the types "really" are. This is the same approach we use for `dynamic`, tuple names and nullable annotations. This mostly works! However, it isn't quite safe at runtime. The most confounding problem is when ad-hoc unions are used as type parameters. Imagine this type: ``` c# public class MyCollection { public bool TryAdd(object o) { if (o is T t) { // add t return true; } else return false; } ... } ``` If you use an ad-hoc union as the type argument, it will be erased to `object` and the type check in `TryAdd` will always succeed, violating the type safety of the collection! An alternative is to not erase, and instead have implementation types such as `ValueUnion` etc. However, this has semantic consequences: Now `(string or bool)` will not be the same type as `(bool or string)`! We've investigated runtime approaches to dealing with this, but they are imperfect and very expensive! ================================================ FILE: meetings/working-groups/discriminated-unions/allows.md ================================================ # `allows` syntax for union declarations ## Summary As compared to the current proposal, this proposal retains all aspects, but removes the parentheses in the declaration syntax: ```cs union Pet(Cat, Dog); ``` And replaces them with the `allows` keyword (first introduced in C# 13): ```cs union Pet allows Cat, Dog; ``` All features of the syntax in the current proposal are retained. The following example exercises more of the syntax: ```cs [Description("Example")] public partial union Pet allows Cat, Dog where T : allows ref struct { // members } ``` ## Motivation The current syntax is a solid and workable syntax. While that is true, there are advantages that would be uniquely available with the `allows` syntax: syntactic feel and reception, and a path to coherence with closed hierarchies. On syntactic feel, some LDM and community members have voiced concerns with commas-inside-parentheses for the syntax. Comma-separated items, especially within parens, convey a sense of carrying or passing _all_ of the items rather than just one of them. The syntax most closely resembles primary constructors based on its placement in the type declaration, but it gains nothing by this resemblence. `allows` communicates that this is a constraint, not a set of items which are all present together. Closed hierarchies are also expected to need to list the allowed subtypes at the base declaration. This is so that exhaustiveness can be evaluated for a given expression involving a closed hierarchy without having to first bind all type declarations in the entire compilation. Java uses the keyword `permits` for the same purpose (). Thus, closed hierarchies could also benefit from this same syntax. This syntax even works well alongside primary constructors: ```cs closed record Pet(int Feet) allows Cat, Dog; record Cat(int Feet, ...) : Pet(Feet); record Dog(int Feet, ...) : Pet(Feet); ``` It would feel coherent to have the exact same syntax meaning the exact same thing between unions and closed non-unions. ## Detailed design All elements of the syntax retain the meaning they have under the current proposal, and can appear in all the same combinations. The only change is replacing the tokens `(`...`)` with the token `allows`. ### Base types Thinking to the future, if unions ever supported base types such as implementing interfaces, it might look like this: ```cs union Pet allows Cat, Dog : ISomeInterface where T : allows ref struct { // members } ``` Putting everything on one line shows a possible downside where the `:` may appear strongly related to the last allowed type: ```cs union Pet allows Cat, Dog : ISomeInterface; ``` Where the original design's parentheses would clearly show that the `:` applies to `Pet` and not to `Dog`: ```cs union Pet(Cat, Dog) : ISomeInterface; ``` If we do not expect base types to be a large percentage of the use cases for the syntax, it may be wise not to optimize heavily around them. Both the `allows` syntax and the parenthesized syntax have aspects which may be initially misleading. Perhaps it could hit a sweet spot to optimize the syntax that will be seen more often. ## Specification The grammar is updated to the following: ```antlr union_declaration : attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list? ('allows' type (',' type)*)? type_parameter_constraints_clause* (`{` struct_member_declaration* `}` | ';') ; ``` ================================================ FILE: meetings/working-groups/discriminated-unions/brace-syntax.md ================================================ # Braced Union Syntax ## Summary Nominal type unions are declared using a comma separated list of case members inside braces. Some unions declare fresh cases like an enum does. ```csharp union Gate { Locked, // declaration of Gate.Locked Closed, Open(float Percent) // declaration with a parameter list } ``` While others refer to existing types as cases. ```csharp record Cat(...); record Dog(...); union Pet { Cat, // reference to type Cat Dog } ``` And some do both. ```csharp union Result { T, // reference to type T Error(string Message) // declaration of Result.Error } ``` Notice, there is an ambiguity here since simple named members sometimes refer to existing types and other times refer to declarations of fresh ones. How this ambiguity is resolved is the crux of the issues surrounding syntax for unions. #### Disambiguating The following is an explanation of how the proposal disambiguates without introducing additional syntax. If a union member is a simple name that refers to a type in scope, it is a type reference. ```csharp record Cat(...); record Dog(...); union Pet { Cat, // reference to type Cat Dog } ``` If a union member is a simple name that does not refer to a type in scope, it is a declaration. ```csharp union Gate { Locked, // declaration of Gate.Locked Closed, Open(float Percent); } ``` If a union member is a simple name with a parameter list, it is a declaration. ```csharp union Gate { Locked, Closed, Open(float Percent); // declaration of Gate.Open } ``` If a union member is not a simple name, it is a type reference. ```csharp union SomeUnion { System.Int32, // dotted name can only be reference Int32?, // nullable types can only be references int, // keyword type can only be reference Int32[], // array can only be reference IEnumerable // type arguments can only exist on references } ``` To override a simple name to be a declaration instead of a type reference add an empty parameter list. ```csharp using Locked=Int32; union Gate { Locked(), // declaration of Gate.Locked Closed, Open(float Percent) } ``` #### Other Declarations To include additional kinds of declarations within the braces, use a semicolon at the end of the case member list. ```csharp union Pet { Cat, Dog, Bird; public bool HasPaws => this is Cat or Dog; } ``` *Note: The declaration or type reference list can be thought of as a single declaration in the body of the type. The semicolon is not acting as a separator between the two sections, it is a terminator for the case member declaration as with other declarations. However, it is optional if the case member list is the only declaration.* ## Motivation To have a nominal union syntax that works equally well for declaring unions of existing types and declaring unions of fresh types in a single list. Having a union declaration syntax be similar to enum syntax is ideal, since it leans on the existing enum concept of a set of finite values or states. An enum-like syntax is one that appears as a list of simple names with the one addition that members may include declarations of associated values. This proposal tries to find a compromise between type references and type declarations that maintains this connection. ## Specification This is a specification for an alternate syntax for nominal type unions. It otherwise depends on the existing nominal type union spec and case declarations spec, even though it eliminates the need for case declarations within union declarations. ### Syntax ```antlr union_declaration: attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list? type_parameter_constraints_clause* `{` union_body `}` ; union_body: union_member_list (';' struct_member_declaration*)? ; union_member_list: union_member (',' union_member)* ; union_member: type | identifier parameter_list ; ``` ### Member Declarations Union member declarations are translated to nested records with the same declaration. This is done using the same rules defined in case declarations feature, except members cannot declare type parameters, base types or bodies. ```csharp union Pet { Cat(string Name, string Personality), Dog(string Name, bool Friendly), Bird(string Name, string Species), None } ``` Translates to: ```csharp record struct Pet : IUnion { public Pet(Cat value) { this.Value = value; } public Pet(Dog value) { this.Value = value; } public Pet(Bird value) { this.Value = value; } public object? Value { get; } public record Cat(string Name, string Personality); public record Dog(string Name, bool Friendly); public record Bird(string Name, string Species); public record None; } ``` To describe a more full featured case type, you must declare the case types separately. They may be nested types. ```csharp union Pet { Cat, Dog, Bird, None; public record Cat(string Name, string Personality) : IHavePaws {...}; public record Dog(string Name, bool Friendly) : IHavePaws {...}; public record Bird(string Name, string Species) { ... }; public record None {...}; } ``` ### Type Reference Scope The scope available for members to be type references is the body of the union itself, including any type parameters declared. Unions can refer to their own nested types. ```csharp union Shape { Point, Line, Circle; public class Point {...}; public class Line {...}; public class Circle {...}; } ``` Unions can refer to types that are their own type parameters. ```csharp union Union { T1, T2 }; ``` Or combination thereof. ```csharp union OneOrMore { T, IEnumerable, Two; public record Two(T First, T Second) : IEnumerable {...}; }; ``` A union member can refer to another union member, but that would be an error to include the same member twice. ```csharp union Pet { Cat(...), Cat } ``` ## Concerns * Simple name declarations can accidentally become references if same named types are introduced in scope. > This may not be a big concern, since this kind of change will likely impact use sites and cause build breaks. And it can be avoided be using parentheses. > Possible solution may be to add a warning when a simple name matches a type in scope and the union also has member declarations. ```csharp record Cat(...); union Pet { Cat, // warning - Identifier refers to external type in Dog(...) // union with member declarations. } ``` * Simple names type references can unexpectedly become declarations if/when the type is removed or no longer in scope. > No solution yet for this one. * There is no easy grow up story for declared members. > Having to declare the member fully as a nested type is not that much hardship when deviating from the common case. ## Alternatives The inability to be certain about the nature of simple names in the primary proposal leads to a desire to explore alternatives that are easier to digest. ### Simple Names are Type References In this alternative, all simple names are interpreted as type references. The only way to get one to mean a member declaration is to include an empty parameter list. ```csharp union Pet { Cat, // reference to existing type or error Dog } union Gate { Locked(), // declaration of Gate.Locked Closed(), Open(float Percent) } ``` #### Variation A variation of this is to require additional syntax on all declarations. ```csharp union Pet { Cat, // reference to existing type or error Dog } union Gate { case Locked, // declaration of Gate.Locked case Closed, case Open(float Percent) } ``` This eliminates the need to put parentheses at the end of member declarations, but it does require non-enum-like syntax for the main enum-like scenario. It also may appear awkward when mixed. ```csharp record Cat(...); union Pet { Cat, case Dog(...); } ``` For example, it's non-intuitive why the declaration is the only one denoted as a case, when they are all cases. Possibly a different keyword would work better. ```csharp union Pet { Cat, declare Dog(...); } ``` Downsides: * It is no longer enum-like. You cannot simply start with an enum and upgrade it to a union without adding parentheses to all members. ### Simple Names Are Declarations In this alternative, all simple names are considered to be declarations. Instead, type references require additional syntax. This makes union syntax be enum-like by default for declarations. ```csharp union Gate { Locked, // declaration of Gate.Locked Closed, Open(float Percent) } ``` Additional punctuation indicates it is a type reference. ```csharp union Pet { ~Cat, // reference to existing type or error ~Dog } ``` Other single character punctuation options: ```csharp ^Cat, `Cat, // finally the back-tick :Cat, =Cat, // maybe.. >Cat, !Cat, ?Cat, $Cat, &Cat, *Cat, /Cat, +Cat, -Cat, [Cat] (Cat) ``` #### Variation: Keywords A keyword indicates the name is a type reference. ```csharp union Pet { type Cat, // reference to existing type or error type Dog } ``` *Note: An alternative to this alternative is to only require the extra syntax for simple names (identifiers). Unfortunately, simple name type references are the common case.* Downsides: * Type references end up requiring additional syntax making the union declaration feel biased toward being used with declarations when in fact the underlying union concept is the opposite. #### Variation: Conditional Simple names are declarations if other declarations also exist in the same union declaration, otherwise they are type references. This alternative determines the meaning of simple names depending on the meaning of other members. If any other member has a parameter list then simple names are member declarations too. ```csharp union Pet { Cat, // type reference since no members have parameter lists Dog } union Gate { Locked, // declaration of Gate.Locked Closed, Open(float Percent) } ``` This alternative works well since the common case is that unions have case members that are either all references or all declarations, and those with declarations will have at least one with parameters. However, there would be no way to include a type that can only be specified as a simple name when there is also a declaration. ```csharp union Result { T, // oops, this is a now a declaration Error(string message) } ``` A variant of this variation could, of course, introduce an optional syntax to specify references, but would have all the same issues as the other alternate syntaxes. ```csharp union Result { ~T, Error(string message) } ``` Downsides: * The condition makes it cumbersome to understand the meaning of simple names. * It still needs additional syntax in some cases. ### Separate Indicator In this alternative some part of syntax outside the braces indicates the meaning of simple named members inside the braces. In this example, the keyword `type` is added to the declaration to indicate that simple names inside the braces mean references to existing types, not declarations. ```csharp type union Pet { Cat, // reference to existing type or error Dog, Bird } ``` Without the additional keyword, the default meaning is a fresh case member. ```csharp union Gate { Locked, // declaration of Gate.Locked Closed, Open(float Percent) } ``` To have a mix of both simple named type references and declarations you must use empty parameter lists to get have a simple name declaration. ```csharp record Unknown; type union Gate { Unknown, // reference to external type Locked(), // reference to Gate.Locked Closed(), Open(float Percent) } ``` Downsides: * There now seems to be two different almost identical syntaxes and the reader must be aware which is in use to understand the meaning of a simple name. ### Type References Outside In this alternative all type references are specified somewhere outside the braces leaving the body to only specify declarations. This allows a declaration-only union to remain enum-like and takes advantage of the intuition that braces inside declarations contain other declarations. The example includes a list of references to existing types inside parentheses, similar to the current plan-of-record proposal, except also including the enum-like syntax for fresh case member declarations. ```csharp union Pet (Cat, Dog, Bird); // references to existing types union Gate { Locked, // declaration of Gate.Locked Closed, Open(float Percent) } ``` #### Variation: Brackets This alternative changes the parentheses to square brackets to avoid similarity with primary constructors. ```csharp union Pet [Cat, Dog, Bird]; // clearly not a primary constructor or declarations union Gate { Locked, // declaration of Gate.Locked Closed, Open(float Percent) } ``` This similarity of the square brackets to collection expressions helps reinforce that the members listed are not declarations. #### Variation: Keyword list This alternative is similar to the 'allows' keyword proposal, but it retains the enum-like syntax for declarations inside the braces. A keyword initiated clause with list of type expressions appears somewhere in the syntax after the name and possible type parameters. ```csharp union Pet allows Cat, Dog, Bird; union Gate { Locked, Closed, Open(float Percent) } ``` Other example keywords: ```csharp union Pet includes Cat, Dog, Bird; union Pet contains Cat, Dog, Bird; union Pet has Cat, Dog, Bird; union Pet with Cat, Dog, Bird; union Pet of Cat, Dog, Bird; ``` Downsides: * The keyword implies meaning that may not correctly describe what is going on. * It makes the case members appear to be more of an addendum than the core of the declaration. ## Extra Credit ### Declarations Outside Braces This alternative moves away from using braces as a means of listing case types. It allows both case member declarations and type references to be included in a single list but leave the brace syntax for other kinds of declarations. ```csharp union Pet(Cat, Dog, Bird); // type references union Gate(Locked(), Closed(), Open(float Percent)); // declarations union Result(T, Error(string Message)); // references and declarations. ``` It has all the issues as the braces syntax, requiring the same kinds of solutions, but does not try to be enum-like. #### Variation: Leaving the Nest A variation of this proposal allows for declarations both inside and outside of the braces. Declarations inside the braces become nested, while declarations outside the braces become types in the same declaration scope as the union. Type references can appear in both, but simple names are considered type references outside the braces and declarations inside the braces. The is no syntax that inverts the meaning of simple names inside the braces. ```csharp namespace NS; union Gate ( Locked(), // declaration of NS.Locked Closed(), Open(float Percent) ); union Gate { Locked, // declaration of Gate.Locked Closed, Open(float Percent) } union Result ( T, // reference to type T Error(string Message) // declaration of NS.Error } union Result(T) // reference to type T { Error(string Message) // declaration of Result.Error } ``` ================================================ FILE: meetings/working-groups/discriminated-unions/enum-like-unions.md ================================================ # Enum-like unions ## Summary **Enum-like unions** are "enum-like" in the same sense that field-like events are "field-like": They are introduced by the same keyword (`union`/`event`), and to the consumer they are the same kind of thing (unions/events), but their body uses an alternative, terser, syntax that is similar to another kind of declaration (enums/fields): ```csharp public union Gate { Locked, Closed, Open(float percent) } ``` ## Motivation For straightforward "discriminated union" scenarios, it is desirable to have a very terse syntax for declaring fresh case types, and it is helpful to lean into enum syntax (curly braces, simple names, commas) to manifest the analogy. Both these claims are borne out by other programming languages. However, to a consumer such unions aren't observably different from ones declared with type references, just like field-like events aren't observably different from other events. Sharing the `union` keyword makes the shared semantics clear. At the same time, this lets us allow the same enum-like bodies in closed hierarchies if we want, creating symmetry between the two kinds of closed type declarations. Finally, while this proposal separates "struct-like" and "enum-like" declarations, it leaves open the possibility of unifying them. ## Detailed design ### Syntax ```antlr union_declaration : attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list? union_declarator ; union_declarator : struct_like_union_declarator | enum_like_union_declarator ; struct_like_union_declarator : '(' type (',' type)* ')' type_parameter_constraints_clause* (`{` struct_member_declaration* `}` | ';') ; enum_like_union_declarator : type_parameter_constraints_clause* `{` enum_like_union_member_list `}` ; enum_like_union_member_list : enum_like_union_member (',' enum_like_union_member)* (`,`)? ; enum_like_union_member : identifier (`(` parameter_list? `)`)? ; ``` Note that the `struct_like_union_declarator` shown here just reflects the current plan of record, but could change as part of other decisions. Its exact shape is not part of this proposal. ### Semantics Enum-like unions are translated into struct-like unions, where enum-like union members are translated into nested record declarations (with primary constructor parameter lists if they contain parameter lists `(...)`) and added to the resulting unions case type list. For example: ```csharp public union Gate { Locked, Closed, Open(float percent) } ``` Translates to: ```csharp public union Gate(Gate.Locked, Gate.Closed, Gate.Open) { public record class Locked; public record class Closed; public record class Open(float percent); } ``` ### Examples ```csharp public union Pet { Cat(string Name, string Personality), Dog(string Name, bool Friendly), Bird(string Name, string Species), None } public union Option { None, Some(T value), } ``` ## Drawbacks - Is it too subtle that the presence or absence of a list of case type references determines whether the `{...}` body is enum-like or struct-like? - Does the `enum` keyword need to be present to stress the analogy to enums? - Like enums, enum-like unions cannot declare struct members such as function members or nested types. Their body is reserved for case members. - Types declared as enum-like union members cannot declare their own bodies. This represents quite a cliff, as doing so for even one member requires the whole union to be rewritten as a struct-like union. ## Alternatives - Use other keywords or additional keywords to further differentiate an enum-like union declaration from a struct-like one. ## Additions - Allow closed hierarchies to also have an enum-like body. That way, the author of a "discriminated union" can freely choose whether to implement it as a union or a closed class without paying a syntactic penalty either way: ```csharp public union Gate { Locked, Closed, Open(float percent) } // or public closed class Gate { Locked, Closed, Open(float percent) } ``` - Allow the `enum_like_union_member_list` to be followed by a `;` and a list of `struct_member_declaration`s so that enum-like unions also can have e.g. function members. ```csharp union Pet { Cat, Dog, Bird; public bool HasPaws => this is Cat or Dog; } ``` This could also be allowed in actual enum declarations, maintaining the analogy. ```csharp enum TrafficLight { Green, Yellow, Red, public bool Stop => this is not Green; } ``` Additionally, if we continue to embrace the [Case declarations](https://github.com/dotnet/csharplang/blob/main/proposals/case-declarations.md) proposal, this could mitigate the cliff occurring when you want to add a member body to a case type: Simply put that case type as a case declaration: ```csharp public closed record GateState { Closed, Locked; // Simple cases case Open(float Percent) { public static Open Fully => new Open(100); } } ``` - Fully unify struct-like and enum-like declarations by allowing both a list of case type references and a list of enum-like union members in the same union declaration. This would be a superset of the proposal, but would go against the current decision to keep the two kinds of union declarations separate. It is unclear how many scenarios would benefit from this, but it is also unclear who would benefit from forbidding it. ```csharp public record None; public union Option(None) { Some(T value) } ``` ## Open questions - Does this coexist with [Case declarations](https://github.com/dotnet/csharplang/blob/main/proposals/case-declarations.md)? ================================================ FILE: meetings/working-groups/discriminated-unions/extended-enums.md ================================================ # Discriminated Unions and Enhanced Enums for C# This proposal introduces enhanced enums as an elegant way to build discriminated unions in C#. Building on [type unions](https://raw.githubusercontent.com/dotnet/csharplang/refs/heads/main/proposals/unions.md), enhanced enums provide familiar, concise syntax for algebraic sum types where cases are known at declaration time. It consolidates design feedback from many years of repository discussions, especially [#113](https://github.com/dotnet/csharplang/issues/113) and related issues.
Key discussion threads... - [#75](https://github.com/dotnet/csharplang/issues/75) - Early comprehensive discussion exploring DU syntax options including enum class - [#2962](https://github.com/dotnet/csharplang/discussions/2962) - Major discussion on Andy Gocke's DU proposal, debating enum class vs enum struct - [#7016](https://github.com/dotnet/csharplang/issues/7016) - Fast, efficient unions proposal focusing on struct-based implementations - [#3760](https://github.com/dotnet/csharplang/discussions/3760) - Community "shopping list" of desired discriminated union features - [#7544](https://github.com/dotnet/csharplang/issues/7544) - Simple encoding of unions exploring type unions vs tagged unions - [#8804](https://github.com/dotnet/csharplang/discussions/8804) - String-based enums for cloud services with extensibility needs - [#1860](https://github.com/dotnet/csharplang/issues/1860) - Long-running request for string enum support citing TypeScript/Java - [#9010](https://github.com/dotnet/csharplang/discussions/9010) - "Closed" enum types that guarantee exhaustiveness - [#6927](https://github.com/dotnet/csharplang/discussions/6927) - Constant enums discussion around strict value enforcement - [#7854](https://github.com/dotnet/csharplang/issues/7854) - Exhaustiveness checking for ADT patterns using private constructors - [#8942](https://github.com/dotnet/csharplang/discussions/8942) - Track subtype exhaustiveness for closed hierarchies - [#8926](https://github.com/dotnet/csharplang/discussions/8926) - Extensive discussion on Option as canonical DU use case - [#7010](https://github.com/dotnet/csharplang/discussions/7010) - Union types discussion heavily featuring Option and Result - [#274](https://github.com/dotnet/csharplang/discussions/274) - Java-style class-level enums with methods and constructors - [#8987](https://github.com/dotnet/csharplang/discussions/8987) - Champion "permit methods in enum declarations" - [#5937](https://github.com/dotnet/csharplang/discussions/5937) - Smart Enums In C# Like Java" (extra state!) - [#782](https://github.com/dotnet/csharplang/discussions/782) Sealed enums (completeness checking in switch statements) - [#2669](https://github.com/dotnet/csharplang/discussions/2669) Feature request: Partial enums
## 1. Overview C# gains a layered approach to union types: type unions provide the foundation for combining types, while enhanced enums offer elegant syntax for discriminated unions where you define cases and their union together. ```csharp // Type unions - combine existing types union Result(string, ValidationError, NetworkException); // Shape enums - discriminated unions with integrated case definitions enum struct PaymentResult // or `enum class` { Success(string transactionId), Declined(string reason), PartialRefund(string originalId, decimal amount) } ``` ## 2. Motivation and Design Philosophy ### From Type Unions to Discriminated Unions Type unions solve the fundamental problem of representing "one of several types". A particularly important pattern is discriminated unions, where: - Cases are defined together as a logical unit - Each case may carry different data - The set of cases is typically closed and known at design time Shape enums provide natural syntax for this pattern—expressing the entire discriminated union in a single declaration rather than manually defining and combining types. ### Limitations of Current Enums Today's C# enums have significant limitations: 1. **No associated data**: Cases are merely integral values 2. **Not truly exhaustive**: Any integer can be cast to an enum type 3. **Limited to integers**: Cannot use strings or doubles Enhanced enums address all these limitations while preserving conceptual simplicity. ### Building on Familiar Concepts By extending the existing `enum` keyword, enhanced enums provide a grow-up story. Simple enums remain simple, while advanced scenarios become possible without abandoning familiar patterns. ## 3. Type Unions (Foundation) Type unions are fully specified in the [unions proposal](https://raw.githubusercontent.com/dotnet/csharplang/refs/heads/main/proposals/unions.md#summary). They provide: - Implicit conversions from case types to the union type - Pattern matching that unwraps union contents - Exhaustiveness checking in switch expressions - Enhanced nullability tracking - Flexible storage strategies (boxing or non-boxing) ## 4. Enhanced Enums ### Design Principles - **Progressive enhancement**: Simple enums stay simple; complexity is opt-in - **Data carrying**: Each case can carry its own constituent data - **Familiar syntax**: Builds on existing enum and record concepts - **Union foundation**: Shape enums are discriminated unions ### Syntax Extensions Enhanced enums extend traditional enum syntax in three orthogonal ways: #### Extended Base Types Support any constant-bearing type: ```csharp enum Traditional : int { A = 1, B = 2 } enum Priority : string { Low = "low", Medium = "medium", High = "high" } enum TranscendentalConstants : double { Pi = 3.14159, E = 2.71828 } ``` #### Shape Declarations Create a shape enum (discriminated union) by: - Adding `class` or `struct` after `enum` ```csharp enum class Result { Success, Failure } // shape enum via 'class' keyword enum struct Result { Success, Failure } // shape enum via 'struct' keyword ``` #### Data-Carrying Cases ```csharp enum class Result // or enum struct { Success(string id), Failure(int code, string message) } ``` Each case with a parameter list generates a nested record type. `enum class` generates `sealed record class` types; `enum struct` generates `readonly record struct` types. While these are the generated types, the union implementation may optimize internal storage. TODO: Should the structs be readonly? Seems like that goes against normal structs (including `record struct`). Seems like that could be opt in with `readonly enum struct X`. #### Combination Rules - **Constant enums**: Can use extended base types but NOT have parameter lists - **Shape enums**: Can have parameter lists but NOT specify a base type - **Mixing cases**: Cannot mix constant values and parameterized cases ```csharp // ✓ Valid - constant enum with string base enum Status : string { Active = "A", Inactive = "I" } // ✓ Valid - shape enum with data enum class Result { Ok(int value), Error(string msg) } // ✗ Invalid - cannot mix constants and shapes enum class Bad { A = 1, B(string x) } // ✗ Invalid - shape enums cannot have base types enum struct Bad : int { A, B } // ✗ Invalid - Constant enums cannot have parameter lists enum Bad { A(), B() } ``` For the complete formal grammar, see [Appendix A: Grammar Changes](#appendix-a-grammar-changes). ### Constant Value Enums Enhanced constant enums support any primitive type with compile-time constants: ```csharp enum Priority : string { Low = "low", Medium = "medium", High = "high" } ``` These compile to `System.Enum` subclasses with the appropriate `value__` backing field. Non-integral constant enums require explicit values for each member. ### Shape Enums: Discriminated Unions Made Elegant Shape enums combine type unions with convenient integrated syntax: ```csharp enum class FileOperation // or enum struct { Open(string path), Close, Read(byte[] buffer, int offset, int count), Write(byte[] buffer) } ``` #### Reference Type and Value Type ```csharp enum class WebResponse { Success(string content), Error(int statusCode, string message), Timeout } enum struct Option { None, Some(T value) } ``` **`enum class`** creates discriminated unions with reference type cases: - Cheap to pass around (pointer-sized) - No struct tearing risk - Natural null representation **`enum struct`** creates discriminated unions with optimized value-type storage: - No heap allocation - Better cache locality - Reduced GC pressure #### Members and Methods Enums can contain members just like unions: ```csharp enum class Result { Success(T value), Error(string message); public bool IsSuccess => this switch { Success(_) => true, _ => false }; public T GetValueOrDefault(T defaultValue) => this switch { Success(var value) => value, _ => defaultValue }; } ``` Members are restricted to: - Instance methods, properties, indexers and events (no additional state) - Static members - Nested types ## 5. Translation Strategy Shape enums translate directly to unions—generating case types as nested types and creating a union that combines them. ### `enum class` Translation ```csharp enum class Result { Success(string value), Failure(int code) } // Translates to: public union Result(Success, Failure) { public sealed record class Success(string value); public sealed record class Failure(int code); } ``` Singleton cases generate types with shared instances: ```csharp enum class State { Ready, Processing, Complete } // Translates to: public union State(Ready, Processing, Complete) { public sealed class Ready { public static readonly Ready Instance = new(); private Ready() { } } // Similar for Processing and Complete } ``` ### `enum struct` Translation ```csharp enum struct Option { None, Some(T value) } // Conceptually translates to: public struct Option : IUnion { public readonly struct None { } public readonly record struct Some(T value); // Optimized layout: discriminator + space for largest case private byte _discriminant; private T _value; object? IUnion.Value => _discriminant switch { 1 => new None(), 2 => new Some(_value), _ => null }; // Non-boxing access pattern public bool TryGetValue(out None value) { value = default; return _discriminant == 1; } public bool TryGetValue(out Some value) { if (_discriminant == 2) { value = new Some(_value); return true; } value = default!; return false; } // Constructors and factories public Option(None _) => _discriminant = 1; public Option(Some some) => (_discriminant, _value) = (2, some.value); public static Option None => new Option(new None()); public static Option Some(T value) => new Option(new Some(value)); } ``` ## 6. Pattern Matching and Behaviors ### Unified Pattern Matching Shape enums inherit all union pattern matching behavior: ```csharp var message = operation switch { Open(var path) => $"Opening {path}", Close => "Closing file", Read(_, var offset, var count) => $"Reading {count} bytes at {offset}", Write(var buffer) => $"Writing {buffer.Length} bytes" }; ``` ### Exhaustiveness The compiler tracks all declared cases. Both constant and shape enums can be open or closed (see [Closed Enums proposal](https://github.com/dotnet/csharplang/blob/main/proposals/closed-enums.md)). Open enums can be used to signal that the enum author may add new cases in future versions—consumers must handle unknown cases defensively (e.g., with a default branch). Closed enums signal that there is no need to handle unknown cases, such as when the case set is complete and will never change—the compiler ensures exhaustive matching without requiring a default case. For constant enums, "open" also means values outside the declared set can be safely freely cast to the enum type. ```csharp closed enum Status { Active, Pending(DateTime since), Inactive } // Compiler knows this is exhaustive - no default needed var description = status switch { Active => "Currently active", Pending(var date) => $"Pending since {date}", Inactive => "Not active" }; enum Priority : string { Low = "low", Medium = "medium", High = "high" } // Default case is needed, other string values may be converted to Priority, or new cases may be added in the future: var value = priority switch { Low => -1, Medium => 0, High => 1, _ => /* fallback to low priority */ -1, } ``` ### All Union Behaviors Shape enums automatically get: - Implicit conversions from case values - Nullability tracking - Well-formedness guarantees ## 7. Examples and Use Cases ### Migrating Traditional Enums ```csharp // Traditional enum enum OrderStatus { Pending = 1, Processing = 2, Shipped = 3, Delivered = 4 } // Enhanced with data enum struct OrderStatus { Pending, Processing(DateTime startedAt), Shipped(string trackingNumber), Delivered(DateTime deliveredAt); public bool IsComplete => this is Delivered; } ``` ### Result and Option Types ```csharp enum class Result { Ok(T value), Error(E error); public Result Map(Func mapper) => this switch { Ok(var value) => Result.Ok(mapper(value)), Error(var err) => Result.Error(err) }; } enum struct Option { None, Some(T value); public T GetOrDefault(T defaultValue) => this switch { Some(var value) => value, None => defaultValue }; } ``` ### State Machines ```csharp enum class ConnectionState { Disconnected, Connecting(DateTime attemptStarted, int attemptNumber), Connected(IPEndPoint endpoint, DateTime connectedAt), Reconnecting(IPEndPoint lastEndpoint, int retryCount, DateTime nextRetryAt), Failed(string reason, Exception exception); public ConnectionState HandleTimeout() => this switch { Connecting(var started, var attempts) when attempts < 3 => ConnectionState.Reconnecting(null, attempts + 1, DateTime.Now.AddSeconds(Math.Pow(2, attempts))), Connecting(_, _) => ConnectionState.Failed("Connection timeout", new TimeoutException()), Connected(var endpoint, _) => ConnectionState.Reconnecting(endpoint, 1, DateTime.Now.AddSeconds(1)), _ => this }; } ``` ## 8. Design Decisions and Trade-offs ### Why Extend `enum` - **Familiarity**: Developers already understand enums conceptually - **Progressive disclosure**: Simple cases remain simple - **Cognitive load**: One concept instead of two - **Migration path**: Existing enums can be enhanced incrementally ### Union Foundation Shape enums are discriminated unions expressed through enum syntax. By building on union machinery: - All union optimizations automatically benefit shape enums - No risk of semantic divergence between features - Simple mental model: shape enums generate types and combine them with a union - Future union enhancements immediately apply ### Storage Strategy Trade-offs The distinction between `enum class` and `enum struct` allows developers to choose the right trade-off, similar to choosing between `record class` and `record struct`. ## 9. Performance Characteristics ### Memory Layout **`enum class`**: - Union contains single reference (8 bytes on 64-bit) - Case instances allocated on heap - Singleton pattern for parameter-less cases **`enum struct`**: - Size equals discriminator plus space for largest case - Inline storage, no heap allocation - Optimized layout per union's non-boxing pattern ### Allocation Patterns ```csharp // Allocation per construction enum class Result { Ok(int value), Error(string message) } var r1 = Result.Ok(42); // Heap allocation // No allocation enum struct Result { Ok(int value), Error(string message) } var r2 = Result.Ok(42); // Stack only ``` ### Optimization Opportunities Shape enums benefit from all union optimizations: - Singleton cases to shared instances - Small structs fitting in registers - Pattern matching via optimized paths - Exhaustive switches avoiding default branches ## 10. Open Questions 1. **Nested type accessibility**: Should users reference generated case types directly? 2. **Partial support**: Should enhanced enums support `partial` for source generators? 3. **Default values**: What should `default(EnumType)` produce for shape enums? 4. **Serialization**: How should enhanced enums interact with System.Text.Json? 5. **Additional state**: Should shape enums allow instance fields outside case data? 6. **Custom constructors**: Should enums allow custom constructors that delegate to cases? 7. **Construction syntax**: `Result.Ok(42)` or `new Result.Ok(42)` or both? 8. **Generic cases**: Should cases support independent generic parameters? 9. **Interface implementation**: Should enhanced enums automatically implement `IEquatable`? 10. **Exact lowering**: Should the spec define exact names and shapes of generated types? ## Appendix A: Grammar Changes ```antlr enum_declaration : attributes? enum_modifier* 'enum' ('class' | 'struct')? identifier enum_base? enum_body ';'? ; enum_base : ':' enum_underlying_type ; enum_underlying_type : simple_type // all integral types, fp-types, decimal, bool and char | 'string' | type_name // Must resolve to one of the above ; enum_body : '{' enum_member_declarations? '}' | '{' enum_member_declarations ';' class_member_declarations '}' ; enum_member_declarations : enum_member_declaration (',' enum_member_declaration)* ; enum_member_declaration : attributes? identifier enum_member_initializer? ; enum_member_initializer : '=' constant_expression | parameter_list ; ``` ================================================ FILE: meetings/working-groups/discriminated-unions/original-nominal-type-unions.md ================================================ # Nominal Type Unions for C# Champion issue: https://github.com/dotnet/csharplang/issues/9411 ## Summary [summary]: #summary A type union is a type that can represent a single value from a closed and disjoint set of types declared separately. These types, known as *case types* in this proposal, are not required to be declared in a hierarchy or share a common base (other than object). ```csharp record Cat(...); // the case types record Dog(...); record Bird(...); union Pet(Cat, Dog, Bird); // the type union ``` Values of a case type can be assigned directly to a variable of a type-union type. However, since type unions can represent values of unrelated types, having a variable of one is similar to having a variable typed as object. Interacting with a type union instance typically first involves testing and accessing its value in the form of one of its case types. ```csharp Pet pet = new Dog(...); ... if (pet is Dog d) {...} ``` This proposal introduces a first class nominal type union to C# that is fundamentally a struct wrapper around a value. It is declared and used like any other type, yet, it is also treated specially in some situations to help create an illusion that it is the value and not the wrapper. ```csharp union Pet(Cat, Dog, Bird); ``` becomes ```csharp readonly struct Pet { public Pet(Cat value) {...} public Pet(Dog value) {...} public Pet(Bird value) {...} public object Value => ...; } ``` To treat it like its value, certain operations like casting and pattern matching are translated by the compiler to API calls on the type union instance. ```csharp if (pet is Dog dog) {...} ``` becomes ```csharp if (pet is { Value: Dog dog }) {...} ``` The proposal is broken down into a core set of features needed to make nominal type union's viable and an additional assortment of optional features that may improve performance or capability and can be conditionally implemented over time. ## Motivation C# has been adopting functional programming concepts for many years as the language continues to evolve to support popular paradigms as they become mainstream, like the monadic behaviors of nullable types and sequence operators in LINQ and more recently tuples and records that focus on data over behavior and help shift development to a more immutable style. Writing software using functional paradigms often utilizes coding practices that are opposite or inverted from object-oriented ones. Frequently data is not hidden in the way one might do with objects, where ideally only the contract of behavior is exposed via methods and polymorphism is handled by virtual dispatch. Instead, a fixed set of types are used to represent a well known and finite data model with elements that are themselves often polymorphic leading to the need to identify and handle cases explicitly in the code. You see this happening in the real world in places where the veil of abstraction is lifted, such as when protocols and data models are defined for communicating and exchanging information between boundaries (like machines, processes and libraries) and beneath the veil where the stuff of algorithmic minutia takes place. In these situations behavior is not the contract, the data is. When you are operating on a data model in this way it becomes necessary to be confident that you are handling all cases when a property or collection can optionally contain more than one kind of data. Having the language help you enforce and know when this is true is extremely valuable. Both are usually solved in programming languages using the concepts of *type unions* and *discriminated unions*. Type unions allow you to specify a variable as being able to hold a value from a set of otherwise unrelated types instead of just one type. Values of other types are not allowed and using the variable typically requires testing and converting it to one of its known case types. Likewise, discriminated unions allow you to have a variable that holds different kinds of values depending on its given named state and then depending on which state the variable is in you can get access to just those associated values. They are kind of like type unions without the types. Yet, in a language like C#, these named states with their values are better expressed as records allowing discriminated unions to be built out of type unions. The only way currently to have a variable hold values of more than one type in C# is to either give the variable the object type or to use a base type that all the case types share. Yet, using object does not allow the language to restrict what the variable contains and a base type, while better, still does not constrain it because the type itself does not describe the set of possible sub types and additional sub types can easily be declared elsewhere outside the scope of the original design. C# needs type unions to enable a better style of programming when polymorphic data models are being operated on. It solves the problem of guaranteeing that a variable can hold a value from more than one type, but also from only a specific set of types, and helps inform you when all cases have been handled. ### Everyday Unions Type unions can also be used for less grand purposes. While you might not have any published protocols or data models to operate on, simply being able to return a value that can be one of many types and have that documented strongly in the method's signature is a huge benefit. Both consumers of a method like this and the compiler will know right away what types need to be handled in the result. ```csharp public union TestResult(double, string); // Possible values are obvious public TestResult RunTest(...) {...}; ``` Likewise, a parameter using a type union can constrain the values passed in, that might otherwise require abundant overloading, or be used as a settable property that cannot even have an overload. ```csharp // otherwise would need N overloads to constrain input public void RegisterVisit(Pet patient, DateTime time, string reason, string vet); // cannot have overloads for properties public Pet Patient { get; set; } ``` Having a way to express this makes code easier to read and prove correct because there are less ways to provide invalid input and more ways to ensure that all cases are understood. ### Beyond Class Hierarchies A shallow class hierarchy can sometimes be used as a substitute for a type union if there is a way for both the compiler and you to know the full set of sub-types being used as cases and you have the freedom to declare all these cases for just this purpose. A separate proposal known as *Closed Class Hierarchies* attempts to solve this. Yet, even when this is possible, nominal type unions are often a better solution. When to use *nominal type unions* over *class hierarchies*. - **To avoid allocations of the case types.** *This is important when the cases may be constructed and consumed frequently, like with the Option and Result types.* - **Some or all of the case types are declared elsewhere.** *For example, you want to constrain a field to only double and string values. You don't get to redeclare these types. You could declare a hierarchy with special sub-types that wrap these values but that would no longer be a type union of the desired types and those wrapper types would require allocations too.* - **The case types have other uses outside the union.** *While it might make sense to declare Animal and Vehicle in the same hierarchy of ThingsInAutoAccident, it may be awkward to have the Animal type as part of that hierarchy when also using it in your MeatsOnTheMenu model.* - **The need to handle only a subset of cases.** *You have a hierarchy of animals but need to limit a variable to just pets. You might be able to introduce Pet as an additional abstract class wedged between Animal and the pet specific case types but sometimes a perfect taxonomy cannot be defined when multiple subsets are needed.* - **The need for multiple unions of similar cases.** *There is no multiple inheritance in C# and therefore no sharing of the same case types in different hierarchies. You would need to create multiple identical classes with different base classes instead with no easy way to convert between them.* ## Principles Some foundational principles that guided this proposal. - **A nominal type union represents a single value.** *It holds a single value that can be one of its case types. Its not meant to have other instance fields or properties.* - **A nominal type union is a distinct type from other type unions even when they share the same cases.** *They are not interchangeable or structural. However, conversion between similar type-union types should be possible.* - **A nominal type union instance behaves as if it were its value in some situations.** *Some operations applied to the union instance are instead applied to the value of the union, as far as its meaning is well understood and it is practical to do so.* - **A nominal type union is its own type.** *The type is not erased. An instance of one might behave as if it were the value it wraps in some situations, but it is also its own instance distinct from the value everywhere else.* - **A nominal type union is always meant to have a value of one of its case types.** *The reality, however, is that a default or uninitialized state does exist that we pretend does not, leading to the need to define what happens when this occurs.* - **A nominal type union's value is never meant to be null.** *The reality, however, is that null values can still sneak in which leads us to need to define what happens when this occurs.* - **A nominal type union's storage model is opaque.** *The field(s) storing the value are hidden behind constructors and properties/methods allowing the storage model to vary depending on the cases and potentially improve over time.* - **A nominal type union is exactly what you declare it to be.** *A type union that includes cases that are other type unions (not recommended but could happen due to type argument cases) is not flattened into a union of the leaf cases. The values held for these cases are type union instances not the values they indirectly contain.* ## Declaration A nominal type union is declared using the `union` keyword and minimally a name and a list of case types that are each declared elsewhere. ```csharp union Pet(Cat, Dog, Bird); ``` Though rare, some nominal type unions may declare type parameters: ```csharp union Option(Some, None); ``` Or add additional members: ```csharp union Option(Some, None) { public bool HasValue => this is Some; } ``` They may even declare their case types as nested declarations: ```csharp public union Option(Some, None) { public record struct Some(TValue Value); public record struct None(); } ``` Others may choose to declare and implement interfaces for special circumstances: ```csharp public partial union Pet(Cat, Dog, Bird) : ICustomSerializable { void ICustomSerializable.SerializeTo(Stream stream) => ...; } ``` *These interfaces are elements of the union type and not related to the case types or the value of the union. The might be needed for interop with libraries or frameworks that require them.* ### Grammar ``` type_union_declaration : attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list? case_type_list? struct_interfaces? type_parameter_constraints_clause* struct_body ; case_type_list : '(' case_type (',' case_type)* ')' case_type : attributes? case_modifiers? type case_modifiers : ??? ``` ### Modifiers A nominal type union may have zero or more modifiers. However, only certain modifiers are allowed. - Any accessibility modifier: public, private, internal, etc - partial - file ### Type Parameters The nominal type union can have zero or more type parameters that can be used in member declarations, interface declarations and case type specifications. ```csharp union OneOrList
(A, IReadOnlyList); ``` - Note: declaring case types that are just type parameters hides knowledge of the true types used at runtime from the compiler at compile time making it possible to end up with type unions at runtime that would typically be avoided like unions containing other type unions or unions with duplicate case types. It also reduces the ability of the compiler to optimize the storage layout of the data. ### Case Types A nominal type union specifies a list of case types. These types determine the closed set of types that a value contained by the type union can have. They can be any type except for the ones that are excluded by producing errors. The following conditions produce an error by the compiler: - A case type is a pointer or ref type. *These types cannot be boxed to object and accessed via Value property.* - A case type is the containing union's type. *Infinite storage recursion?* The following conditions produce a warning from the compiler. If found the author has likely made a mistake. The warning can be overridden with pragmas. - A case type is a type-union type. *The author likely intended to have the individual cases of this type listed instead.* - A case type is specified more than once. *This is a redundant case that will never be used in practice.* - A case type subsumes another case type. *The author probably does not realize the subsumption and redundancy of additional cases.* - A case type is the object type. *The object type not only subsumes all other cases, it does not constrain the values the union may have in any way, defeating the purpose of the union.* - A case type is a nullable struct or reference type. *Type Unions only have non-null values.* ### Base Type A nominal type union cannot declare a base type, only interfaces. If the intention of having a base type was to extend an existing type union with more cases, the way of doing this is to list the cases of the other union in this union's case type list. An optional [Combined Unions](#combined-unions) feature would make this easier. ### Interfaces A nominal type union can declare any interfaces that it implements. These are interfaces of the union type and may have no relation to the interfaces implemented by the case types. ### Body A nominal type union may declare additional members beyond the members generated by the compiler. These are member of the union type and may have no relation to the members of the case types. - Any member that introduces an instance field is disallowed and will produce an error at compile time. A type union only represents a single value. - Any constructor declared must defer to one of the compiler's generated constructors. These each behave like a record's generated primary constructor. *There may not be a reasonable use case for defining additional constructors.* ### Partial Type Unions A nominal type union may have multiple partial declarations. ```csharp public partial union Pet(Cat, Dog, Bird); ``` ```csharp public partial union Pet { public bool HasFourLegs => ...; } ``` - Each partial declaration follows the same rules as other types with respect to modifiers, type parameters and constraints. - One and only one partial declaration includes the list of case types. The order of the case types may be significant and a change in order may be a breaking change. ## Basic Representation The nominal type union is emitted as a read-only struct, with constructors corresponding to the case types and a property for accessing the contained value. All interfaces, members and attributes declared on the type union are included as part of the emitted struct type. ```csharp public union Pet(Cat, Dog, Bird); ``` Becomes ```csharp public readonly struct Pet { // constructors public Pet(Cat value) {...} public Pet(Dog value) {...} public Pet(Bird value) {...} // value access pattern public object Value => ...; } ``` ### Construction A constructor exists for each case type, initializing the type union instance with the argument value. These constructors are the minimum API necessary to create type union instances from case type values using language idioms such as casting or implicit coercion. An optional API, outlined in [conditional construction](#conditional-construction) may also exist to enable construction of type unions from values that are not known to be case types. #### Constructing with Nulls A nominal type union is not meant to have a null value. It would never match its case type in a type pattern match. The compiler warns when a type union is declared to include case types that can be null. However, it is still possible that a null value may be passed as an argument to a constructor by ignoring this warning or others. When this happens, the type union is initialized to the equivalent of its default state instead, indicating that it does not contain a value. The [Nullable Unions](#nullable-unions) section covers techniques for enabling null or non values in type unions. ### Access Patterns A nominal type union exposes one or more methods or properties used to access the value contained by the union. These are referred to in this proposal as *access patterns*. A nominal type union always has the [Value Access Pattern](#value-access-pattern), a single weakly-typed property that returns the value used to construct the type union. This is the minimum API required to translate type tests, pattern matching and explicit coercion, but not always the best way to access the value. If supported, other optional access patterns may be preferred. Possible Access Patterns: - [Value Access Pattern](#value-access-pattern) - [Discriminator Access Pattern](#discriminator-access-pattern) - [TryGet Access Pattern](#tryget-access-pattern) - [Generic TryGet Access Pattern](#generic-tryget-access-pattern) ### Storage Layouts Because the truth of how the value is stored within the nominal type union is hidden behind the facade of the access patterns it is possible to store the value in one of many possible and reasonable ways. For example, there may be a strongly-typed field for each possible case type or there may only be one field typed as object, shared by all the cases, boxing any struct types. *Due to runtime limitations it is not possible to simply overlay the memory used by the different cases (like a C++ union would) so an alternative storage layout must be chosen that finds a balance between the footprint (the size of the type itself) and the potential use of heap allocation due to boxing.* The compiler choses a layout that best suits the case types given a heuristic and possible hint by the author. In future versions of C#, the set of layouts known by the compiler may grow, the heuristic changed or the ability to specify preferences in the declaration may increase, meaning when the code is recompiled in the future the storage layout of the type may change. By default, a nominal type unions uses the [Boxed Layout](#boxed-layout) which simply backs the `Value` property with a single field. If supported, other optional layouts may be preferred. Possible Layouts: - [Boxed Layout](#boxed-layout) - [Discriminated Boxed Layout](#discriminated-boxed-layout) - [Fat Layout](#fat-layout) - [Skim Layout](#skim-layout) - [Overlapped Layout](#overlapped-layout) - [Deconstructed Layout](#deconstructed-layout) - [Overlapped Deconstructed Layout](#overlapped-deconstructed-layout) - [Hybrid Layout](#hybrid-layout) *Storage fields are not accessible to user written logic in member declarations within the body of the union declaration. They can only be assigned via the constructors and accessed via the access patterns or the language operations that use them.* ### Metadata Encoding The emitted struct is annotated with a `TypeUnion` attribute to indicate in metadata that this type is a type union. Constructors corresponding to case types are annotated with the `TypeUnionCase` attribute, indicating that the parameter type of the constructor is one of the case types. ```csharp [TypeUnion] public readonly struct Pet { [TypeUnionCase] public Pet(Cat value) {...} ... } ``` >*Case types cannot be listed in the `TypeUnion` attribute since type parameters cannot be specified in an attribute literal so they must appear as part of a member signature.* ## Language Integration When integrated into the language a type union instance behaves as if it were the value it contains for some operations when the instance is known to be a type union. While this is a gray area that may not be practical or reasonable to push to the extremes, it is a useful illusion to maintain as it simplifies code. To implement the illusion some language operations are defined to instead operate on the value contained by the union. These operations are translated into interactions with the type union's API like how cast operations are translated to calls on user-defined operator methods. This section details the minimum necessary integration points for type unions to be considered a part of the language. ---- ### Implicit Conversion to Union To make type-union types feel like they are the value they contain, assignment from a case type value to a type-union variable should happen automatically, without needing to manually construct one from the other, similar to how a value of any type can be assigned to an object variable. To enable this, an implicit conversion is introduced for any value that would match the parameter type of a case constructor. This conversion is emitted as an invocation of the constructor. ```csharp Pet pet = dog; ``` Translates to: ```csharp Pet pet = new Pet(dog); ``` *Note: This conversion cannot be specified using the user-defined conversion operators feature since case types may be interface types and user-defined conversion operators cannot be specified for interface types.* ---- ### Conversion to Union from Default Assigning default to a type union variable or converting default to a type-union results in a compiler warning. ```csharp public union Pet(Cat, Dog, Bird); Pet pet = default; // warning: cannot assign default to union ``` - It is still possible to have a type union in a default state. This rule just catches unintentional conversions similar to assigning null to a non-nullable reference type. - **Optional**: We allow use of the forgiveness operator `!` to silence this warning. ---- ### Null Pattern Match A nominal type union's value is never null. When a null pattern match is made against a type union type, it always fails. ```csharp if (pet is null) {...} ``` While the `Value` property may return a null value when the struct wrapper is in the default state, this accessor is only used for a type pattern match and not a null pattern match. To have a type union with a null value see the [Nullable Unions](#nullable-unions) section. ---- ### Type Pattern Match from Union The illusion of the union being its value means that accessing the value of the type union should feel like asking an object variable if it is one of the case-types via pattern matching. Type pattern matches on a statically known type-union type are translated to matches against the value contained in the type union. ```csharp pet is X ``` Some target types, however, might also apply to the type-union type itself. A target type of an interface that the type union is known to implement or that any case type might implement or just being a type parameter, means the match must be translated to a match on both the type union and the contained value. | Example | Translation | |-----------------------------------|----------------------------------------------| | `pet is Dog` | `pet is { Value: Dog }` | | `pet is Dog dog` | `pet is { Value: Dog dog }` | | `pet is Dog { Name: "Spot" } dog` | `pet is { Value: Dog { Name: "Spot" } dog }` | | `pet is Corgi corgi` | `pet is { Value: Corgi corgi }` | | `pet is ICase value` | `pet is { Value: ICase value }` | | `pet is ISomething value` | `pet is { Value: ISomething value }` | | `pet is IUnion value` | N/A - no translation | | `pet is TCase value` | `pet is TCase value or { Value: TCase value }` | | `pet is T value` | `pet is T value or { Value: T value }` | - Dog: A case type. - Corgi: A sub-type of Dog. - ISomething: an interface not implemented by the union but may be implemented by case types. - ICase: A case type that is an interface and not implemented by the type union itself. - IUnion: An interface that is implemented by the union. - TCase: A case type that is a type parameter - T: An arbitrary type parameter not known to be a case type. - N/A: Translation not supported, original interpretation of operation is used. ---- ### Type Pattern Match to Union A value of a known case type can be converted to a type-union type via pattern matching. ```csharp dog is Pet pet ``` Being similar to [implicit conversion](#implicit-conversion-to-union), the pattern match translates into an invocation of a corresponding constructor. ```csharp {Pet pet = new Pet(value); true} ``` *Note: Pattern matching to a type-union type from values of types not known to be a case type are supported via the optional [Type Pattern Match Unknown to Union](#type-pattern-match-unknown-to-union) feature.* ---- ### Explicit Conversion Explicit conversions involving a type-union type as either the source or target of the conversion where special rules for pattern matching would apply for the same source and target types are translated into an `is` operator applying those pattern matching rules, returning the value on success or throwing an exception on failure. ```csharp (Pet)dog; ``` Translates to: ```csharp <> ? tmp : throw new InvalidCastException(...); ``` And the reverse: ```csharp (Dog)pet; ``` Translates to: ```csharp <> ? tmp : throw new InvalidCastException(...); ``` *Note: the << >> brackets denote where additional translation occurs.* ---- ### As Operator An `as` operator involving a type-union type as either the source or target of the conversion where special rules for pattern matching would apply for the same source and target types are translated into an `is` operator applying those pattern matching rules, returning the value of on success or the default of the target type on failure. ```csharp pet as Dog ``` Translates to: ```csharp <> ? tmp : default(Dog) ``` *Note: Normal rules apply for which types can be used on the right side of an `as` operator.* *Note: the << >> brackets denote where additional translation occurs.* ---- ### Switch Statement and Expression The switch statement and expression employ the same translations for type pattern matching involving type-union types in switch cases as outlined above, with switch case using the preferred translation for its target type. ```csharp pet switch { Cat cat => ..., Dog dog => ..., Bird bird => ... _ => ... } ``` Translate to: ```csharp pet switch { { Value: Cat cat } => ..., { Value: Dog dog } => ..., { Value: Bird bird } => ..., _ => ... } ``` #### Exhaustive Unions Since type unions are limited to a closed set of case types they are exhaustible in a switch. If all cases of a type union are handled in the switch cases, a default does not need to be specified. However, a default case will still be added to handle cases of invalid unions as is normally added to an exhaustive switch. ---- ### Nullable Unions Type unions are not allowed to have a null value. Nullable case types are warned against and null values are intercepted during construction placing the type union instance into a default state that does not have a case type value. To represent the equivalent of null in a type union it is preferred to use an explicit case type to represent a non value. For example, an `Option` type union might have a `None` case type that represents not having a value. Still, you may have reasons why a null value makes sense to assign to your type union variable. If this is the case, you can easily declare the type as the nullable version of the union using the question mark syntax. ```csharp Pet? maybePet = null; ``` Because the type union `Pet` is emitted as a `struct` type, this will mean references to `Pet?` are references to `Nullable` at runtime. However, this may cause issues with the translation of some language operations which can be solved with some additional rules that are used when nullables are combined with type unions. When a nullable type union is used in a type pattern match, the match is translated to match on the non-nullable value of the nullable type union. ```csharp maybePet is Dog dog ``` ```csharp maybePet is Pet tmp && <> ``` The other operations, type conversion, etc, are translated likewise. A switch over a nullable union value can be optimized to only access the non-nullable value once. ---- ### Unknown Unions The special translations outlined in this document for nominal type unions only occur when a type union is known statically to be involved. If a type union value exists but is statically typed as object or a type parameter no special translations will occur and the normal meaning of the operation made against the union's struct wrapper type will remain in effect. Code that deals solely with generic types will not have recognized a type union being used and will not translate type tests or casts using these new rules. For example, the `OfType` method does a type test with generics only. ```csharp List pets = [new Dog(...), new Cat(...)]; pets.OfType() // will never match ``` However, doing the equivalent manually in a context that sees the type union will succeed. ```csharp List pets = [new Dog(...), new Cat(...)]; pets.Where(p => p is Dog).Select(p => (Dog)p); // will match ``` *Note: It is possible that a future version of the runtime may become type union aware and allow type tests in generic-only contexts to succeed.* --- ### Union Constraints It may seem to make sense to specify a type-union type in a generic type parameter constraint in order to constrain a type parameter to be one of the type union's case types. ```csharp TPet Feed(TPet pet) where TPet : Pet {...} ``` For example, the `Feed` method takes a pet as input, feeds it and returns a new fed pet instance on output. It uses the type parameter to guarantee the same type going in is the same type going out. ```csharp Dog fedDog = Feed(unfedDog); ``` However, since the type union is represented as a struct wrapper and struct's cannot be used in constraints like this, it is not possible to do so. The proposed feature of a type union is language only and does not exist in the runtime. The best we can do to actually constrain to a type union is to use the type union as the parameter type, but this will not get you the desired return type. ```csharp Pet Feed(Pet pet) {...} ``` This is an interesting area to consider perhaps adding a compile-time only constraint to C# or creating a new kind of runtime constraint that includes an *or* condition. ```csharp TPet Feed(TPet pet) where TPet : Cat or Dog or Bird {...} ``` ---- ---- ## Optional Features The rest of the proposal includes optional features that may enhance capability or improve performance of nominal type unions. #### Features that improve the representation or access of the value - Access Patterns - [Discriminator Access Pattern](#discriminator-access-pattern) - [TryGet Access Pattern](#tryget-access-pattern) - [Generic TryGet Access Pattern](#generic-tryget-access-pattern) - Storage Layouts - [Discriminated Boxed Layout](#discriminated-boxed-layout) - [Fat Layout](#fat-layout) - [Skim Layout](#skim-layout) - [Overlapped Layout](#overlapped-layout) - [Deconstructed Layout](#deconstructed-layout) - [Overlapped Deconstructed Layout](#overlapped-deconstructed-layout) - [Hybrid Layout](#hybrid-layout) #### Features that make the union behave more like the value in more places - More Conversions - [Conversion from unknown case to union](#type-pattern-match-unknown-to-union) - [Conversion from union to union](#type-pattern-match-from-union-to-union) - [Implicit conversion from union to union](#implicit-conversion-from-union-to-union) - Common Members - [Common Member Proxies](#common-member-proxies) - [Common Member Access](#common-member-access) - [Common Base Types](#common-base-types) - [Common Interface Implementation](#common-interface-implementation) - [ToString](#tostring) - [Equality](#equality) #### Features that help with interop or reflection - [Common Helper Methods](#common-helper-methods) - [Common Interfaces](#common-interface) #### Miscellaneous - [Default Unions](#default-unions) - [Combined Unions](#combined-unions)
---- ### Type Pattern Match Unknown to Union To enable conversion of a value not statically known to be a case type to a union type, a special translation exists. ```csharp object value = ...; if (value is Pet pet) {...} ``` To do this translation the source is checked against all possible case types, calling the corresponding constructor when a match is found. If the [TypeUnion.TryConvert](#typeuniontryconvert) feature is available, the pattern match is translated into a call on the `TryConvert` method that does these checks and more. ```csharp if (value is Pet Pet || TypeHelper.TryConvert(value, out tmp)) {...} ``` Otherwise, if the [Conditional Construction](#conditional-construction) feature is available, the pattern match is translated into a call on the `TryCreate` method that does exactly these checks. ```csharp if (value is Pet Pet || Pet.TryCreate(value, out tmp)) {...} ``` If neither are available, the pattern match has no special translation, leaving just the original test for the type union type. ```csharp if (value is Pet pet) {...} ``` **Optional:** The compiler generates the checks against all case types inline, including all the same logic that would appear in the [Conditional Construction](#conditional-construction) implementation of the `TryCreate` method. ```csharp if (value is Pet pet || (value is Cat tmp1 && new Pet(tmp1) is Pet pet) || ...) {...} ``` **Optional:** The compiler generates a per-compilation-unit helper method with the same logic ```csharp public static class PrivateTypeUnionHelpers { public static bool TryCreate(T value, out Pet pet) {...} } ... if (value is Pet pet || PrivateTypeUnionHelpers.TryCreate(value, out pet)) {...} ``` ---- ### Conditional Construction The Type Union's generated struct includes a `TryCreate` method to help in conditionally constructing type unions. ```csharp public static bool TryCreate(TValue value, out Pet pet); ``` The method tests the input value against the known case types and constructs the union using the corresponding constructor. The implementation would be similar to this: ```csharp public static bool TryCreate(TValue value, out Pet union) { switch (value) { case Cat value1: union = new Pet(value1); return true; case Dog value2: union = new Pet(value2); return true; case Bird value3: union = new Pet(value3); return true; default: pet = default; return false; } } ``` This may be used by the [Type Pattern Match Unknown to Union](#type-pattern-match-unknown-to-union) feature, may be used by the [TypeUnion.TryConvert](#typeuniontryconvert) feature, may be an implementation detail of [Common Interface](#common-interface) feature, or may just be used manually by users to perform this construction/conversion. ---- ### Type Pattern Match from Union to Union Converting between two union types will be a common operation when multiple union types exist that share some of the same case types, or similar case types such as some values from one union would be legal values of another. If the [TypeUnion.TryConvert](#typeuniontryconvert) helper method is available, it is used to conditionally convert between the two type union types. ```csharp animal is Pet pet ``` ```csharp TypeHelper.TryConvert(animal, out Pet tmp); ``` Otherwise, if the [conditional construction](#conditional-construction) `TryCreate` method is available, it is used with the value of the source obtained via the [value access pattern](#value-access-pattern). ```csharp Pet.TryCreate(animal.Value, out var pet); ``` **Optional:** this is only exposed in the explicit conversion form and not a pattern match. ```csharp (Pet)animal ``` ---- ### Implicit Conversion from Union to Union When one type union has at least all the same case types as another, a value of the other is assignable to a variable of the first without requiring an explicit cast. This is translated the same as with [type pattern match from union to union](#type-pattern-match-from-union-to-union) and [explicit conversion](#explicit-conversion) rules. ```csharp Animal animal = pet; ``` ```csharp Animal animal = TypeHelper.TryConvert(pet, out Animal tmp) ? tmp : default; ``` ---- ### Combined Unions The case types of one type union can be included in another type union without manually listing them all using a spread-like operator in its declaration. ```csharp public union Pet(Cat, Dog, Bird); public union Animal(..Pet, Alligator, Bison, Platypus); ``` - Should there be an operators to exclude types? Maybe: !, ~, - - What about including all but some cases? ..Pet-Cat ---- ### Default Unions A nominal type union always has a value. Yet, since it is implemented as a struct wrapper it may not actually have a value when the union is uninitialized or initialized to default. This is mostly dealt with by fact that type tests do not succeed when the union is in the default state. Yet, this can lead to runtime exceptions in an exhausted switch. You may want to give a nominal type union a meaningful value when it is in the default state, especially when it has a case that corresponds to not having a value, like `None` used by the `Option` type. To enable this, one case in the nominal type union's case type list can be designated as the default by assigning it to `default`, as long as that default is not null. ```csharp record None(); record Some(T Value); union Option(None=default, Some); ``` When the type union is in the default state, the union behaves like it has the specified value, with the `Value` property returning this value and the other accessor patterns acting likewise. If the [Discriminated Access Pattern](#discriminator-access-pattern) is available, the discriminator property returns the value corresponding to this case. ```csharp readonly struct Option { private readonly object _value; ... public object Value => _value ?? default(None); } ``` **Optional:** a full initializer expression may be specified to allow the default to be any non-null value. ```csharp union Pet(Cat, Dog, Bird=new Bird("Polly")); ``` ```csharp readonly struct Pet { private readonly object _value; private static readonly Bird _default = new Bird("Polly"); ... public object Value => _value ?? _default; } ``` **Conversion from Default** A nominal type union with a default case can be converted to from default without warning as listed in [Conversion to Union from Default](#conversion-to-union-from-default). ```csharp union Number(long=default, double, string); ... Number n = default; // okay here ``` ---- ### ToString The type union provides an overload to the `ToString` method, dispatching to the method on the contained value. ```csharp public override string ToString() { return this switch { Cat v => v.ToString(), Dog v => v.ToString(), Bird v => v.ToString(), _ => "" } } ``` ---- ### Equality To support comparing two type-union instances of the same type-union type, necessary to use the union type as a key in a dictionary for example, the compiler emits code that implements the standard equality interfaces and methods by deferring to the contained value when the unions both have the same cases. ```csharp public readonly struct Pet : IEquatable { public bool Equals(Pet other) {...} { return (this, other) switch { (Cat tv, Cat ov) => EqualityComparer.Default.Equals(tv, ov), (Dog tv, Dog ov) => EqualityComparer.Default.Equals(tv, ov), (Bird tv, Bird ov) => EqualityComparer.Default.Equals(tv, ov), _ => false } } public override Equals(object other) => return other is Pet typedOther && Equals(typedOther); public static bool operator == (Pet other) => Equals(other); public static bool operator != (Pet other) => !Equals(other); } ``` - Optional: In addition, the weakly-type Equals overload supports comparing instances of different type-union types, or comparing type union instances to case type values. This might be implemented using the [Common Helper Methods](#common-helper-methods) feature. ```csharp public override Equals(object other) => object.Equals( TypeUnion.GetValueUnwrapped(this), TypeUnion.TryGetValueUnwrapped(other, out object otherValue) ? otherValue : other ); ``` ---- ### Common Member Proxies Sometimes all cases of a type union share similar members. It would be convenient to be able to access those common members directly on the union type instance rather than need to first switch over all the cases and interact with each case separately. This feature adds proxy versions of those members onto the type-union type automatically generated by the compiler for all methods and properties shared by all case types. ```csharp public record Cat(...) { public bool IsHairy => true; } public record Dog(...) { public bool IsHairy => true; } public record Bird(...) { public bool IsHairy => false; } public union Pet(Cat, Dog, Bird); ``` Becomes ```csharp public readonly struct Pet { ... public bool IsHairy => this switch { Cat value1 => value1.IsHairy, Dog value2 => value2.IsHairy, Bird value3 => value3.IsHairy }; } ``` And now it is possible to use the common property directly. ```csharp Pet pet = ...; if (pet.IsHairy) {...} ``` - Note: Works well with cases that are structs or don't otherwise share a base type. ---- ### Common Base Types Sometimes all cases of a type union share a common base type. It would be convenient to be able to access the value as that type and also the members declared by it directly on the union type instance rather than need to first convert the union instance to that common type. This is an alternative to the the [Common Member Proxies](#common-member-proxies) feature. But instead of generating proxy members on the type-union type, it would simply introduce additional translations so the members of the common base type would appear to be accessible from type union instance. If all case types share a common base type, the union type takes advantage of this fact and uses that it the `Value` property instead of object. This common base type could either be inferred byt he compiler or declared using some additional syntax. ```csharp public record Animal { public virtual bool HasFourLegs { get; } } public record Cat(...): Animal; public record Dog(...): Animal; public record Bird(...): Animal; public union Pet(Cat, Dog, Bird); // inferred? public union Pet(Cat, Dog, Bird)[Animal]; // syntax? ``` Becomes ```csharp public readonly struct Pet { public Animal Value { get; } } ``` - Note: Works well with case types that share a base type and does not require proxy members, but does not work for fully disjoint case types. #### Translations When a common base type exists, member access operations against the type union instance that do not bind to the type-union instance itself are translated to apply to the value instead via the `Value` property. This helps maintain the illusion that the union type is its value. ```csharp if (pet.HasFourLegs) ``` Translates to: ```csharp if (pet.Value.HasFourLegs) ``` Likewise, conversions to the base type (or any of its base types or interfaces) translate into accesses directly on the `Value` property. ```csharp Animal animal = pet; ``` Translates to: ```csharp Animal animal = pet.Value; ``` ---- ### Common Member Access Sometimes all cases of a type union share similar members. It would be convenient to be able to access those common members directly on the union type instance rather than need to first switch over all the cases and interact with each case separately. This feature is an alternative to both the [Common Member Proxies](#common-member-proxies) and [Common Base Types](#common-base-types) features. Instead of generating proxies for members or requiring them to share a base type, this feature enables access of common members by translating accesses to members not found on the union into into access of the members on the value by generating a switch inline (at the use site) or in a generated helper method within the emitted module. ```csharp public record Cat(...) { public bool HasFourLegs => true; } public record Dot(...) { public bool HasFourLegs => true; } public record Bird(...) { public bool HasFourLegs => false; } public union (Cat, Dog, Bird); ``` ```csharp if (pet.HasFourLegs) {...} ``` Becomes ```csharp if (pet switch { Cat value => value.HasFourLegs, Dog value => value.HasFourLegs, Bird value => value.HasFourLegs }) {...} ``` ---- ### Common Interface Implementation While rare, it may be necessary to declare and implement an interface on a type union that is also declared and implemented by the case types. You may have an interface you need to be available even in a context that is not aware of the union, such as when the type union is represented boxed as an object or as a type parameter. ```csharp interface ICustomSerialize { void SerializeTo(Stream stream); } record Cat(...): ICustomSerialize {...} record Dog(...): ICustomSerialize {...} record Bird(...): ICustomSerialize {...} union Pet(Cat, Dog, Bird); Pet pet = ...; Serialize(pet); void Serialize(T item) where T : ICustomSerialize { item.SerializeTo(...); } ``` You could declare the interface on the type union and implement it yourself to delegate to the union's value. It would be easier if you could avoid writing the boilerplate implementation and let the compiler generate it for you in a manner similar to the [Common Member Proxies](#common-member-proxies) feature. *When a type union declares an interface that all case types share, but does not implement a member of the interface, that member is auto-generated by the compiler on your behalf to delegate to the specific case type values.* ```csharp // declare without implementation union Pet(Cat, Dog, Bird) : ICustomSerialize; ``` ```csharp // implementation generated private public readonly struct Pet : ICustomSerialize { void ICustomSerializable.SerializeTo(Stream stream) => ((ICustomSerialize)this.Value)?.SerializeTo(stream); } ``` ---- ### Common Helper Methods Some code may need to interact with type unions when the unions or case type values are weakly-typed. For instance, a serialization tool may need to deserialize a value that is a case type and store it via reflection in a field that is a type-union type. To enable this, the `TypeUnion` static class of helper methods exists with methods that help identify facts about type unions and converting values to and from unions and even conditionally converting unions to other kinds of unions. #### TypeUnion.IsTypeUnion ```csharp public static bool IsTypeUnion(Type type); ``` Returns true if the type is a type union. #### TypeUnion.GetCaseTypes ```csharp public static IReadOnlyList GetCaseTypes(Type type); ``` Returns a read only list of the case types of a type union. If the type is not a type union it returns an empty list. #### TypeUnion.GetValue ```csharp public static object? GetValue(object union); ``` The `GetValue` method gets the current value of the union. If the `union` parameter is not actually a type union it returns null. #### TypeUnion.GetUnwrappedValue ```csharp public static object? GetUnwrappedValue(object union); ``` The `GetUnwrappedValue` method is similar to the `GetValue` method, except it continues to drill down through values that might also be type unions until a non-type-union value is reached. #### TypeUnion.TryConvert (boxed) ```csharp public static bool TryConvert(object source, Type targetType, out object target); ``` The `TryConvert` method is used to conditionally convert a source value to a target union type, a source union's value to a target type or a source union to a target union type. #### TypeUnion.TryConvert ```csharp public static bool TryConvert(TSource source, [NotNullWhen(true)] out TTarget target); ``` The `TryConvert` method is used to conditionally convert a source value to a target union type, a source union's unwrapped value to a target type or a source union to a target union type. *This method may be targeted by the compiler to offer additional language conversions of type unions.* ---- ### Common Interface Using [helper methods](#helper-methods) is a good way to write code that constructs, accesses and converts type unions. But those helper methods would be relying on reflection to function, which may require runtime dependencies that you would rather not have and extra performance costs you would rather avoid. Having an interface that type unions implement would solve this. The helper methods could then defer to the type union via its interface for all interactions. *The `ITypeUnion` interface (actually two interfaces) provides a common means to construct and access the value of arbitrary type unions. Type unions that implement these interfaces can be handled more efficiently.* ```csharp public interface ITypeUnion { object Value { get; } bool TryGetValue([NotNullWhen(true)] out TValue value); } public interface ITypeUnion : ITypeUnion { static bool TryCreate(TValue value, [NotNullWhen(true)] out TSelf union); } ``` - The `Value` property accesses the value of the type union weakly-typed. This may cause boxing of struct values not already boxed. - The `TryGetValue` method offers a way to conditionally access the value without boxing. - The `TryCreate` static method enables conditional construction of the type union without boxing the value. ---- ### Discriminator Access Pattern The discriminator access pattern offers an alternative to the [value access pattern](#value-access-pattern) that can be significantly more performant with storage layouts that use an underlying discriminator. This pattern offers a discriminator property that returns a number corresponding to the case type of the value contained by the union and an accessor property for each case type to access the value without boxing. ```csharp public readonly struct Pet { // discriminator public int Kind { get; } // accessors public Cat Value1 { get; } public Dog Value2 { get; } public Bird Value3 { get; } } ``` - The discriminator property returns an integer value 1 through N, indicating which accessor property should be used to access the value. It returns 0 when the type union has not been formally initialized or has been assigned default, indicting that the type union is in an invalid state and none of the accessor properties should be used to access the value, unless the union has a case that is declared to be default and then the number corresponding to this default case is returned. - The accessor properties are named Value#N. The number N corresponds to the ordinal of the corresponding case type in the type union declaration. - The accessor properties are intended to only be accessed when they correspond to the current value of the discriminator. They always return a non-null value when they do correspond to the discriminator, and always return the default value of the corresponding case type when they do not. - The introduction of this access pattern will cause the order of the case types in the type union declaration to become significant. A change in the order will become a breaking change. #### Pattern Matching When used to translate a type pattern match the discriminator property is used to match the associated case and the corresponding accessor property is used to perform any additional type tests or matches. | Example | Translation | |-----------------------------------|----------------------------------------------| | `pet is Dog` | `pet is { Kind: 2 }` | | `pet is Dog dog` | `pet is { Kind: 2, Value2: var dog }` | | `pet is Dog { Name: "Spot" } dog` | `pet is { Kind: 2, Value2: { Name: "Spot" } dog }` | | `pet is Corgi corgi` | `pet is { Kind: 2, Value2: Corgi corgi }` | | `pet is ICase value` | N/A | | `pet is ISomething value` | N/A | | `pet is IUnion value` | N/A | | `pet is TCase value` | N/A | | `pet is T value` | N/A | While it is always safe to use the discriminator access pattern to access the value of the union, since it tells you which accessor to use to access the value, it is not always safe for the compiler to assume a single discriminator value comparison can be used to replace a type check. Target types that are interfaces or type parameters may match multiple cases. This is why the translation table is incomplete. Its not safe to use the discriminator access pattern in these cases. In addition, the existence of any case type that is an interface potentially pollutes the use of the discriminator access pattern for any other case. If other case types might also implement the same interface it is not possible for the compiler to know that the other case types can be provably accessed via one and only one discriminated accessor. ```csharp public union Pet(IAnimal, Dog); ... Pet pet = (IAnimal)new Dog(...); ... if (pet is Dog dog) {...} // comparing Kind to 2 (Dog) fails, it is encoded as 1 (IAnimal) ``` The existence of any case types that are type parameters has similar issues. However, it is possible that an instantiated generic type union has replaced the type parameters with types that are now sufficiently disjoint such that the compiler can prove that a target type can be accessed from one and only one discriminated accessor. It all depends on what case types are known to exist in the context that the type union is being used. ```csharp public union OneOf(T1, T2); ... OneOf number = 1; if (number is int n) {...} // succeeds because int can only occur when Kind==1 ... OneOf something = ...; if (something is Tx x) {...} // may fail because cannot know which Kind is Tx is stored. ``` Pros: - Does not box to test or access the value. - Significantly faster than a type test and can help optimize switch statements and expressions that match multiple cases. Cons: - A change in the set or order of case types is a binary breaking change because it changes the meaning of the discriminator values and the naming of the accessor properties. - Some case types may not be sufficiently disjoint to correspond to a single discriminated accessor. ---- ### TryGet Access Pattern The TryGet access pattern is another alternative to the value access pattern, that like the [discriminator access pattern](#discriminator-access-pattern) can access the values strongly-typed, but may not be as favorable to optimization. The TryGet access pattern has a `TryGetValue` method for each case type, that returns a bool indicating if the value is successfully accessed as that type and the value itself as a strongly-typed out parameter. ```csharp public readonly struct Pet { public bool TryGetValue(out Cat value); public bool TryGetValue(out Dog value); public bool TryGetValue(out Bird value); } ``` - Each `TryGetValue` method tests and accesses the value in one call, with the semantics exactly the same as pattern matching using the [Value Access Pattern](#value-access-pattern), regardless of the storage model. #### Pattern Matching When a corresponding `TryGetValue` method is available, a type pattern match can be translated using this method. | Example | Translation | |-----------------------------------|----------------------------------------------| | `pet is Dog` | `pet.TryGetValue(out Dog _)` | | `pet is Dog dog` | `pet.TryGetValue(out Dog dog)` | | `pet is Dog { Name: "Spot" } dog` | `pet.TryGetValue(out Dog tmp) && tmp is { Name: "Spot" } dog }` | | `pet is Corgi corgi` | `pet.TryGetValue(out Dog tmp) && tmp is Corgi corgi` | | `pet is ICase value` | `pet.TryGetValue(out ICase value)` | | `pet is ISomething value` | N/A | | `pet is IUnion value` | N/A | | `pet is TCase value` | `pet.TryGetValue(out TCase value)` | | `pet is T value` | N/A | Some examples cannot be translated. Only cases with `TryGetValue` overloads that correspond to the target type can be used. Pros: - Does not box to test or access the value like the [Value Access Pattern](#value-access-pattern). - Does not have the drawback of the [Discriminated Access Pattern](#discriminator-access-pattern) with respect to target types can case types that are not sufficiently disjoint. - Faster than the [Generic TryGet Access Pattern](#generic-tryget-access-pattern). Cons: - Cannot help optimize a switch statement or expression. ---- ### Generic TryGet Access Pattern The generic TryGet access pattern is similar to the [TryGet Access Pattern](#tryget-access-pattern) but only has a single generic `TryGetValue` method that can be used to test and access any value without boxing. ```csharp public readonly struct Pet { public bool TryGetValue([NotNullWhen(true)] out T value); } ``` #### Pattern Matching When this access pattern is available, all type pattern matches can be translated using the generic `TryGetValue` method. | Example | Translation | |-----------------------------------|----------------------------------------------| | `pet is Dog` | `pet.TryGetValue(out Dog _)` | | `pet is Dog dog` | `pet.TryGetValue(out Dog dog)` | | `pet is Dog { Name: "Spot" } dog` | `pet.TryGetValue(out Dog tmp) && tmp is { Name: "Spot" } dog }` | | `pet is Corgi corgi` | `pet.TryGetValue(out Dog tmp) && tmp is Corgi corgi` | | `pet is ICase value` | `pet.TryGetValue(out ICase value)` | | `pet is ISomething value` | `pet.TryGetValue(out ISomething value)` | | `pet is IUnion value` | N/A | | `pet is TCase value` | `pet is TCase value or pet.TryGetValue(out value)` | | `pet is T value` | `pet is T value or pet.TryGetValue(out value)` | Pros: - Does not box to test or access the value like the [Value Access Pattern](#value-access-pattern). - Does not have the drawback of the [Discriminated Access Pattern](#discriminator-access-pattern) with respect to target types can case types that are not sufficiently disjoint. - Only requires one method, unlike the [TryGet Access Pattern](#tryget-access-pattern). Cons: - Slower than the [TryGet Access Pattern](#tryget-access-pattern) due to generic method overhead. ---- ### Value Access Pattern The value access pattern is the default access pattern and is always generated for a type union by the compiler. It is listed here only for completeness. ```csharp public readonly struct Pet { // value access pattern public object Value => ...; } ``` - The `Value` property returns the same value used to construct the type union, even if the case type was another type union. #### Pattern Matching The translation for the value access pattern is outlined in the [Type Pattern Matching From Union](#type-pattern-match-from-union) section. - Depending on the nature of the case types, this access pattern may not be best suited for use in translating language operations. Other optional access patterns, if included may be preferred. ---- ### Access Pattern Heuristic The compiler chooses the access pattern to use for a pattern match or conversion translation given the patterns available on the type union. 1. If the [discriminated access pattern](#discriminator-access-pattern) is available and the target type of a type pattern match or conversion can be reduced to a single discriminated accessor, then this access pattern is used. 2. If the non-generic [TryGet access pattern](#tryget-access-pattern) is available and the target type of a type pattern match or conversion has a corresponding `TryGet` method, then this access pattern is used. 3. Either the [value access pattern](#value-access-pattern) or the generic [TryGet access pattern](#generic-tryget-access-pattern) is used. The choices are: - The value access pattern is always used. (current plan) - The generic TryGet access pattern is always used. - If all cases are known to be reference types, the value access pattern is used. - If all cases are known to be value types, the generic TryGet access pattern is used. ---- ### Boxed Layout The boxed layout includes a single field typed as object for all values. This is the default layout unless other layouts are available and chosen by the compiler. ```csharp public record Cat(...); public record Dog(...); public record Bird(...); public union Pet(Cat, Dog, Bird); ``` ```csharp public readonly struct Pet { private readonly object _value; private Pet(object value) { _value = value; } public Pet(Cat value): this(value) {} public Dog(Dog value): this(value) {} public Bird(Bird value): this(value) {} public object Value => _value; } ``` - The boxed layout is never combined with a [discriminated access pattern](#discriminator-access-pattern) because determining a discriminator value would defeat the performance advantage of using the pattern. ---- ### Discriminated Boxed Layout The discriminated boxed layout includes a field typed as object for all values and a discriminator field. ```csharp public record Cat(...); public record Dog(...); public record Bird(...); public union Pet(Cat, Dog, Bird); ``` ```csharp public readonly struct Pet { private readonly int _kind; private readonly object _value; private Pet(int kind, object value) { _kind = kind; _value = value; } public Pet(Cat value): this(1, value) {} public Dog(Dog value): this(2, value) {} public Bird(Bird value): this(3, value) {} public object Value => _value; // optional discriminator access pattern public int Kind => _kind; public Cat Value1 => _kind == 1 ? (Cat)_value : default!; public Dog Value2 => _kind == 2 ? (Dog)_value : default!; public Bird Value3 => _kind == 3 ? (Bird)_value : default!; } ``` - This layout is practical only when the [discriminator access pattern](#discriminator-access-pattern) is being exposed. ---- ### Fat Layout The fat layout includes a strongly-typed field for each case type and a discriminator field typed as int. ```csharp public union Pet(Cat, Dog, Bird); ``` ```csharp public readonly struct Pet { private readonly int _kind; private readonly Cat _value1; private readonly Dog _value2; private readonly Bird _value3; public Pet(Cat value) { _kind = 1; _value1 = value; } public Dog(Dog value) { _kind = 2; _value2 = value; } public Bird(Bird value) { _kind = 3; _value3 = value; } // value access pattern public object Value => _kind switch { 1 => _value1, 2 => _value2, 3 => _value3, _ => null! }; // optional discriminator access pattern public int Kind => _kind; public Cat Value1 => _value1; public Dog Value2 => _value2; public Bird Value3 => _value3; } ``` - This layout never boxes a case type value since all cases have their own strongly-typed field. - Use of the [value access pattern](#value-access-pattern) may cause boxing. ---- ### Skim Layout The skim layout is similar to the [Fat Layout](#fat-layout) except when multiple case types are known to be reference types then those cases stored in a single shared object field and cast back to the case type on access. ```csharp public record Cat(...); public record Dog(...); public record struct Bird(...); public union Pet(Cat, Dog, Bird); ``` ```csharp public readonly struct Pet { private readonly int _kind; private readonly object _value; private readonly Bird _value3; public Pet(Cat value) { _kind = 1; _value = value; } public Dog(Dog value) { _kind = 2; _value = value; } public Bird(Bird value) { _kind = 3; _value3 = value; } // value access pattern public object Value => _kind switch { 1 => _value, 2 => _value, 3 => _value3, _ => null! }; // optional discriminator access pattern public int Kind => _kind; public Cat Value1 => _kind == 1 ? (Cat)_value : default!; public Dog Value2 => _kind == 2 ? (Dog)_value : default!; public Bird Value3 => _kind == 3 ? _value3 : default!; } ``` - When all cases are known reference type this reduces to the same as the [discriminated boxed layout](#discriminated-boxed-layout). - When fewer than two cases are known to be reference types this layout reduces to the [fat layout](#fat-layout). ---- ### Overlapped Layout The overlapped layout is similar to the [Skim Layout](#skim-layout), except that it overlaps the values of case types that can be overlapped. If two or more case types can be overlapped, a special `Overlap` struct type is generated using `StructLayout` and `FieldOffset` attributes to store the values of different cases in the same memory area. Those cases with types that can be overlapped have a field corresponding to them in the `Overlap` type instead of a field in type-union type. A single field of the `Overlap` type is then added to store and access these cases. Types that are safe to overlap are: - primitive value-types - value-types from the runtime libraries that do not contain reference values - value-types from the current compilation unit that do not contain reference values *Other value types from outside the compilation unit cannot be proven to be free of reference values at compile time, since compile-time metadata may not show all private fields of a type.* ```csharp public union Number(long, double, decimal, string); ``` ```csharp public readonly struct Number { private readonly int _kind; private readonly Overlap _overlap; private readonly string _value4; [StructLayout(LayoutKind.Explicit)] file struct Overlap { [FieldOffset(0)] public long value1; [FieldOffset(0)] public double value2; [FieldOffset(0)] public decimal value3; } public Number(long value) { _kind = 1; _overlap.value1 = value; } public Number(double value) { _kind = 2; _overlap.value2 = value; } public Number(decimal value) { _kind = 3; _overlap.value3 = value; } public Number(string value) { _kind = 4; _value4 = value; } // value access pattern public object Value => _kind switch { 1 => _overlap.value1, 2 => _overlap.value2, 3 => _overlap.value3, 4 => _value4, _ => null!, }; // optional discriminator access pattern public int Kind => _kind; public long Value1 => _kind == 1 ? _overlap.value1 : default!; public double Value2 => _kind == 2 ? _overlap.value2 : default!; public decimal Value3 => _kind == 3 ? _overlap.value3 : default!; public string Value4 => _kind == 4 ? _value4 : default!; } ``` ---- ### Deconstructed Layout This deconstructed layout is similar to the [Skim Layout](#skim-layout) except that it also deconstructs value tuples and record structs into their constituent parts, allocating storage for each element using rules to share fields with other cases when possible, and then reconstructing the values on access. Type union cases under this technique end up having zero or more elements each stored in zero or more fields. If the case type cannot be deconstructed then it simply represents its own single element. If it can it is deconstructed into its elements, and then the same process is applied to each of these elements until a final set of elements that cannot be deconstructed is obtained. Each element of a case is assigned a storage location using the same pool of fields shared with all other cases. Multiple fields of the same type may be required to satisfy storage of all elements. - Elements of reference type are stored in a field with type object, unless a field of the reference type can satisfy all uses of the field across all cases. - Elements of value type (struct) are stored in a field of that type. - Case types that deconstruct into zero elements take up no additional storage. ```csharp public record struct Cat(string Name, int Lives); public record struct Dog(string Name, bool Hunts); public record struct Bird(string Name, bool WantsCracker); public union Pet(Cat, Dog, Bird); ``` ```csharp public readonly struct Pet { private readonly int _kind; private readonly string _element1; private readonly int _element2; private readonly bool _element3; public Pet(Cat value) { _kind = 1; (_element1, _element2) = value; } public Pet(Dog value) { _kind = 2; (_element1, _element3) = value; } public Pet(Bird value) { _kind = 3; (_element1, _element3) = value; } // value access pattern public object Value => _kind switch { 1 => new Cat(_element1, _element2), 2 => new Dog(_element1, _element3), 3 => new Bird(_element1, _element3) _ => null! }; // optional discriminator access pattern public int Kind => _kind; public Cat Value1 => _kind == 1 ? new Cat(_element1, _element2) : default!; public Dot Value2 => _kind == 2 ? new Dog(_element1, _element3) : default!; public Bird Value3 => _kind == 3 ? new Bird(_element1, _element3) : default; } ``` ---- ### Overlapped Deconstructed Layout The [Overlapped Layout](#overlapped-layout) and the [Deconstructed Layout](#deconstructed-layout) can be combined. Deconstructed elements with types that can be overlapped are overlapped. ```csharp public record struct Cat(string Name, int Lives); public record struct Dog(string Name, bool Hunts); public record struct Bird(string Name, bool WantsCracker); public union Pet(Cat, Dog, Bird); ``` ```csharp public readonly struct Pet { private readonly int _kind; private readonly string _element1; private readonly Overlap _overlap; [StructLayout(LayoutKind.Explicit)] file struct Overlap { [FieldOffset(0)] public int element2; [FieldOffset(0)] public bool element3; } public Pet(Cat value) { _kind = 1; (_element1, _overlap.element2) = value; } public Pet(Dog value) { _kind = 2; (_element1, _overlap.element3) = value; } public Pet(Bird value) { _kind = 3; (_element1, _overlap.element3) = value; } // value access pattern public object Value => _kind switch { 1 => new Cat(_element1, _overlap.element2), 2 => new Dog(_element1, _overlap.element3), 3 => new Bird(_element1, _overlap.element3) _ => null! }; // optional discriminator access pattern public int Kind => _kind; public Cat Value1 => _kind == 1 ? new Cat(_element1, _overlap.element2) : default!; public Dot Value2 => _kind == 2 ? new Dog(_element1, _overlap.element3) : default!; public Bird Value3 => _kind == 3 ? new Bird(_element1, _overlap.element3) : default; } ``` - Each case that has at least one element that can be overlapped has a field corresponding to it in generated `Overlap` type. If a case has multiple elements that can be overlapped, those elements are grouped into a value-tuple. ---- ### Hybrid Layout This layout stores both the value (in all its cases) and discriminator in a special hybrid type that can store both reference and struct value types in a minimal representation without requiring custom code generation particular to the case types. ```csharp public union Pet(Cat, Dog, Bird); ``` ```csharp public readonly struct Pet { private readonly HybridLayout _data; private static HybridLayout.Encoding _encoding1 = ...; private static HybridLayout.Encoding _encoding2 = ...; private static HybridLayout.Encoding _encoding3 = ...; file Pet(HybridLayout data) { _data = data; } public Pet(Cat value): this(_encoding1.Create(value)) {} public Pet(Dog value): this(_encoding2.Create(value)) {} public Pet(Bird value): this(_encoding3.Create(value)) {} // value access pattern public object Value => _data.Value; // discriminator access pattern public int Kind => _data.Kind; public Cat Value1 => _encoding1.Decode(_data); public Dog Value2 => _encoding2.Decode(_data); public Bird Value3 => _encoding3.Decode(_data); } ``` - This layout contains an object field for reference type values and 64 bits of extra memory for small structs that do not contain references. Large structs or structs with references are boxed and stored in the same field as reference type values. - Structs wrapping a single reference value are deconstructed and the reference value stored in the reference type field. - A discriminator value is also recorded using *tricks* not explained here. - This layout is a compromise that avoid boxing for some commonly used small value types, and boxing for larger value types. ---- ### Layout Heuristic The compiler chooses a layout that best suits the case types of the union. The following options are listed in order of preference, assuming the each layout is available to the compiler. 1. If all case types are reference types, the [Boxed Layout](#boxed-layout) is used. 2. The [Overlapped Deconstructed Layout](#overlapped-deconstructed-layout) is used. 3. The [Deconstructed Layout](#deconstructed-layout) is used. 4. The [Overlapped Layout](#overlapped-layout) is used. 5. The [Skim Layout](#skim-layout) is used. 6. The [Fat Layout](#fat-layout) is used. 7. The [Hybrid Layout](#hybrid-layout) is used. 8. The [Boxed Layout](#boxed-layout) is used. ---- ### Layout Hints A hint can be given to the compiler to influence the heuristic into choosing a different layout. The `UnionLayout` attribute can be specified to influence the layout used, by allowing you specify a value of the `UnionLayoutKind` enum. The `UnionLayoutKind` contains the following values: - Boxed: The [Boxed Layout](#boxed-layout) is used. - Fat: The [Fat Layout](#fat-layout) is used. - Balanced: The Standard Heuristic is used. Example: ```csharp [UnionLayout(UnionLayoutKind.Fat)] public union Pet(Cat, Dog, Bird) ``` ---- # END OF PROPOSAL ================================================ FILE: meetings/working-groups/discriminated-unions/pre-unification-proposals/custom-unions.md ================================================ # Custom Unions ## Summary [Nominal Type Unions](https://github.com/dotnet/csharplang/blob/main/proposals/nominal-type-unions.md) allow the compiler to generate union types that have special behavior when consumed. This proposal specifies a pattern that a class or struct declaration can follow in order to get the same special behavior when consumed. ## Motivation The declaration syntax for nominal unions is intended to cover most green-field situations where people want to specify a union. However, for some scenarios the generated outcome is not optimal or even usable: - There is already an existing widely consumed library type representing a "union", and the author wants to imbue it with "union powers" without breaking existing usage. - For e.g. performance, architectural or interop reasons, the contents of the union need to be stored differently than the default boxed object field used in compiler-generated unions. ## Specification A type is considered a "custom union type" if it implements the [`IUnion` interface](https://github.com/dotnet/csharplang/blob/main/proposals/union-interfaces.md). Every constructor on the type that is at least as accessible as the type and takes exactly one parameter contributes the type of that parameter as a case type of the custom union type. The consumption of such a type as a custom union type is enabled in the following ways: - **Implicit conversion**: There is an implicit union conversion from each case type to the custom union type. It is implemented by calling the corresponding constructor. - **Pattern matching**: Patterns applied to a value of the custom union type (other than always-succeeding patterns such as `_` and `var`) are instead applied to the `IUnion.Value` property. - **Exhaustiveness**: Switch expressions covering all the case types of the custom union type are considered exhaustive. ## Example Say the following type already exists: ```csharp public sealed class Result { internal Result(object? outcome) => (Value, Error) = outcome switch { Exception error => (default!, error), T value => (value, null), null when default(T) is null => (default!, null); _ => throw new InvalidOperationException(...); }; public Result(T value) => (Value, Error) = (value, null); public Result(Exception error) => (Value, Error) = (default!, error); public T Value { get; } public Exception? Error { get; } public bool Succeeded => Error is null; } ``` It can be made a union type simply by implementing the `IUnion` interface: ```csharp public sealed class Result : IUnion { object? IUnion.Value => Error ?? Value; ... // Existing members } ``` Note that in this example the existing type already has a public `Value` property with a different meaning than the one on the `IUnion` interface, so `IUnion.Value` gets implemented explicitly, and that's the one the compiler will consume for pattern matching purposes. Note also that the type is only considered to have two case types, `T` and `Exception`, even though it has a third single-parameter constructor. That's because the `object?` constructor is less accessible than the type itself and doesn't count. ## Drawbacks Not every existing type may be enhanced in a non-breaking way to become a custom union type using these rules. For instance, it may not be able to expose the right set of constructors to establish the desired set of case types - e.g. it relies on factory methods for creating values. It is possible that we need to refine or enhance the mechanism by which a type is interpreted as a custom union type. ================================================ FILE: meetings/working-groups/discriminated-unions/pre-unification-proposals/nominal-type-unions.md ================================================ # Nominal Type Unions Champion issue: https://github.com/dotnet/csharplang/issues/9411 Union overview issue: https://github.com/dotnet/csharplang/issues/8928 ## Summary Named unions have a closed list of "case types": ``` c# public union IntOrString(int, string); ``` Fresh case types can be succinctly declared if we adopt the "Case Declarations" proposal: ``` c# public union Pet { case Cat(...); case Dog(...); } ``` Case types convert implicitly to the union type: ``` c# Pet pet = dog; ``` Patterns apply to the *contents* of a union. Switches that handle all case types are exhaustive: ``` c# var name = pet switch { Cat cat => ..., Dog dog => ..., // No warning about missing cases } ``` There is no "is-a" relationship between a union type and its case types: ``` c# _ = obj is Pet; // True only if 'obj' is an actual boxed 'Pet' ``` ## Motivation - **Cost:** Forego runtime support and get 90% of the value for 10% of the cost. - **Performance:** Eliminate more allocations now and in the future. - **Simplicity:** Avoid introducing a new kind of runtime type relationship in e.g. generic code. - **Flexibility:** Embrace custom unions and low allocation layout options in the future. ## Detailed design ### Syntax A union declaration has a name and a list of case types. ``` antlr type_union_declaration : attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list? '(' type (',' type)* ')' type_parameter_constraints_clause* (`{` type_declarations `}` | ';') ; ``` Case types can be any type that converts to `object`, e.g., interfaces, type parameters, nullable types and other unions. It is fine for cases to overlap, and for unions to nest or be null. ### Lowering ``` c# public union Pet(Cat, Dog){ ... } ``` Is lowered to: ``` c# [Union] public record struct Pet { public object? Value { get; } public Pet(Cat value) => Value = value; public Pet(Dog value) => Value = value; ... } ``` ### Implicit conversion A "union conversion" implicitly converts from a case type to the union. It is sugar for calling the union's constructor: ``` c# Pet pet = dog; // becomes Pet pet = new Pet(dog); ``` If more than one constructor overload applies an ambiguity error occurs. ### Pattern matching Except for the unconditional `_` and `var` patterns, all patterns on a union value get implicitly applied to its `Value` property: ``` c# var name = pet switch { Dog dog => dog.Name, // applies to 'pet.Value' var p => p.ToString(), // applies to 'pet' }; ``` A pattern cannot _explicitly_ access a union's `Value` property. This is similar to `Nullable`. ### Exhaustiveness A `switch` expression is exhaustive if it handles all of a union's cases: ``` c# var name = pet switch { Dog dog => ..., Cat cat => ..., // No warning about non-exhaustive switch }; ``` ### Nullability The null state of a union's `Value` property is tracked normally, with these additions: - When none of the case types are nullable, the default state for `Value` is "not null" rather than "maybe null". - When a union constructor is called (explicitly or through a union conversion), the new union's `Value` gets the null state of the incoming value. Switch exhaustiveness obeys normal nullability rules: ``` c# Pet pet = GetNullableDog(); // 'pet.Value' is "maybe null" var value = pet switch { Dog dog => ..., Cat cat => ..., // Warning: 'null' not handled } ``` ### Nested unions Unions can recursively have unions as case types: ``` c# union Pet (Cat, Dog); union Animal (Pet, Cow); ``` There is no "merging" of nested unions - in patterns or elsewhere. An `Animal` is never directly a `Cat`, but it might be a `Pet` that is a `Cat`: ``` c# if (animal is Pet p && p is Cat c) ... ``` Or simply ```c# if (animal is Pet and Cat c) ... ``` ## Drawbacks - *Merging:* Unlike runtime-supported unions, nested unions do not "merge". If the declaration is nested then so is the consuming code. - *Runtime type relationships:* Some functionality, e.g. testing whether a value belongs to a union, requires helper methods. - *Serialization:* Serialization frameworks may need to special case unions to roundtrip them correctly. - *Generic specialization:* Unions, like other structs, put pressure on generic specialization in the current runtime. ## Optional features - *Custom union types:* Generalize to a compiler pattern so custom union types can be manually authored. - *Helper methods:* Generate helper methods to facilitate common union functionality such as testing union membership. - *Alternative layouts:* Let the user request a non-boxing union layout, e.g. with a `struct` keyword. ================================================ FILE: meetings/working-groups/discriminated-unions/pre-unification-proposals/non-boxing-access-pattern.md ================================================ # Non-boxing Access Pattern for Custom Unions ## Summary A custom union can provide an alternative, non-boxing means to access its value by implementing a `TryGetValue` method overload for each case type, as well as a `HasValue` property to check for null. ## Motivation A motivating scenario for manually implementing a [custom union type](https://github.com/dotnet/csharplang/blob/main/proposals/custom-unions.md) is to customize how the value is stored to hopefully either match an existing interop layout or to avoid allocations. However, this goal is hampered by the limitation of the compiler only understanding how to access the value via the `Value` property, resulting in struct values being boxed regardless of layout. This proposal allows custom union types that support non-allocation scenarios, opening the way to possible future first-class syntax for non-allocating unions and to the development of special union types like `Option` and `Result` that would benefit from minimizing or eliminating extra allocations. ## Specification ### Pattern A custom union may offer non-boxing access to its value by implementing `TryGetValue` methods that accept each of its case types, plus a `HasValue` property to check for null: ```csharp public struct MyUnion : IUnion { public bool HasValue => ...; public bool TryGetValue(out Case1 value) {...} public bool TryGetValue(out Case2 value) {...} object? IUnion.Value => ...; } ``` ### Lowering When the compiler lowers a type pattern match, and the type involved corresponds to a `TryGetValue` overload, the compiler uses this overload instead of the `Value` property to implement the pattern match. ```csharp if (u is Case1 c1) {...} ``` lowers to: ```csharp if (u.TryGetValue(out Case1 c1)) {...} ``` If multiple `TryGetValue` overloads apply, and overload resolution fails to pick a unique best overload, the compiler will pick one arbitrarily rather than yield an ambiguity error. When the compiler lowers a `null` constant pattern match, and a `HasValue` property is available, the compiler uses this property instead of the `Value` property to implement the pattern match: ```csharp if (u is null) {...} ``` lowers to: ```csharp if (!u.HasValue) {...} ``` ### Well-formedness It is up to the author of a custom union with non-boxing access to ensure that the behavior of the access methods is functionally equivalent to the behavior of using the `Value` property: - `u.HasValue` yields true if and only if `u.Value is not null` would yield true - `u.TryGetValue(out T value1)` yields true if and only if `u.Value is T value2` would yield true, and `value1` is equal to `value2`. ## Example Here is an example of a custom union employing a strategy of using separate fields for each case, and an additional field acting as a discriminator. ```csharp public record struct Point(double X, double Y); public record struct Rectangle(Point TopLeft, Point BottomRight); public struct PointOrRectangle : IUnion { private enum Kind { Null = 0, Point, Rectangle } private readonly Kind _kind; private readonly Point _value1; private readonly Rectangle _value2; public PointOrRectangle(Point value) => (_kind, _value1, _value2) = (Kind.Point, value, default); public PointOrRectangle(Rectangle value) => (_kind, _value1, _value2) = (Kind.Rectangle, default, value); object? IUnion.Value => _kind switch { Kind.Point => _value1, // boxes Kind.Rectangle => _value2, // boxes _ => null }; public bool HasValue => _kind != Null; public bool TryGetValue(out Point value) { if (_kind == Kind.Point) { value = _value1; return true; } else { value = default; return false; } } public bool TryGetValue(out Rectangle value) { if (_kind == Kind.Rectangle) { value = _value2; return true; } else { value = default; return false; } } } ``` ================================================ FILE: meetings/working-groups/discriminated-unions/pre-unification-proposals/union-interfaces.md ================================================ # Union Interfaces ## IUnion ### Summary Union types implement the interface `IUnion` that makes it easy to identify and interact with unions at runtime when a value that might be a union is weakly typed or represented as a type parameter. ```csharp object value = ...; if (value is IUnion union) { // don't write union wrapper Write(union.Value); } else { Write(value); } ``` ### Motivation To enable library or utility code that is unaware of application specific union types to become union aware and interact with union values without using reflection. To provide a means for the compiler to identify union types and access the value of the union with certainty. ### Detailed Design ```csharp public interface IUnion { // The value of the union or null object? Value { get; } } ``` * The `IUnion` interface is all that is necessary for a type to be considered a union by the language. * The `Value` property of the interface is targeted by the compiler to implement pattern matching. * *Nominal Type Unions* generated by the compiler implement this interface. ### Example ```csharp public struct MyUnion : IUnion { public MyUnion(Case1 value) { this.Value = value; } public MyUnion(Case2 value) { this.Value = value; } // implements IUnion.Value public object Value { get; } } ``` ## IUnion<TUnion> ### Summary Unions may also implement the `IUnion` to provides a means to construct union instances at runtime when the union type is a constrained type parameter. ```csharp TUnion ReadUnion() where TUnion : IUnion { object val = ReadValue(); // wrap the value in the union if (TUnion.TryCreate(val, out var union)) return union; throw ...; } ``` ### Motivation To enable library or utility code that is unaware of application specific union types to become union aware and construct union instances from case values at runtime. ### Detailed Design ```csharp public interface IUnion : IUnion where TUnion : IUnion { // Creates a union from a value static abstract bool TryCreate(object? value, [NotNullWhen(true)] out TUnion union); } ``` * *Nominal Type Unions* generated by the compiler implement this interface. * Maybe named `IUnionTryCreate` instead? ### Example ```csharp // lowered MyUnion public struct MyUnion : IUnion { public Union(Case1 value) { this.Value = value; } public Union(Case2 value) { this.Value = value; } // IUnion.Value public object? Value { get; } // IUnion.TryCreate public static bool TryCreate(object? value, [NotNullWhen(true)] out MyUnion union) { // handle all known case types and null switch (value) { case Case1 value1: union = new MyUnion(case1); return true; case Case2 value2: union = new MyUnion(value2); return true; case null when _canBeNull: union = default; return true; default: union = default!; return false; } } // precompute if this union can be created with a null value. private static readonly bool _canBeNull = default(Case1) == null || default(Case2) == null; } ``` * *Note*: it is also valuable for a union type to provide a public means to construct another instance when the union type is fully understood, but the value is not. ```csharp object? value = ReadValue(); // if not type-safe certain that value is a case type, use TryCreate. if (MyUnion.TryCreate(value, out MyUnion union)) { ... } ``` ## Additional Interfaces (Not Approved) ### Summary Additional interfaces expose the remaining methods and properties that the compiler is designed to look for without requiring the union to adopt these specific signatures as its public API. For example, the following union-like type is not compatible as is. It uses the term `Value` to refer to a case and it does not expose public constructors. ```csharp public record Error(int code); public struct Result { public bool IsValue => ...; public bool IsError => ...; public T Value => ...; public Error Error => ...; public static Result FromValue(T value) {...} public static Result FromError(Error error) {...} } ... Result result = ...; if (result is Value(var v)) {...} // error: not a union ``` Without completely redesigning the type and impacting all the current users, the type can be brought forward by implementing union interfaces explicitly. ```csharp public record Error(int code); public struct Result : IUnion, IUnionCreate, T>, IUnionCreate, Error>, IUnionGetValue, IUnionGetValue { public bool IsValue => ...; public bool IsError => ...; public T Value => ...; public Error Error => ...; public static Result FromValue(T value) {...} public static Result FromError(Error error) {...} // incompatible with existing API object? IUnion.Value => IsValue ? this.Value : IsError ? this.Error : null; // custom union pattern to avoid boxing // would be confusing terminology given existing API bool IUnionCreate, Value>.Create(T value) => FromValue(value); bool IUnionCreate, Error>.Create(Error value) => FromError(value); bool IUnionHasValue.HasValue => IsValue || IsError; bool IUnionTryGetValue>.TryGetValue([NotNullWhen(true)] out T value) => ...; bool IUnionTryGetValue.TryGetValue([NotNullWhen(true)] out Error error) => ...; } ... Result result = ...; if (result is Value(var v)) {...} // works! ``` ### Motivation Many existing first and third party types already exist that are unions, but do not today conform to the patterns the compiler is looking for to identify and interact with them correctly. It is already possible to create custom union types that implement the `IUnion` and `IUnion` interfaces without exposing the interface methods on the union itself. The compiler can easily identify these types as union and call through to the interface methods directly (using constrained calls) to guarantee ### Detailed Design ```csharp public interface IUnionCreate where TUnion : IUnionCreate { static abstract TUnion Create(TCase value); } public interface IUnionHasValue { bool HasValue { get; } } public interface IUnionTryGetValue : IUnionHasValue { bool TryGetValue([NotNullWhen(true)] out TCase value); } ``` * The compiler will target the IUnionCreate's Create methods if available, when an appropriate accessible constructor is not. The compiler will also use the IUnionCreate implementations to identify the set of case types. * The compiler will translate null pattern tests to the IUnionHasValue.HasValue property. * The compiler will translate type pattern matches to the IUnionTryGetValue.TryGetValue method if available and applicable, instead of accessing via IUnion.Value property. * Note: it is not required to implement these interfaces if the union type's public API contains the equivalent signatures. ### Known Issues * Multiple implementations of generic interfaces with type parameter arguments produce an error today. However, this kind of usage will not become more ambiguous than the type would already be if instantiated with multiple uses of the same type argument. ```csharp public struct FatUnion : IUnion, IUnionTryGetValue, // error, may become ambiguous at runtime IUnionTryGetValue { private readonly int _kind; private readonly T1 _value1; private readonly T2 _value2; public FatUnion(T1 value) { _kind = 1; _value1 = value; } public FatUnion(T1 value) { _kind = 2; _value2 = value; } bool IUnionHasValue.HasValue => _kind != 0; bool IUnionTryGetValue.TryGetValue(out T1 value) {...} bool IUnionTryGetValue.TryGetValue(out T2 value) {...} } ``` ### Alternatives Instead of requiring union types to implement multiple variations of the same generic interface, a family of similar interfaces could exist for each arity of cases. ```csharp public interface IUnionCreate where TUnion : IUnionCreate { static abstract TUnion Create(T1 value); static abstract TUnion Create(T2 value); } public interface IUnionCreate where TUnion : IUnionCreate { static abstract TUnion Create(T1 value); static abstract TUnion Create(T2 value); static abstract TUnion Create(T3 value); } public interface IUnionTryGetValue : IUnionHasValue { bool TryGetValue([NotNullWhen(true)] out T1 value); bool TryGetValue([NotNullWhen(true)] out T2 value); } public interface IUnionTryGetValue : IUnionHasValue { bool TryGetValue([NotNullWhen(true)] out T1 value); bool TryGetValue([NotNullWhen(true)] out T2 value); bool TryGetValue([NotNullWhen(true)] out T3 value); } ``` * How many of these would be defined? What happens to custom unions that have a large number of cases? ================================================ FILE: meetings/working-groups/discriminated-unions/to-nest-or-not-to-nest.md ================================================ # Case types: To nest or not to nest? Both [closed hierarchies](https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md) and [nominal type unions](https://github.com/dotnet/csharplang/blob/main/proposals/nominal-type-unions.md) allow the declaration of "case types" (derived types of closed classes or listed case types of unions) to be either nested or not - both approaches are perfectly valid for a programmer to choose. With the [case declarations](https://github.com/dotnet/csharplang/blob/main/proposals/case-declarations.md) proposal in its current form we are implicitly encouraging a style where case types are nested within their closed class or union type. This is further supported by the [Target-typed static member access](https://github.com/dotnet/csharplang/blob/main/proposals/target-typed-static-member-lookup.md) proposal, which eliminates the drudgery of fishing such nested members out again, at least when a target type is present. But is nesting case types the right thing to do - or at least to encourage? Is the answer different for closed hierarchies vs union types? Should we use nesting ourselves if we define e.g. `Option` and `Result` types in our libraries? Let's compare nested and non-nested approaches on different parameters. ## Running example: Option types `Option` is a canonical example of a "union type" representing that there may or may not be a value of type `T`. We can define `Option` either as a closed class or a union, and with either nested or top-level case types: ```csharp // Closed hierarchy, non-nested case types public closed record Option; public record None() : Option; public record Some(T value) : Option; // Closed hierarchy, nested case types public closed record Option { public record None() : Option; public record Some(T value) : Option; } // Union, non-nested case types public record None(); public record Some(T value); public union Option(None, Some); // Union, nested case types public union Option(Option.None, Option.Some) { public record None(); public record Some(T Value); } ``` We'll refer back to these declarations in nearly every section below. ## Existing types For closed classes, the "case types" - the derived classes - are always declared in context of their base class. There are no "existing case types". You are always free to choose whether to nest them in the closed base class or declare them at the top level next to it. Unions, on the other hand, are designed to allow existing types as case types. This means that case types may be declared independently elsewhere, unaware that anyone is using them as a case type in a union: ```csharp public union Pet(Cat, Dog); // 'Cat' and 'Dog' are existing types ``` In those situations, you don't have the option of nesting the case types in the union type. Anything we do in the language to improve the nested-case-types experience won't help existing-types scenarios. ## Analogy with enums Unions/closed classes are somewhat analogous to enums, in that there's a list of possible contents. For enums that list is nested: ```csharp public enum Color { Red, Green, Blue, } ``` The analogy only goes so far: Enums list *values*, unions list *types*. And enums aren't actually listing *all* possible values, as by default they are not exhaustive (although [Closed Enums](https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/discriminated-unions/Closed%20Enums.md) aim to help with that). That said, leaning into a syntactic analogy with enums through nesting of case types may help developers better connect with the new concepts of closed hierarchies and unions. ## Pollution of declaration space When nested, case types won't "pollute" the enclosing declaration space. This argument definitely has merit for enums, where "cases" are often many, and their naming is often somewhat contextual to the enclosing enum type. It is not clear that this argument applies to the same degree to closed hierarchies and unions. Perhaps they will tend to have fewer cases with more self-explanatory names? If we look to other languages, case types generally are *not* nested. F# for example puts the case names into the enclosing scope: You can (and do) directly use e.g. the `Some` name to create or match an `Option` value without any target typing. In TypeScript there is also no notion of nesting case types in union types. Both seem to do fine - insofar as we're looking to address similar scenarios with unions in C#, it seems unlikely that we would have different needs. In general, in C# it is fairly rare for types to be nested in other types if their primary use is intended to be outside of those types. Is there reason to believe that this would be different for unions? ## Avoiding explicit type parameters Nested types implicitly have the same type parameters - if any - as the enclosing type. There's no need to repeat them. Looking at the nested `Option` declarations above, the case types don't need explicit type parameters. By contrast, top-level case types do need to explicitly take the type parameters they need and either pass them to a closed base class or instantiate them in a union case type list. ## Avoiding repetition in the declaration The [case declarations](https://github.com/dotnet/csharplang/blob/main/proposals/case-declarations.md) feature provides a much abbreviated way of declaring nested case types for both closed classes and unions: ```csharp // Case declarations in a closed hierarchy public closed class Option { case None(); case Some(T Value); } // Case declarations in a union public union Option { case None(); case Some(T Value); } ``` Compared to the nested `Option` declarations above, it uses the context of the outer declaration to fill in missing information that links the cases to the "union" type: For the closed class it fills in the base class of each of the case types, whereas for the union it fills in the list of case types. In both it avoids one explicit mention of `Option` for each case type. Would it be possible to have similar abbreviations for top-level case types? For closed hierarchies it is hard to see how. The "linking information" between derived and closed type is in the derived class, in the form of a base class specification. If you're not nested in the intended base class, how do you imply what *should* be the base class, short of saying it explicitly? For unions, however, the "linking" information is in the union declaration itself, in the form of the case type list. It would be entirely possible to allow syntax in that list that causes the case types to be implicitly declared, e.g.: ```csharp public union Option(case None(), case Some(T Value)); // or simply public union Option(None(), Some(T Value)); ``` Where either the `case` keyword or the `(...)` parameter list itself is the syntactic signal that the case type doesn't refer to an existing type, but should be declared as a record of the given name. Such a scheme may be great for simple situations like this, but may become unwieldy if you want to e.g. specify explicit bodies on the case types. It is possible that we can come up with better syntax. ## Avoiding type arguments When referencing a case type from the "outside", you need to specify type arguments, regardless of whether it's nested or not. Which features could help with that? First a scenario without useful target types: ```csharp // non-nested, no target type object o = new Some(5); if (o is Some(var value)) ...; // nested, no target type object o = new Option.Some(5); if (o is Option.Some(var value)) ...; ``` Here the only hope of doing a bit of type inference seems to be in the `new` expression. Long ago we discussed, but rejected, a [proposal](https://github.com/dotnet/roslyn/issues/2319) for allowing generic type inference in object creation expressions, similar to how it works in generic methods: ```csharp // non-nested, no target type object o = new Some(5); // '' inferred from constructor argument // nested, no target type object o = new Option.Some(5); // '' inferred from constructor argument? ``` The type inference for the non-nested example seems a straightforward application of existing type inference logic. For the nested case it gets more complicated; we'd essentially have to figure out how to allow type arguments for enclosing types to be inferred as well. Turning to scenarios with a potentially useful target type: ```csharp // non-nested, target type Option option = new Some(5); if (option is Some(var value)) ...; // nested, target type Option option = new Option.Some(5); if (option is Option.Some(var value)) ...; ``` In the nested case, [target-typed static member access](https://github.com/dotnet/csharplang/blob/main/proposals/target-typed-static-member-lookup.md) can abbreviate away not only the type argument but also the enclosing type: ```csharp // nested Option option = new .Some(5); // 'Option' inferred if (option is .Some(var value)) ...; // 'Option' inferred ``` For the non-nested case, there are now proposals to: - Allow target types to be taken into account in generic type inference: [Target-typed generic type inference](https://github.com/dotnet/csharplang/blob/main/proposals/target-typed-generic-type-inference.md). - Allow generic type inference in `new` expressions: [Inference for constructor calls](https://github.com/dotnet/csharplang/blob/main/proposals/inference-for-constructor-calls.md). - Allow generic type inference in type patterns, treating the incoming type (`Option` in this case) as the "target type": [Inference for type patterns](https://github.com/dotnet/csharplang/blob/main/proposals/inference-for-type-patterns.md). Between them, they would enable: ```csharp // non-nested Option option = new Some(5); // '' inferred if (option is Some(var value)) ...; // '' inferred ``` In summary, various auxiliary features have been proposed that would help avoid specifying type arguments when consuming the case types of closed classes or unions, both in nested and non-nested scenarios. ### Too many type parameters When it comes to type parameters, there's a fundamental and significant difference in expressiveness between closed hierarchies and type unions. Looking back at the top-level versions of declarations in the running `Option` example, the closed hierarchy version of `None` needs a type parameter of its own, whereas the union version does not: ```csharp public record None : Option(); // closed hierarchy public record None(); // union ``` The closed hierarchy `None` has to be generic, even though it doesn't use `T` for anything! That's because the relationship between `None` and `Option` is expressed through inheritance, the derived class needs to pass *some* `T` to its base class, and there is no other good option (no pun intended). As a result, each generic instantiation of `Option` has its own `None` type. In a sense, every type parameter of the closed base class is forced upon all case types. By contrast, in the union version, all generic instantiations of `Option` share the same `None` type: ```csharp var none = new None(); Option stringOption = none; // Fine Option intOption = none; // Also fine ``` The upshot is that a generic union type, unlike a closed generic class, is free to have non-generic (or less-generic) case types that are shared between multiple generic instantiations of the union type. Nesting changes this, however: Nested types in general are *also* forced to "inherit" all type parameters of the enclosing type - it happens implicitly in the language. So union types with nested case types are in the same situation as (nested or non-nested) closed hierarchies: They cannot have non-generic or less-generic case types. In the running example, there is no sharing of `None` values: ```csharp var none = new Option.None(); // Why do I need to specify `T` when `None` doesn't use it? Option stringOption = none; // Error: 'Option.None' cannot be assigned to 'Option.None' ``` This loss of expressiveness for nested union types may not be that big of a problem for our `Option` example: `None` values carry no data, so people probably won't be too upset that they can't reuse `None` values across different content types. But that changes if we look at another common union example, the `Result` type. Let's try a nested version first: ```csharp public union Result(Result.Success, Result.Failure) { public record Success(TValue Value); public record Failure(TError Error); } ``` Result types are often brought up in connection with "pipelining" architectures; where a function in a pipeline may look like this: ```csharp // Produce a string from an incoming int, or propagate a failure Result M(Result result) { if (result is Result.Failure failure) return failure; // Error ... } ``` Even though the incoming and outgoing `Result` both have `TError = string`, a `Failure` value cannot just be passed through because it embeds an implicit `TValue` type argument that doesn't match! Not being able to propagate a `Failure` directly without deconstructing and reconstructing at every step seems unreasonably cumbersome. (Of course the same issue applies the other way around: You cannot propagate a `Success` through a function that changes only the `TError` type). By contrast, with top-level case type declarations: ```csharp public record Success(TValue Value); public record Failure(TError Error); public union Result(Success, Failure); Result M(Result result) { if (result is Failure failure) return failure; // Fine! Both have 'Failure' as a case. } ``` Non-nested failures don't have to lug around which kind of value they would have had if they'd succeeded, and non-nested successes don't have to be distinguished by what kind of error they would have had if they had failed. In summary, if case types need only some of the type parameters of their union, nesting them adds significant generic complexity for consuming code. ## Summary - *Existing types*: Not all case types *can* be nested in a closed class or union type. For that reason alone it's important to consider features that improve the experience with non-nested case types. - *Enum analogy*: Some union scenarios are probably closer to enum scenarios than others. Where applicable, the enum analogy can probably help users embrace closed classes and unions. - *Declaration space pollution*: Enums use nesting to protect the wider code from being "polluted" by all the enum value names. On the other hand, union features in e.g. F# and TypeScript do fine without such nesting. - *Repetition in declarations*: "Case declarations" propose a shorthand for declaring nested case types. Features could probably also be designed to abbreviate non-nested case types. - *Avoiding type arguments*: "Target-typed static member access" sidesteps having to provide type arguments *when* there's a target type *and* the case type declaration is nested. For other scenarios we can imagine various extensions to generic type inference. - *Too many type parameters*: Closed classes as well as nesting of case types force case types to have as many type parameters as the base type or union, even when they don't need them. Only unions with top-level case types escape this problem. ## Questions This write-up is not in and of itself a proposal. But it brings up questions the answers to which can help guide which future concrete proposals to consider, and how to think about them: - As we add convenience features to the language and concrete union types to the core libraries, should we adopt a bias for or against nesting of case types? Is one more important to support and encourage than the other? - As we start thinking about guidance around these new features, can we identify scenarios that lend themselves more to nested or to non-nested approaches? ================================================ FILE: meetings/working-groups/discriminated-unions/type-value-conversion.md ================================================ # Type Value Conversion ## Summary A type expression specified in a value context can be converted to a value if the type supports a conversion to value. ```csharp GateState value = GateState.Locked; ``` ## Motivation A nominal type union can declare cases that contain no corresponding values. Unions that are similar to enums will often have many of these no-value cases. ```csharp public union GateState { case Locked; case Closed; case Open(float amount); } ``` Even though only a few of the cases contain data, all cases are separate types. In this example, the types declared when using case declarations are records. However unfortunate, a case without data must still be allocated. ```csharp GateState state = new GateState.Locked() ``` It would be preferable if all uses of these non-value cases could share a single instance and avoid repeated allocation. A typical way of achieving this is to declare a static field or property on the type, using that member to access the same instance each time. ```csharp record Locked { public static readonly Locked Instance = new Locked(); } ``` A case declaration could add this member implicitly, but a user would still need to refer to it explicitly. This may be unexpected at first and tedious always. ```csharp GateState value = GateState.Locked.Instance; ``` If the type expression could be converted directly to a value, then code is clearer because it is always just referring to the case, and not an implementation artifact. ```csharp GateState value = GateState.Locked; ``` * *Note: It is not possible to have a nested type and a property of the same name in the same declaration scope.* ### Other Uses Singleton classes are quite common in the wild and would benefit from a type to value conversion. For example, most custom equality comparers take no outside arguments and rarely ever require more than one instance to exist. ```csharp public class MyEqualityComparer : IEqualityComparer { public bool Equals(MyType a, MyType b) => ...; public int GetHashCode(MyType a) => ...; public static readonly MyEqualityComparer Instance = new MyEqualityComparer(); } ``` ## Detailed Design The type expression to value conversion is declared via a conversion operator on the type. ```csharp public record Locked { public static readonly Locked Instance = new Locked(); public static implicit operator this => Instance; } ``` * The `this` operator converts a type expression to a value of that same type. * The author declaring a conversion operator expresses clear intent for the conversion to exist. ## Alternative Designs ### Conversion by Pattern A conversion exists between a type expression and a value when the type contains a static field or property with a recognized name that returns a value of the type's type. ```csharp public record Locked { public static readonly Locked Instance = new Locked(); } ``` * The pattern looks for a specific member name like `Instance`. ### Conversion by Attribute A conversion exists between a type expression and a value when the type contains a static field or property decorated with the `Singleton` attribute. ```csharp public record Locked { [Singleton] public static readonly Locked Value = new Locked(); } ``` * This alternative allows existing types with fields/properties returning singleton or default values to support this conversion even if the member does not have the pattern recognized name. ### Conversion by Interface A conversion exists between a type expression and a value when the type implements an interface with a known static property. ```csharp public interface ISingleton where TSelf : ISingleton { TSelf Singleton { get; } } ``` ```csharp public record Locked : ISingleton { public static readonly Locked Instance = new Locked(); static Locked ISingleton.Singleton => _instance; } ``` * The compiler uses the property from the interface. ================================================ FILE: meetings/working-groups/discriminated-unions/union-patterns-update.md ================================================ # Union patterns update Union patterns matter for "custom unions", i.e., union types that are "handwritten" to get the language's [union behaviors](https://github.com/dotnet/csharplang/blob/main/proposals/unions.md#union-behaviors), rather than generated from [union declarations](https://github.com/dotnet/csharplang/blob/main/proposals/unions.md#union-declarations). We anticipate custom unions to include existing "union-like" types that are augmented to be recognized as unions by the language, as well as new types for which different characteristics (e.g. in terms of storage or performance) are desired compared to what's generated from union declarations. We expect both kinds to be rare in comparison to use of union declarations, and for most users never having to declare custom union types. We propose a few changes to the current patterns in order to better serve more scenarios: - Union types are marked with a `[Union]` attribute instead of the `IUnion` interface. - Union members are either found on the union type itself or delegated to an interface that the union type implements. In addition there are some potential scenarios that we don't currently have proposals for, but that might come up later. We list those at the end. ## Marking union types with an attribute The current pattern of marking union types with the `IUnion` interface leads to a couple of problems, e.g.: - A type cannot implement the `IUnion` interface *without* being a union. - If a base class is a union then derived classes must also be unions. We propose to use a `[Union]` attribute to mark types that are intended to have union behaviors: ```csharp [Union] public record struct Pet { public Pet(Dog value) => Value = value; public Pet(Cat value) => Value = value; public Pet(Bird? value) => Value = value; public object? Value { get; } } ``` As part of this change, the `Value` property used by the compiler is no longer `IUnion.Value`, but just the property found on the type. Union patterns no longer rely on specific interfaces or interface members. We don't anticipate allowing `[Union]` on type parameters, which therefore cannot be considered union types by the compiler. ## Union member providers The current pattern looks for members on the union type itself. This has a few downsides: - Existing types may have members that would unintentionally be recognized as union members (e.g. copy constructor). - Such members may even clash in such a way that you couldn't have both, even if you had an attribute to distinguish them (e.g. `Value` property). - Types may not wish to expose additional union members as part of their public surface area. We propose to allow a union type to optionally delegate all union members to a nested `IUnionMembers` interface that the union type implements. Since constructors for a type cannot occur on another type, the interface would instead use static factory methods called `Create`: ```csharp [Union] public record struct Pet : Pet.IUnionMembers { object? _value; Pet(object? value) => _value = value; // Look for union members here, not in 'Pet' itself public interface IUnionMembers { static Pet Create(Dog value) => new(value); static Pet Create(Cat value) => new(value); static Pet Create(Bird? value) => new(value); object? Value { get; } } object? IUnionMembers.Value => _value; } ``` The compiler can use constrained calls where applicable to avoid the overhead of interface invocation. ### Alternatives Instead of delegating to another type, it has been proposed to mark union members in the union type itself with attributes. This could allow marking members with different names, or different kinds of members (e.g. factory methods or user-defined implicit conversions) so as to make use of any suitable members the type already has. One downside is that attributes can never be used to hide members from the surface area. Thus it is strictly less expressive than delegation. In addition, the set of attributes and the rules for their usage might get quite complicated. By comparison, the delegation approach puts all union members in one place with well-defined, unambiguous names and signatures. ## Other scenarios We can imagine reasonable scenarios that are not expressible by the above changes. While we are not proposing solutions for those now, it is useful to imagine possible ways to address them in the future, if only to reassure ourselves that we are not accidentally blocking them by choices we make now. ### "Consume-only" unions Not all union types may want to offer the ability for their users to create union values directly; perhaps instead they offer APIs that create them internally and hand them out. Such unions wouldn't have creation members (constructors or factories), so they need another way of specifying their case types. We don't yet have specific proposals. ### More specific shared base types In some union types, case types share a more specific base type than `object?`. We could probably allow the `Value` property to expose such a more specific type, and have the compiler take advantage of the additional knowledge (e.g. non-nullability) in its implementation of union behaviors. ### Non-object case types The requirement for an `object`-returning `Value` property that gives access to the union's value no matter the case type, means that all case types have to be implicitly convertible to object. That rules out e.g. ref structs and pointer types as case types of compiler-recognized unions. There may be ways in which we can amend this, either by removing the requirement for a `Value` property to exist, or allowing certain case types not to be accessed through it. We don't yet have specific proposals. ### Non-boxing access pattern The original proposal includes an optional non-boxing access pattern, and this proposal preserves that. Additional `HasValue` and `TryGetValue` members can be used by the compiler as strongly typed alternatives to matching through the weakly typed `Value` property. This can be used for efficiency purposes, e.g. when a union type stores value types directly instead of boxing them. The current non-boxing access pattern is definitely worth revisiting before we lock it in, but we don't yet have specific alternate proposals. ================================================ FILE: meetings/working-groups/discriminated-unions/union-proposals-overview.md ================================================ # Union proposals overview **Umbrella issue**: https://github.com/dotnet/csharplang/issues/9662 - [Union proposals overview](#union-proposals-overview) - [Unions](#unions) - [Standard type unions](#standard-type-unions) - [Closed enums](#closed-enums) - [Closed hierarchies](#closed-hierarchies) - [Case declarations](#case-declarations) - [Target-typed static member access](#target-typed-static-member-access) - [Target-typed generic type inference](#target-typed-generic-type-inference) - [Inference for constructor calls](#inference-for-constructor-calls) - [Inference for type patterns](#inference-for-type-patterns) - [Type value conversion](#type-value-conversion) ``` mermaid flowchart LR %% Features Unions[Unions]:::approved Standard[Standard type unions]:::approved Enums[Closed enums]:::approved Hierarchies[Closed hierarchies]:::approved Cases[Case declarations]:::approved TargetAccess[Target-typed access]:::approved TargetInfer[Target-typed inference]:::approved InferNew[Inference for constructors]:::approved InferPattern[Inference for type patterns]:::consideration TypeValue[Type value conversion]:::consideration %% Dependencies Unions --> Standard Unions & Hierarchies --> Cases -.-> TargetAccess Hierarchies <-.-> Enums TargetInfer --> InferNew & InferPattern %% Colors classDef approved fill:#cfc,stroke:#333,stroke-width:1.5px; classDef consideration fill:#ffd,stroke:#333,stroke-width:1.5px; classDef unapproved fill:#ddd,stroke:#333,stroke-width:1.5px; classDef rejected fill:#fdd,stroke:#333,stroke-width:1.5px; ``` ## Unions - **Proposal**: [Unions](https://github.com/dotnet/csharplang/blob/main/proposals/unions.md) - **LDM**: Approved. - **Dependencies**: None. A set of interlinked features that combine to provide C# support for union types, including a declaration syntax and several useful behaviors. ```csharp public union Pet(Cat, Dog); // Declaration syntax Pet pet = dog; // Implicit conversion _ = pet switch { Cat cat => ..., // Implicit matching Dog dog => ..., } // Exhaustive switching ``` ## Standard type unions - **Proposal**: [Standard type unions](https://github.com/dotnet/csharplang/blob/main/proposals/standard-unions.md) - **LDM**: Approved. - **Dependencies**: [Nominal type unions](#nominal-type-unions). A family of nominal type unions in the `System` namespace: ```csharp public union Union(T1, T2); public union Union(T1, T2, T3); public union Union(T1, T2, T3, T4); ... ``` ## Closed enums - **Proposal**: [Closed enums](https://github.com/dotnet/csharplang/blob/main/proposals/closed-enums.md) - **LDM**: Approved. - **Dependencies**: None. Design together with [Closed hierarchies](#closed-hierarchies) to ensure coherence. Allow enums to be declared `closed`, preventing creation of values other than the explicitly declared enum members. A consuming switch expression can assume only those values can occur, avoiding exhaustiveness warnings. ```csharp public closed enum Color { Red, Green, Blue } var infrared = Color.Red - 1; // Error, not a declared member _ = color switch { Red => "red", Green => "green", Blue => "blue" // No warning about missing cases }; ``` ## Closed hierarchies - **Proposal**: [Closed hierarchies](https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md) - **LDM**: Approved. - **Dependencies**: None. Design with [Closed enums](#closed-enums) to ensure coherence. Allow classes to be declared `closed`, preventing its use as a base class outside of the assembly. A consuming switch expression can assume only derived types from within that assembly can occur, avoiding exhaustiveness warnings. ```csharp // Assembly 1 public closed class C { ... } public class D : C { ... } // Ok, same assembly // Assembly 2 public class E : C { ... } // Error, 'C' is closed and in a different assembly _ = c switch { D d => ..., // No warning about missing cases } ``` ## Case declarations - **Proposal**: [Case declarations](https://github.com/dotnet/csharplang/blob/main/proposals/case-declarations.md) - **LDM**: Approved. - **Dependencies**: [Closed hierarchies](#closed-hierarchies) and [Nominal type unions](#nominal-type-unions). A shorthand for declaring nested case types of a closed type (closed class or union type). ```csharp public closed record GateState { case Closed; case Locked; case Open(float Percent); } public union Pet { case Cat(...); case Dog(...); } ``` ## Target-typed static member access - **Proposal**: [Target-typed static member access](https://github.com/dotnet/csharplang/blob/main/proposals/target-typed-static-member-access.md) - **LDM**: Approved. - **Dependencies**: None. Informed by union scenarios. Enables a type name to be omitted from static member access when it is the same as the target type. ``` c# return result switch { .Success(var val) => val, .Error => defaultVal, }; ``` ## Target-typed generic type inference - **Proposal**: [Target-typed generic type inference](https://github.com/dotnet/csharplang/blob/main/proposals/target-typed-generic-type-inference.md) - **LDM**: Approved. - **Dependencies**: None. Informed by union scenarios. Generic type inference may take a target type into account. ```csharp MyCollection c = MyCollection.Create(); // 'T' = 'string' inferred from target type ``` ## Inference for constructor calls - **Proposal**: [Inference for constructor calls](https://github.com/dotnet/csharplang/blob/main/proposals/inference-for-constructor-calls.md) - **LDM**: Approved. - **Dependencies**: [Target-typed generic type inference](#target-typed-generic-type-inference) 'new' expressions may infer type arguments for the newly created class or struct, including [from a target type](target-typed-generic-type-inference) if present. ```csharp Option option = new Some(5); // Infer 'int' from argument and target type ``` ## Inference for type patterns - **Proposal**: [Inference for type patterns](https://github.com/dotnet/csharplang/blob/main/proposals/inference-for-type-patterns.md) - **LDM**: Needs more work. - **Dependencies**: [Target-typed generic type inference](#target-typed-generic-type-inference) Type patterns may omit a type argument list when it can be inferred from the pattern input value. ```csharp void M(Option option) => option switch { Some(var i) => ..., // 'Some' inferred ... }; ``` ## Type value conversion - **Proposal**:[Type value conversion](https://github.com/dotnet/csharplang/blob/12e6f5b0d512d15d32c8e7ae95674bd070b2758f/meetings/working-groups/discriminated-unions/type-value-conversion.md) - **LDM**: Needs more work. - **Dependencies**: None. A type expression specified in a value context can be converted to a value if the type supports a conversion to value. ```csharp GateState value = GateState.Locked; ``` ================================================ FILE: meetings/working-groups/expressions-statements/ES-2022-11-30.md ================================================ # Bridging expressions and statements working group meeting - Nov 30, 2022 Our agenda was to review proposal issues and make an initial decision on whether or not we wanted to pursue the feature in the short term. We organized the discussion around feature areas and scenarios. ## Making things that are currently statements exist as expressions - [#973](https://github.com/dotnet/csharplang/issues/973) - **Declaration expressions** This would enable syntax like `(var x = 1).ToString()`. > Resolution: We felt this is not a feature we will work on in the near term. - [#1340](https://github.com/dotnet/csharplang/issues/1340) - **Rethrow expressions** This would allow `throw` in expression contexts, where we currently allow `throw ex`. > We will work on this in the near term. - [#867](https://github.com/dotnet/csharplang/issues/867) - **Allow `break`, `return`, `continue` in an expression context** This would allow `break`, `return`, or `continue` where we allow `throw ex` expressions today. > We think `throw` was a special case. We think `return` may be a similar special case. There are identified scenarios where `??` and `null` checks might immediately return from a method. We're less sure about `break` and `continue`. We need to see scenarios where those would be compelling. Those may not be added now. - **Allow any statement to appear as an expression** There isn't an issue for this. We added it to the discussion as a logical extension. We don't see a compelling scenario for investing in this at this time. ## Modernizing older syntax These centered on the different structures of the `switch` statement and `switch` expression. There are two alternative for bringing the `switch` expression and statement syntax closer together: - [#3038](https://github.com/dotnet/csharplang/issues/3038) - **Revamping switch statements to reflect switch expression syntax** This would allow switch statements to use some of the syntax for switch expressions. - [#2632](https://github.com/dotnet/csharplang/issues/2632) - **Allow a switch expression as a statement expression** This can be thought of as the inverse of #3038. It would allow a switch expression to be a *statement-expression* (or a void returning statement). > We're interested in pursuing this in the near term. We're not sure if these are mutually exclusive, or which would be more useful. ## Allowing sequences of operations in place of a single expression These are are very similar, each being a different syntax with potentially different things allowed inside the syntax, but they're all similar in concept. - [#377](https://github.com/dotnet/csharplang/issues/377) - This is most similar to let bindings in Lisp. Most restrictive of the proposals, it doesn't allow things like foreach - [#3086](https://github.com/dotnet/csharplang/issues/3086) - More general than #377, allows any statement before the final expression. - [#3037](https://github.com/dotnet/csharplang/issues/3037) - A more specific version of #3086, it only expands the syntax for switch expressions > Of these, the only one we're interested in pursuing in the near term is #3037 Overall, of this set, we are interested in these three items: 1. Allowing switch expressions to be statement expressions 1. Coalescing switch expression and switch statement syntax 1. Allowing multi-line arms in a switch expression. ================================================ FILE: meetings/working-groups/extensions/Compatibility through coexistence between extension types and extension methods.md ================================================ # Compatibility through coexistence between extension types and extension methods This proposal approaches compatibility between classic extension methods and new extension types by emphasizing smooth coexistence and avoiding arbitrary behavior differences. * **Goal:** Bring _expressiveness_ of new extension methods close enough to that of old ones that new code won't need to use classic extension method syntax * **Goal:** Avoid arbitrary _behavior differences_ that may be jarring or surprising * **Goal:** Allow smooth _coexistence_ between new and old extension methods for the same underlying type * **Non-goal:** Allow all existing extension methods to be faithfully ported to the new paradigm * **Non-goal:** Allow reliance on details of lowered format for new extension members The dual purpose of extension types is to allow other member kinds and also raise the level of abstraction. The two are linked: other member kinds are possible - and pleasant - because the declaration syntax is detached from the generated format. We should protect that by not exposing the lowered shape of declarations at the language level. The basis of this proposal is the [extension proposal](https://github.com/dotnet/csharplang/blob/main/proposals/extensions.md) resulting from [separating extensions from roles](https://github.com/dotnet/csharplang/blob/1e8255a438517bc3ad067c726c28cfa20cb60f1e/meetings/working-groups/extensions/rename-to-roles-and-extensions.md) and [dropping their ability to act as instance types](https://github.com/dotnet/csharplang/blob/1e8255a438517bc3ad067c726c28cfa20cb60f1e/meetings/working-groups/extensions/extensions-as-static-types.md). We'll address the three goals in turn. At the end is a brief summary of the features proposed throughout. ## Expressiveness In classic extension methods, the fact that the receiver is expressed as a parameter enables a lot of variation in how it is specified, some of which is useful, much of which isn't. In extension types, the receiver is specified as a shared underlying type for all the members. This proposal takes the viewpoint that where variations allowed by old extension methods are useful, the new syntax should enable them to be specified at the member level. This way they won't be split between multiple extension types. Not all the variations are worth carrying forward. In the following, some are kept, and some are discarded based on perceived prevalence and usefulness. ### Attributes Only about .7% of extension methods have attributes on the receiver, and an overwhelming fraction (95%) of those are nullability-related. However, the next section on nullability may add to that. We could add a new attribute target `this`, so that such attributes can be placed on the member declaration: ``` c# [this:NotNullWhen(false)] public bool IsNullOrEmpty() { ... } ``` ### Nullability About 1.5% of extension methods have a nullable receiver type. Some fraction of those are nullable value types. Nullable reference types are useful in this position for extension methods like `IsNullOrEmpty` that are explicitly null-safe. This feels like a useful scenario, and some core infrastructural extension methods rely on it. However, it is also quite rare and probably doesn't warrant dedicated language syntax. Instead, we can use attributes in conjunction with the `this` attribute target proposed above: ``` c# [this:AllowNull][this:NotNullWhen(false)] public bool IsNullOrEmpty() { ... } ``` Using attributes to refine the nullability profile of members is already quite common, and this doesn't seem out of place. ### Ref and readonly Extension method receivers are usually value parameters, but if the receiver type is a value type they can also be declared as `ref`, `in` or `ref readonly` parameters. The latter was eventually allowed in C# so that value types can be mutated by extension methods as well as passed to them more efficiently. Extension types in their current design take a different approach: Reference type receivers are _always_ passed by value and value type receivers are _always_ passed by reference. (If the receiver type is an unconstrained type parameter, the decision is made at runtime!) This closely emulates the behavior of `this` inside instance member declarations, which extension members syntactically imitate, leading to the least surprising semantics of `this`. The new design does not allow for passing the reference receivers by reference or value receivers by value. Classic extension methods do not allow reference types to be passed by reference either, so that is no loss of expressiveness. There hasn't been a user ask for this. Passing value receivers by value, however, is common in classic extension methods. It's what happens by default, and passing by reference wasn't even allowed in C# for several versions. Any extension method that doesn't actively need to mutate the receiver is likely to take its receiver by value, and only about 1% of extension methods in fact take the receiver by reference. The main benefit of choosing to pass a value receiver by value is that it protects the original from being mutated. The proposal here is to instead satisfy that desire in a different way: Members of extension types for value types can be declared `readonly`, just as members of a struct can be declared `readonly`. ``` c# public extension TupleExtensions for (int x, int y) { public readonly int Sum() => x + y; // `this` is readonly and cannot me mutated } ``` Just as in struct declarations, the extension type itself could be declared `readonly` as a shorthand for declaring `readonly` on all members. A `readonly` annotated member will have its receiver passed as `ref readonly`, which protects it from mutation. It differs semantically from pass-by-value only in subtle ways that aren't likely to be important in usage scenarios. However, unlike pass-by-value, it prevents mutation of `this`, avoiding undetected bugs where mutation is accidentally applied to a copy and changes are lost. ### Disambiguation Classic extension methods allow disambiguation simply by calling them as a static method. Extension types do not allow this, because from a user perspective there *are* no static methods. When extensions were still intended to be usable as instance types, disambiguation could happen simply by casting the receiver to the extension type. This option is no longer open - or at least it would need special context-specific semantics. Disambiguation for certain static extension members such as methods and properties is likely to be simple: Just dot off the extension type instead of the underlying type: ``` c# MyStringExtensions.Format(...); // Instead of string.Format(...) ``` For instance members and e.g. operators it is still possible to disambiguate through other means, but they are not elegant, and the LDM has agreed that we should work towards a good disambiguation solution. ### Grouping Classic extension methods can - and often do - co-inhabit the same static class even when they have different receiver types. Extension types do not offer the same possibility, since the receiver type is tied to the extension type. While this is a difference to get used to, it does not seem to be a limit to expressiveness. It may lead to more type declarations, but probably not excessively so. This proposal does not attempt to address it. ## Behavior differences ### Overload resolution and type inference The current proposal for extension types does overload resolution in two stages: First, suitable extension types are found, inferring any type arguments to the extension type in the process. Then, type inference and overload resolution is performed on eligible candidate members in those extensions. This two-stage approach seems intuitively right: the first stage identifies the receivers, and once determined, the second stage works exactly the same as would an instance method call. However, this differs from how overload resolution and type inference work on classic extensions methods, where all type parameters are already on the method, and the receiver participates in one big round of type inference alongside other arguments to the underlying static method. Most behavioral differences currently stem from using a weaker inference approach in the first phase of extension type resolution. At minimum this should be replaced with full type inference. This still leaves scenarios where, with classic extension methods, non-receiver arguments can impact type inference for type parameters that occur in the receiver type. However, those situations are likely to be exceedingly rare in practice. There's a proposal for how to do the one-phase approach with extension types. Embracing that would allow overload resolution to mix old and new extension methods in the same candidate sets, something which we rely upon in the Coexistence section below. At the same time, with behavior differences to the more intuitive two-phase approach being so rare, it is unlikely to lead to user confusion. ### Refness As already mentioned, extension types pass the receiver using the same semantics - by-value, `ref` or `ref readonly` - as the `this` parameter in corresponding instance members. This is a deliberate behavior deviation from that of extension methods, intended to minimize behavioral differences from the instance members that extension members are syntactically imitating. ## Coexistence This proposal specifically does not attempt to preserve semantics from old to new syntax. This means that library authors concerned with compatibility may need to keep their classic extension methods around for a while, possibly forever. In recognition of this, we should strive to make coexistence of new and old extensions friction-free. There are several points on the dial that a library author may aim for; possibly going through them from one to the next over time in a deprecation process. Staying at any one of these stages forever is a fully supported and encouraged choice, depending on the library author's priorities. 1. *Keep old extension methods, add new extension members:* You should not ever need to write another classic extension method, and if you already have some, that should not hamper the ability to seamlessly add new ones in the new style. This should give library users full source and binary compatibility. 2. *Migrate old extension methods, keep most back compat:* Most old extension methods should be able to migrate to new ones in ways that are fully source compatible - or very close to it - as long as they are used *as* extension methods. If the old static methods are kept *without* the `this` modifier and made to redirect to the new ones, then binary compatibility is achieved, and invocations *as* static methods stay source compatible as well. 3. *Deprecate old extension methods:* The old static methods - formerly extension methods - could be deprecated. This would drive users to switch to new disambiguation syntax, while still maintaining binary compatibility. 4. *Remove old extension methods:* This would force recompilation of code that was compiled against the old extension methods. Let's look at what it takes to allow this. ### Precedence If either kind of extension method - old or new - is given precedence in overload resolution then it would allow old and new versions of the same extension method to coexist without giving rise to ambiguity. However, this could lead to surprising results where less suitable members of the preferred kind are chosen. Allowing two simultaneous versions of the same extension method is not a goal. While it could ensure slightly higher source compatibility in rare cases, the interactions are confusing, and there's a risk of the two versions drifting apart. The proposal here is to have new and old extension methods share the same precedence. For that to work, we need to use the one-phase type inference and overload resolution approach described earlier. ### Type names Static classes with extension methods often already have the "good" names. It would be great if new non-generic extension types could optionally use the same type name. The easiest way is for the two to just *be* the same type. There are some different ways you could imagine allowing this in syntax; the simplest and most readable would probably be to allow both to be declared, and then implicitly merge them as if they were declared `partial`. ## Summary of proposed decisions 1. New `this:` target for attributes 2. Nullable reference receivers expressed with `[this:AllowNull]` attribute 3. Allow `readonly` modifier on extension members and extension types with a value-type underlying type 4. Disambiguation mechanism still to be designed 5. Use one-phase approach to type inference and overload resolution of extension type members 6. Share overload resolution precedence between old and new extension methods 7. Allow non-generic top-level extension type and static class of the same name to implicitly merge ================================================ FILE: meetings/working-groups/extensions/Extension-API-docs.md ================================================ # Extensions and API reference Extensions introduce new requirements for our API reference pipeline. The addition of new extension member types, and the representation of extension containers require changes to the pipeline for a good customer experience: - The docs term is "Extension Methods". That term currently means "extension methods with an instance receiver". Now, extensions can be properties, indexers, or operators. These extension members can be accessed as either an instance member on the extended type, or as a static member on the extended type. - Readers need to know if the receiver is an instance of a type, or the type itself. - Readers occasionally need to know the class name of holding the extension, typically for disambiguation. - The extension block is emitted as a nested class with skeleton members and XML doc comments. The nested class is given an unspeakable name. The new extensions experience should be built on the framework used for the existing extension methods. In fact, when a new extension member is a method whose receiver is an instance, both forms are binary compatible. The document describes the new experience as a set of enhancements to the existing extension method documentation. ## Existing Extension methods The prototype for an extension method communicates many of the key concepts that consumers need to use these methods in their application. Consider this prototype: ```csharp public static class SomeExtensionsAndStuff { public static bool IsEmpty(this IEnumerable source) => source.Any() == false; } ``` The prototype and the class declaration communicate important information to readers. - The first parameter, noted with the `this` modifier indicates two important keys: - The method is an extension method. - The receiver is an instance of an `IEnumerable`. - The class name `SomeExtensionsAndStuff` indicates how it can be called as a static method, if multiple extension methods have signatures that create an ambiguity. For example, users can call extension methods in two ways: - As though it were an instance of a receiver: ```csharp bool empty = sequence.IsEmpty(); ``` - As a static method call using the declaring type as the receiver: ```csharp bool empty = SomeExtensionsAndStuff.IsEmpty(sequence); ``` The presentation and navigation elements used for the docs site help users find these methods and recognize that these methods are *extension methods*. For the following notes the links are to the docs for `System.Linq.Enumerable` and the extensions on `System.Collections.Generic.IEnumerable` as they exist today: - The Table of Contents (TOC) nodes for the extended type, such as [`IEnumerable`](https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1), list only the members defined on the interface. In other words, none of the extension methods are listed in the TOC (left navigation pane) under the extended type. - The API docs build system generates a section on the type page for the extended type, such as `IEnumerable`, that lists all [extension methods](https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1#extension-methods). This section uses the following format: - The prototypes show the `this` modifier, indicating that they are extension methods. - Each overload has a separate entry in the section. - The signatures indicate if the method is a specialization (`Sum(this IEnumerable)`) or generic (`Where(this IEnumerable source, Func predicate)`). - The section lists all extensions, from all extending types. They are grouped by type, then sorted alphabetically. **We would like this to change, and have the extension methods sorted alphabetically, without regard to the containing class.** - **Alternative: This should change so that the API pages always group extension methods by their containing class and receiver type, including generic specialization.** - The signature does not show any indication of the extending type. - The docs pipeline generates this section on the extended type from the descriptions of the extension methods. The `///` comments on the extended type (for example, `System.Collections.Generic.IEnumerable`) doesn't need to include all extension methods in the entire library. In source, the existing `///` elements on the extending type and the extension method declarations enable this presentation. ## Docs presentation for C# 14 extension members The presentation for C# 14 extensions needs to account for several new types of extension members: - Extension methods (new, instance or static) - Extension properties (instance or static) - Extension indexers. - Extension operators. This proposal currently assumes no changes to the presentation for existing extension methods. > Alternative: We could experiment with displaying all extension members using this updated format. Readers can give feedback on whether they prefer a unified presentation, or want to know if a method follows the new or old format. There is no reason a consumer needs to know which format was used to declare an extension. The declarations are binary compatible. ### Extension member prototypes When an extension member prototype is shown, the format should show the extension container: ```csharp extension(IEnumerable source) { public bool IsEmpty { get; } } ``` The `source` parameter is referred to as the *receiver parameter*, or *receiver*. The *receiver parameter* may be an instance, as shown above, or it may be a type, as in the following: ```csharp extension(IEnumerable) { public static IEnumerable Create(int size, Func factory); } ``` > Alternatives: The prototypes above show a single extension member in an extension container. In source, multiple members share the same receiver and are declared in the same extension container. An alternative for the prototypes would be as follows: > > ```csharp > extension(IEnumerable source) > { > public bool IsEmpty { get; } > public int Length { get; } > public IEnumerable Sample(int sampleInterval); > } > ``` > > It could save screen space to collect members by receiver declaration and remove the duplication. That would only apply on pages where multiple prototypes are shown together. Another negative is that it could conflict with the current lists in the TOC that have all overloads collected together. We should specify the URL for the docs page for a single `extension` declaration. The *receiver parameter* includes a parameter name when the receiver is an instance. The *receiver parameter* doesn't include a parameter name when the receiver is a type. This presentation enables the following: - Readers see the new extension syntax when consuming new extensions, driving awareness and adoption. - Readers can quickly distinguish a new-style extension (noted by the receiver parameter), and existing extension methods (noted by the `this` modifier on the first parameter). - The `extension` container indicates that the member is an extension member. - The parameter on the `extension` node indicates the type extended, and provides a key to know if the member is intended to extend an instance or extend the type. - The prototype only includes the `static` modifier if it would be present in source, as in an operator declaration: ```csharp extension(IEnumerable) { public static IEnumerable operator + (IEnumerable left, IEnumerable right); } ``` ### Extension members in the extended type's page The API docs build system generates the section on the type page for the extended type that lists all extension members. This section should have sub-sections for *extension methods*, *extension properties*, and *extension operators*. Extension indexers should follow the format for indexers, and be listed as an `Item[]` property. There isn't a `this` modifier on the first parameter. In fact, the receiver is declared on the extension, not the member. The prototypes in this section should expand to show the `extension` container, as follows: - The prototypes are displayed as described in the previous section. - Otherwise, the format is consistent with the current format: - Each overload has a separate entry in the section. - The signatures indicate if the method is a specialization (`extension(IEnumerable source) { ... }`) or generic (`extension(IEnumerable source) { ... }`). - The section lists all extensions, from all extending types. They are sorted alphabetically, as proposed for current extension methods. - **Alternative: All extension members are grouped by containing class and receiver type, including generic specialization.** ### Extension class page The page for the class containing extensions will need only minimal updates in how extension members are displayed. The `static` classes that contain extension methods are classes, and could already define static properties, indexers, and operators. The additional work involves understanding the [unspeakable extension type](#unspeakable-extension-type) that contains new extension members. - The TOC node for the class will typically have additional nodes for **Properties** (including indexers), and **operators**. Classes already support this, so it should already work. Note that the node for methods displays *method groups*, not *individual overloads*. That should remain. - The page should also have sections for **Properties**, and **operators**. - The prototypes for extensions should be displayed as shown [above](#extension-member-prototypes). Note alternative for grouping extensions by receiver declaration. ### Extension member page There should be a new style for extension members. This should be modeled after the existing member template, with the following changes: - The receiver type should be shown in the title and the header block. - The receiver parameter should have its own block. It should precede the other parameter block. - The prototype for the member should follow the format shown [above](#extension-member-prototypes). The receiver parameter is named for extensions whose receiver is an instance. The name is not included for extensions where the receiver is a type. The emphasis on the receiver parameter reinforces the new syntax, and is necessary for readers to see the extended type on the new extension member. ### Unspeakable extension skeletons The compiler generates a public skeleton class that defines prototypes for extension members. The skeleton class has an unspeakable name and contains the following prototypes: - A private static unspeakable method declaration starting with ``. This private static method includes one parameter that represents the receiver for the extension members declared in the skeleton. - Prototypes for all extension members defined for the receiver. These prototypes show the declarations as though declared on the receiver type. The unspeakable skeleton provides the prototypes for the extension members and the receiver type. The nodes of the skeleton provide a location for the XML output from the `///` comments on the extension members and the receiver parameter. The `///` comments on the extension declaration are written as XML on the node for the unspeakable member declaring the receiver. The `///` comments on each extension member are written as XML on the node for the embedded member of the unspeakable containing class. See the following code and XML for an example of extension members and the resulting XML output. ```csharp /// Summary for E static class E { /// Summary for extension block /// Description for T /// Description for t extension(T t) { /// Summary for M /// Description for U /// Description for u public void M(U u) => throw null!; /// Summary for P public int P => 0; } } ``` produces the following XML output: ```xml Test Summary for E Summary for extension block Description for T Description for t Summary for M Description for U Description for u Summary for P ``` ### XML Output for `///` comments The compiler uses the skeleton declarations to produce the XML output for all `///` comments on the extension node and the embedded extension members: - Comments from the `extension` block are applied to the embedded receiver field in the unspeakable nested class. This can include type parameter information and parameter information for the receiver. - Comments from extension members are output to the skeleton prototypes embedded in the unspeakable nested class. - Any extension member declaration that includes `typeparamref` or `paramref` nodes in the extension block resolve to the corresponding `typeparam` and `param` nodes on the extension block. - The implementation nodes for the extension members use the `` node to point to the generated XML output from the skeleton member. The nodes on the receiver and each member must be merged by the tools that consume the XML (for example, Visual Studio IntelliSense, or the MS Learn build process). ## Disambiguation and API docs A disambiguation syntax is required when more than one `class` declares extension members with the same signature. Consumers must use a static syntax to specify which method should be called. We believe this is the less common case. However, it is common enough that our docs presentation should clearly display the class where an extension member is declared. The C# LDM hasn't finalized the [disambiguation syntax](https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/extensions/disambiguation-syntax-examples.md). The final disambiguation syntax shouldn't impact the API generation pipeline. We will demonstrate it in docs. ================================================ FILE: meetings/working-groups/extensions/anonymous-extension-declarations.md ================================================ # Anonymous extension declarations ## Summary This design draws on many recent proposals, and is derived based on a set of assumptions about the kind of feature that would be successful with developers. It places extension member declarations within anonymous extension declarations that specify underlying type and accompanying type parameters, and which are in turn nested within non-generic static classes. ``` c# public static class Enumerable { // Extension members for IEnumerable extension(IEnumerable) { // 'this' refers to underlying value public bool IsEmpty => !this.GetEnumerator().MoveNext(); } // Extension members for IEnumerable extension(IEnumerable) { public IEnumerable Where(Func predicate) { ... } public IEnumerable Select(Func selector) { ... } public static IEnumerable operator +(IEnumerable e1, IEnumerable e2) { ... } } // Classic extension method public static IEnumerable Cast(this IEnumerable source) { ... } // Non-extension member public static IEnumerable Range(int start, int count) { ... } } ``` ## Motivation This design starts from a set of assumptions about what matters more or less to users of the feature, deriving a design that meets them. Those assumptions, though gathered from the criticisms of both "type-based" and "member-based" proposals, are certainly debatable and won't all be agreed upon. They represent the beliefs and opinions of the author. Let's go through them below, along with a sneak peek at how they drive the design. ### Type names and grouping * Developers want to be able to group a collection of related extension members under just one type name, even if they span multiple different underlying types. People would resent having to come up with multiple separate type names for different underlying types, and having to navigate those many names for disambiguation, `using static` inclusion etc. * Separate type names for different receiver types won't be necessary for disambiguation - they aren't today. This implies an overarching grouping entity that carries no inherent information beyond simply a type name. The proposed design reuses top-level non-generic static classes for this purpose. ### Relationship to classic extension methods * People would like to be able to place their new extension members together with their existing extension methods. * People do appreciate being able to declare non-extension static members next to extension declarations. * People are better off leaving their classic extension methods as static method declarations so that the static invocation signature is obvious, and to avoid churn and risk. * People will, however, expect lookup to work very closely similar between old and new extension methods. Because the proposed design keeps using non-generic static classes as the overarching grouping entity, new extension declarations can sit side by side with old extension methods and non-extension static members. However, new extension methods do not work exactly the same as old ones. (Although do see the "Alternatives" section). ### Type parameters and underlying types * Underlying types belong together with their type parameter declarations, and it would be confusing to separate them. * People will resent the verbosity of having to repeat underlying types and accompanying type parameters for each member. * Sometimes there's a need to specify different parameter details (ref-ness, nullability, ...) for the same underlying type. The design uses anonymous extension declarations within static classes. These anonymous extension declarations specify type parameters, underlying type and any parameter details for the extension member declarations they contain. ### Member declarations * Extension member declarations should look identical to corresponding member declarations in classes and structs. * Parameter names for underlying values aren't important and people will resent the forced verbosity of having to specify them. The design keeps extension members with the same syntax as their class and struct counterparts, including using `this` to refer to the underlying value. ### Regularity * Rules and syntax should be consistent across all different member kinds, generic arities etc. * Implementation limitations or compatibility constraints shouldn't be materially felt in the user level feature experience. * There should be just one right way to declare a given extension member. The design has exactly one syntactic location for each of the extension type name, underlying types with accompanying type parameters, additional parameter info and the extension members themselves. ### Efficiency * Extension declarations shouldn't result in an unnecessarily large number of types being generated. * Extension member invocations should not incur hidden penalties, allocations or copying. The design represents anonymous extension declaration as nested static classes which are merged as much as generic arity and constraints allow. Extension members are represented as static members with an extra parameter for the underlying value, using any additional parameter info in that parameter's declaration. Extension member invocation is just invocation of those static members. ## Detailed design ### Declaration Extensions are declared inside top-level non-generic static classes, just like extension methods today, and can thus coexist with classic extension methods and non-extension static members: ``` c# public static class Enumerable { // New extension declaration extension(IEnumerable) { ... } // Classic extension method public static IEnumerable Cast(this IEnumerable source) { ... } // Non-extension member public static IEnumerable Range(int start, int count) { ... } } ``` An extension declaration is anonymous, and provides a specification of an underlying type with any associated type parameters and ref kinds, followed by a set of extension member declarations: ``` c# public static class Enumerable { extension(IEnumerable) // extension members for IEnumerable { public bool IsEmpty { get { ... } } } extension(IEnumerable) // extension members for IEnumerable { public IEnumerable Where(Func predicate) { ... } public IEnumerable Select(Func selector) { ... } public static IEnumerable operator +(IEnumerable e1, IEnumerable e2) { ... } } } ``` The extension member declarations are syntactically identical to corresponding instance and static members in class and struct declarations. Instance members refer to the underlying value with the keyword `this`: ``` c# public static class Enumerable { extension(IEnumerable) { // 'this' refers to underlying value public bool IsEmpty => !this.GetEnumerator().MoveNext(); } } ``` As in instance members, the reference to `this` can be implicit: ``` c# public static class Enumerable { extension(IEnumerable) { // implicit 'this.GetEnumerator()' public bool IsEmpty => !GetEnumerator().MoveNext(); } } ``` By default, the underlying value is passed to instance extension members by value, but an extension declaration can explicitly specify a different ref kind, as long as the underlying type is known to be a value type: ``` c# public static class Bits { extension(ref ulong) // underlying value is passed by ref { public bool this[int index] { get => (this & Mask(index)) != 0; set => this = value ? this | Mask(index) : this & ~Mask(index); // mutates underlying value } } static ulong Mask(int index) => 1ul << index; } ``` Underlying types in extension declarations can be or contain nullable reference types. If underlying types vary by nullability, separate extension declarations are needed. Individual extension members can place attributes on the implicit `this` parameter by using the `param` target in an attribute placed on the member itself. `param` is not currently allowed in this position, but is already used elsewhere in C# to refer to implicit parameters: ``` c# public static class NullableExtensions { extension(string?) { public string AsNotNull => this is null ? "" : this; [param:NotNullWhen(false)] public bool IsNullOrEmpty => this is null or []; } extension (T) where T : class? { [param:NotNull] public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(this); } } ``` ### Lowering Extension declarations give rise to static classes nested within the enclosing static class. These inner static classes are generated with unspeakable names and do not pollute the namespace of the enclosing static class. Within a given enclosing static class, extension declarations with the same number of type parameters and equivalent constraints are merged into a single static class even if they differ on underlying type, ref-kind or type parameter names. Thus the minimum number of nested classes are generated to represent the combinations of type parameters and constraints present across all the extension declarations within the enclosing static class: ``` c# public static class MyExtensions { extension(Span) where TSource : class? { ... } extension(Span) where TElement : class { ... } } ``` Because the signatures are sufficiently equivalent, this is lowered to just one class: ``` c# public static class MyExtensions { public static class __E1<__T1> where __T1 : class { ... } } ``` The requirement is not unlike that between the two parts of a partial method in today's C#. The members themselves are generated as static members. For instance members, insofar as it is possible to represent in IL, the same kind of member is generated, but with an extra first parameter for the underlying value. Where not possible, the body is represented as one or two (for accessors) static methods, and attributes are emitted to record their original member kind. The generated first parameter includes any ref kind specified in the extension declaration, and any attributes on the member which have the `param` target specifier: ``` c# public static class NullableExtensions { extension (T) where T : class? { [param:NotNull] public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(this); } } ``` Generates: ``` c# public static class NullableExtensions { public static class __E1<__T1> where T1__ : class { public static void ThrowIfNull([NotNull] __this) => ArgumentNullException.ThrowIfNull(__this); } } ``` ### Checking __Inferrability:__ The underlying type of an extension declaration must make use of all the type parameters of that extension declaration, so that it is always possible to infer the type arguments when applied to the underlying type. __Uniqueness:__ Within a given enclosing static class, the set of extension member declarations with the same underlying type (modulo identity conversion and type parameter name substitution) are treated as a single declaration space similar to the members within a class or struct declaration, and are subject to the same [rules about uniqueness](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#153-class-members). The uniqueness rule also applies across classic extension methods within the same static class, where any type parameter list is used unchanged, the `this` parameter is used to determine the underlying type, and the remaining parameters are used for the method signature. ``` c# public static class MyExtensions { extension(IEnumerable) // Error! T1 not inferrable { ... } extension(IEnumerable) { public bool IsEmpty { get ... } } extension(IEnumerable?) { public bool IsEmpty { get ... } // Error! Duplicate declaration } } ``` ### Lookup and disambiguation When an extension member lookup is attempted, all extension declarations within static classes that are `using`-imported contribute their members as candidates, regardless of underlying type. Only as part of resolution are candidates with incompatible underlying types discarded. A full generic type inference is attempted between the type of a receiver and any type parameters in the underlying type. The inferrability and uniqueness rules mean that the name of the enclosing static type is sufficient to disambiguate between extension members on a given underlying type. As a strawman, consider `E @ T` as a disambiguation syntax meaning on a given expression `E` begin member lookup for an immediately enclosing expression in type `T`. For instance: ``` c# string[] strings = ...; var query = (strings @ Enumerable).Where(s => s.Length > 10); public static class Enumerable { extension(IEnumerable) { public IEnumerable Where(Func predicate) { ... } } } ``` Means lookup `Where` in the type `Enumerable` with `strings` as its underlying value. A type argument for `T` can now be inferred from the type of `strings` using standard generic type inference. A similar approach also works for types: `T1 @ T2` means on a given type `T1` begin static member lookup for an immediately enclosing expression in type `T2`. This disambiguation approach should work not only for new extension members but also for classic extension methods. Note that this is not a proposal for a specific disambiguation syntax; it is only meant to illustrate how the inferrability and uniqueness rules enable disambiguation without having to explicitly specify type arguments for an extension declaration's type parameters. ## Drawbacks ## Alternatives ### Avoiding nesting for simple cases The proposed design avoids a lot of repetition, but does end up with extension members being nested two-deep in a static class _and_ and extension declaration. Two kinds of short-hand syntax are possible: __Merge static class and extension declarations:__ When a static class contains only a singe extension declaration and nothing else, allow it to be abbreviated to a top-level extension declaration _with_ a name: ``` c# public extension(IEnumerable) Enumerable { public bool IsEmpty => !GetEnumerator().MoveNext(); } ``` This ends up looking more like what we've been calling a "type-based" approach, where the container for extension members is itself named. However, the nesting would still be applied in the generated output. __Merge extension declaration and extension member:__ When an extension declaration contains only one member, allow the `{ ... }` curlies to be omitted around that member: ``` c# public static class Bits { extension(ref ulong) public bool this[int index] { get => (this & Mask(index)) != 0; set => this = value ? this | Mask(index) : this & ~Mask(index); // mutates underlying value } static ulong Mask(int index) => 1ul << index; } ``` ``` c# public static class Enumerable { extension(IEnumerable) public IEnumerable Where(Func predicate) { ... } } ``` This ends up looking more like what we've been calling a "member-based" approach, where each extension member contains its own details about the underlying type. However, in the generated output the type parameter and underlying type would still be applied to a generated nested class, not the member itself. ### Embracing compatibility Allowing full compatibility for existing extension methods to be ported to new syntax was an explicit non-goal for this proposal, but in the end it does come quite close to being able to embrace such compatibility. an instance extension method in this syntax has everything it needs to generate an equivalent classic extension method - except a parameter name for the `this` parameter. Why is this necessary? Because existing callers of the extension method as a static method may pass the `this` argument by name! ``` c# public static class Enumerable { extension(IEnumerable) { [GenerateStaticMethod("source")] public IEnumerable Where(Func predicate) { foreach (var e in this){ ... } } [GenerateStaticMethod("source")] public IEnumerable Select(Func selector) { foreach (var e in this){ ... } } } } ``` Type parameters from the extension declaration and the method declaration would be concatenated. In principle there are some classic extension methods that couldn't be expressed like this: ones where the type parameters used in the underlying type aren't first in the type parameter list: ``` c# public static IEnumerable WeirdSelect( this IEnumerable source, Func selector) { ... } ``` I don't think I've ever seen an extension method like that! But any such method would have to stay in the classic syntax. ### Member-based lowering strategy The proposed lowering strategy merges extension declarations into anonymous static classes under the hood. This helps reduce the amount of transformation that needs to happen on individual members - properties can stay properties, and so on. However, it does lead to challenges: - How do we generate stable names for these anonymous classes? - This would be the first time we generate "unspeakable" public types. How does the ecosystem manage that - other languages, documentation, etc.? As an alternative, we could generate extension members as static methods directly on the enclosing static class. Any member from a generic extension declaration would need to be turned into one or two generic methods with mangled names, and metadata would need to be generated to track how it all belongs together in extension declarations. All in all, members would be much more heavily transformed, but we would avoid the extra layer of anonymous static classes. ## Unresolved questions - How stable can we make the generated names of merged static classes, and how much does it matter? ================================================ FILE: meetings/working-groups/extensions/compat-mode-in-extensions.md ================================================ # Compat mode in extensions _This document is very much work in progress!_ There are several ways in which the new extension members design causes potential compat breaks, in the sense that a classic extension method being upgraded to the new syntax might break existing callers. The "compat mode" is an attempt at addressing these. ## Syntax Compat mode is activated by using the `this` modifier on a receiver parameter: ``` c# public static class Enumerable { extension(this IEnumerable source) // `this` means compat mode { ... } } ``` ## Objective A design goal of compat mode is that any instance extension method within the extension declaration either a) behaves in a fully compatible way with the corresponding classic extension method, or b) yields a compile time error if that is not possible. Thus, if an existing classic extension method is ported to the new syntax with compat mode on, it will either be disallowed or guaranteed to continue to work as before. ## Semantics in compat mode The following are areas where classic and new extension methods are known to differ. Compat mode addresses each of those differences. More may be added as we discover them. There are currently no examples of giving errors. ### Static methods Classic extension methods are static methods on the enclosing static class, and they may be invoked as such. In compat mode, a static method is generated by the compiler, using: - the attributes, accessibility, return type, name and body of the declared instance extension method, - a type parameter list concatenated from the type parameters of the extension declaration and the extension method, in that order, - a parameter list concatenated from the receiver parameter of the extension declaration and the parameter list of the extension method, in that order. ``` c# public static class Enumerable { extension(this IEnumerable source) // Generate compatible extension methods { public IEnumerable Where(Func predicate) { ... } public IEnumerable Select(Func selector) { ... } } } ``` Generates ``` c# public static class Enumerable { public static IEnumerable Where(this IEnumerable source, Func predicate) { ... } public static IEnumerable Select(this IEnumerable source, Func selector) { ... } } ``` ### Type arguments When type arguments are explicitly given to a classic extension method, they must correspond to all type parameters, including those that are used in the receiver type. By contrast, with new extensions any type parameters on the extension declaration are inferred from the receiver, and type arguments on invocation correspond only to those declared on the extension method itself. In compat mode, type argument lists are first matched against the full type parameter list generated in the corresponding static method. Only if no applicable such methods are found is an attempt made using only the type parameter list from the extension method itself. Given: ``` c# public static class Enumerable { extension(this IEnumerable source) { public IEnumerable Select(Func selector) { ... } } ``` The call `myList.Select(...)` would provide type arguments for `TSource` and `TResult`, foregoing the separate inference of `TSource` from the type of `myList`. The call `myList.Select(...)` would still be allowed, and if it isn't resolved to another extension method in the first round, would provide a type argument for `TResult` in the above `Select` method, with `TSource` being inferred from the type of `myList`. ### Type inference When type arguments are inferred for a given classic extension method, any argument may impact the inference of any type parameter. By contrast, with new extensions type arguments for the extension declaration type parameters are inferred from the receiver, whereas arguments to the extension method may only impact type arguments for the extension method's own type parameters. In compat mode, type arguments are inferred using the generated static method signature, passing the receiver as the first argument. ``` c# void M(I i, out object o) { i.M1(out o); // infers E.M1 i.M2(out o); // error CS1503: Argument 1: cannot convert from 'out object' to 'out string' i.M3(out o); // infers E.M3 } public static class E { public static void M1(this I i, out T t) { ... } extension(I i) { public void M2(out T t) { ... } } extension(this I i) // Compat mode { public void M3(out T t) { ... } } } public interface I { } ``` ### Inferrability New extension declarations have an inferrability requirement which dictates that any type parameter on the extension declaration occurs at least once in the receiver type. This allows type arguments for those type parameters to be inferred solely from the type of the receiver. However, this prevents certain classic extension methods from being ported to the new syntax, if their type parameters are not in the right order to have those that occur in the receiver type come first in the type parameter list. In compat mode, the inferrability requirement is waived. This means that type parameters can occur on the extension declaration itself without occurring in the receiver type. For any classic extension method there is therefore the option of porting it to an extension declaration that contains all its type parameters (or as many as needed), instead of leaving them on the extension method declaration. Making use of this option to have uninferrable type arguments on the extension declaration does introduce other limitations: - Only instance extension methods can be declared in the the extension declaration - Explicit type arguments will only be matched to the full argument list of the generated static method, not subsequently to only those declared in the extension method ``` c# public static class E { public static IEnumerable Select1(this IEnumerable source, Func selector) { ... } extension(this IEnumerable source) // TResult not used { public static IEnumerable Select2(Func selector) { ... } } } ``` ## Alternatives ### No explicit compat mode Some of the above effects of compat mode might be acceptable for all extension declarations, as they would have little negative effect on the user experience as seen from the perspective of the new extension feature. Perhaps a decent compromise and simplification would be to not have an explicit compat mode, adopt some of its behaviors for all extension members and live with the hopefully small breaking changes caused by abandoning the rest of those behaviors. Based on currently known scenarios, the two first features - calling extension methods as static methods and passing all type arguments to extension methods - seem likely to be common. Both seem reasonable to support for all new extension methods, as long as the ability to pass only the method's own type arguments is also preserved. Allowing extension method arguments to influence inference of extension declaration level type arguments seems like a much more contrived scenario. From a user perspective it seems reasonable that we could quietly support it, although the implementation cost - or the degree of complification to the spec - may not be so reasonable. However, even if we give the advice to keep the extension method in classic form, we then have to make two different type inference approaches - the old and the new - coexist side by side and figure out how to make them work together. Having type parameters "out of order" so that ones that are not used in the `this`-parameter precede ones that are also seems vanishingly rare. We can probably live with not having those be portable to the new syntax. ================================================ FILE: meetings/working-groups/extensions/compromise-design-for-extensions.md ================================================ # Compromise design for extensions ## Summary [summary]: #summary This proposal mixes the type- and member-based approaches to extension declarations: It combines a type-based approach for all member kinds with a member-based syntax for member-specific annotations and backwards-compatible extension method declarations. ## Motivation [motivation]: #motivation We're currently facing the conundrum that the type-based approach to extensions does not provide a compatible way for existing extension methods to move forward into new syntax, whereas the member-based approach imposes a high syntactic burden on new extension members (both methods and others) that do not need such compatibility. This proposal tries to bring the best of the two approaches together without creating too many different ways of doing the same thing. At its core it uses the type-based approach, where type parameters and a parameter-like "for-clause" with the receiver type are specified on the extension type declaration. Non-method extension members cannot declare type parameters. Extension members _can_ specify for-clauses of their own, but, with one exception, only to add additional annotations such as attributes and ref-ness modifiers that are specific to how this particular member interacts with the receiver. The one exception is "compatible extension methods". These are extension methods that _do_ override the for-clause of the extension declaration. Such extension methods generate static methods that are guaranteed to be fully source and binary compatible with corresponding classic extension methods. While the proposal encompasses core tenets of both type-based and member-based proposals, it also loses some expressiveness from both. Compared to pure member-based proposals, it doesn't allow the general grouping of extension members with different receiver types into the same extension declaration. There isn't a proposal on the table that achieves that without raising the as-yet unaddressed issue of type parameters and type arguments for member kinds other than members. On the type-based side the proposal loses the proposed implicit "this-style" parameter passing mode, which automatically treats the receiver as a value parameter when it is of a reference type, and as a reference parameter when it is of a value type. While some explicit version of this may be possible to add, doing it implicitly likely introduces too much of an arbitrary behavior difference with classic extension methods. ## Detailed design [design]: #detailed-design There are many variations on what a merged syntax could look like. This is one attempt, but pretty much anything is debatable! ``` antlr extension_declaration : attributes? extension_modifier* 'partial'? 'extension' identifier type_parameter_list? for_clause? type_parameter_constraints_clause* extension_body ';'? ; extension_modifier : 'new' | 'public' | 'protected' | 'internal' | 'private' | 'sealed' | 'static' | unsafe_modifier // unsafe code support ; for_clause : 'for' attributes? extension_mode_modifier? type ; extension_mode_modifier: | 'ref' | 'ref readonly' | 'in' ; extension_body : '{' extension_member_declaration* '}' ; extension_member_declaration : constant_declaration | field_declaration | extension_method_declaration | extension_property_declaration | extension_event_declaration | extension_indexer_declaration | operator_declaration | constructor_declaration | static_constructor_declaration | type_declaration ; extension_method_declaration : attributes? method_modifiers return_type extension_method_header method_body | attributes? ref_method_modifiers ref_kind ref_return_type extension_method_header ref_method_body ; extension_method_header : member_name '(' formal_parameter_list? ')' method_for_clause? | member_name type_parameter_list '(' formal_parameter_list? ')' method_for_clause? type_parameter_constraints_clause* ; method_for_clause : for_clause identifier? ; extension_property_declaration : attributes? property_modifier* type member_name for_clause? property_body | attributes? property_modifier* ref_kind type member_name for_clause? ref_property_body ; extension_event_declaration : attributes? event_modifier* 'event' type member_name for_clause? '{' event_accessor_declarations '}' ; extension_indexer_declaration : attributes? indexer_modifier* indexer_declarator for_clause? indexer_body | attributes? indexer_modifier* ref_kind indexer_declarator for_clause? ref_indexer_body ; ``` An `extension_method_declaration` with a `for_clause` that includes an `identifier` is called a "_compatible extension method_." The following syntactic restrictions apply to `extension_declaration`s: - `field_declaration`s must be `static`. - `extension_member_declaration`s may not have a `new`, `protected`, `virtual`, `sealed`, `override` or `abstract` modifier. - `extension_member_declaration`s with a `static` modifier may not have a `for_clause`. - Compatible extension methods may not occur within a generic `extension_declaration`. - An `extension_declaration` may omit its `for_clause` only if all its member declarations are compatible extension methods. - An accessor body of a non-static `extension_property_declaration` may not be `;` and may not use `field` keyword. The `for_clause` of any `extension_member_declaration` other than a compatible extension method must specify a `type` that is identity-convertible to that of the enclosing extension declaration's `for_clause`. The `identifier` of a compatible extension method's `method_for_clause` may not be referenced within the method body, but is added to the local variable declaration space of the method body to prevent other declarations from using it. Within the bodies of all non-static `extension_member_declaration`s, the receiver is referred to by the keyword `this`. Compatible extension method declarations implicitly generate a static method with the same signature, except that the `method_for_clause` is used as an additional first parameter. Within the body of the generated method, implicit and explicit occurrences of `this` are replaced with the identifier from the `method_for_clause`. Other extension member declarations do not implicitly generate additional members that are visible at the language level. Their lowering is an implementation detail, and does not affect language level semantics. The `extension_mode_modifier` is interpreted the same way as a `parameter_mode_modifier`, and determines the parameter passing mode of `this` in the body of non-static extension members. There is no proposed way to specify the "mixed" parameter passing mode that applies to `this` within instance members in classes (by value) and structs (by ref). This proposal is for declaration syntax and its meaning. It does not take a stance on lookup, type inference and overload resolution, but can likely accommodate most variations under discussion. ## Examples ``` c# // Different kinds of extension members public extension E for C { // Instance members - assume f is an accessible instance field on C public string P { get => f; set => f = value.Trim(); } // Property public T M() where T : IParsable => T.Parse(f, default); // Method public char this[int index] => f[index]; // Indexer public C(string f) => this.f = f; // Constructor // Static members public static int ff = 0; // Static field public static int PP { get; set => field = Abs(value); } // Static property public static C MM(string s) => new C(s); // Static method public static C operator +(C c1, C c2) => c1.f + c2.f; // Operator public static implicit operator C(string s) => new C(s); // UD conversion } // Type- and member-level for-clauses for attributes, nullability and ref-ness public extension NullableStringExtensions for string? { public bool IsNullOrEmpty for [NotNullWhen(false)] string? => this is null or []; public string AsNotNull => this is null ? "" : this; public void MakeNotNull for [NotNull] ref string? => this ??= ""; } // Core LINQ methods as a non-compatible generic extension public extension Enumerable for IEnumerable { public IEnumerable Where(Func predicate) { ... } public IEnumerable Select(Func selector) { ... } } // Core LINQ methods as compatible extension methods public extension Enumerable { public IEnumerable Select(Func selector) for IEnumerable source { ... } public IEnumerable Where(Func predicate) for IEnumerable source { ... } } ``` ## Drawbacks [drawbacks]: #drawbacks - As a compromise, this proposal may be less principled and conceptually clear to users. - The proposal does not allow grouping of extension members with different receiver types (except for compatible extension methods). The same is true for any proposal that puts type parameters on the extension declaration itself. - The proposal takes a parameter-like view of the underlying type, and therefore does not allow - let alone default to - a "this-style" mixed parameter passing modes for the receiver that depends on whether the receiver is a reference or a value type. ## Alternatives [alternatives]: #alternatives There are many possible variations on the details of the proposed syntax, restrictions and semantics. Here are a few: - For-clauses are syntactically identical between the type and the member levels. This underscores their "parameter-ness" and supports the notion that member-level clauses just override type-level ones. Another approach would be to let the type-level for-clause only specify a type, and leave any other detail to member-level ones. - Compatible extension methods have a subtle syntactic marker in the form of an identifier - a parameter name - on their for-clause. This attempts to strike a balance of making them blend in with the new syntax, while still giving the user a way to express their intent. One could argue for removing this distinction, e.g. by allowing parameter names on all for-clauses. Determining whether a method is compatible would then depend on syntactic and semantic context. At the other end, the distinction could be made more pronounced, e.g. by putting parentheses around the for-clause elements of a compatible extension method. - Non-static extension member bodies refer to the receiver with the keyword `this`. We could make receivers even more parameter-like by letting them have a parameter name as part of the for-clause, and then using that name in the bodies. This would be more verbose of course, both because of the added parameter names, and because bodies cannot make use of implicit `this.` to refer to fellow members with simple names. - Even compatible extension methods, which do have parameter names in this proposal, use `this` in the body. That choice was made for greater syntactic uniformity, but one could argue that compatible extension methods should be more like classic extension methods in this regard. - The proposal disallows member-level specification of the receiver type (except to modify a type-level one for nullability etc.) for non-method members on the grounds that, since those member kinds can't have type parameters, they can't specify open generic types as receiver types in their for-clauses, and then it's cleaner that they can't specify receiver types at all. That line could be drawn more leniently to allow closed types. About 25% of current extension methods are on open generic types. ## Unresolved questions [unresolved]: #unresolved-questions This proposal is only about declaration syntax and its meaning. There are many decisions, especially around lookup, type inference and overload resolution, that lie ahead. Since those felt largely orthogonal - or at least secondary - to the syntax, they've been left for later. ================================================ FILE: meetings/working-groups/extensions/content-based-naming.md ================================================ # Content Based Naming ## Terminology An extension member in source will result in two members in metadata: 1. The implementation member: this is the method generated into the type which contains the extension block declaration and used at runtime. 2. The declaration member: this is the method generated for the purposes of the compiler to support build and design time capabilities of extension members. The declaration type is the type that contains the declaration members. The marker method is the method in a declaration type which is used to recover the original C# declaration semantics of the extension block parameter. ## Summary The current metadata strategy for declaration members relies on ordinals to create declaration types from extension blocks. The use of ordinals causes friction in a few parts of the C# ecosystem: - Edit and Continue: any use of ordinals tied to source ordering means that adding or removing elements can create unnecessary, and possibly unresolvable, conflicts during ENC operations. Natural operations like adding a new extension block in the middle of two others can lead to renaming of existing blocks which is difficult to reconcile. - Public API Tracking: the declaration methods are necessarily generated as `public` and there are many tools in the .NET ecosystem that track `public` method usage. The current design means moving extension blocks around in source can observably change the set of `public` API in an assembly. Shipping with this design would mean this ecosystem of tools would need to change to treat this `public` API as _special_ in some way. - CREF generate an observable reference to declaration methods via `inheritdoc`. This means source reordering can break any place CREFs are treated as a durable item. This proposal wants to revamp our approach to declaration type and method metadata such that the following goals are achieved: 1. The declaration type and set of declaration and marker that will be generated for a given extension member can be determined by looking at solely that extension member. There is no need to consider other members in the same extension block, `partial` declarations in the containing type, etc ... 2. Declaration types and methods are treated like `public`. This proposal accepts that declaration types and members will be observable by design and build time processes. This means they need to have, as much as possible, the same characteristics as other `public` API. Specifically the following types of actions should **not** break runtime binary compatibility for `public` members in a declaration type: 1. Reordering extension blocks or members in source code 2. Changing C# specific aspects of the type like adding / removing nullable annotations, changing tuple names, etc ... 3. The C# compiler should be able to fully rehydrate the original extension paramater from metadata. This means for each declaration member the compiler can recreate the _exact_ type name, ref-ness, type parameter names, parameter name, etc... that was written in source. This will be achieved by moving to a content based naming scheme for declaration types and members. ## Detailed Design This proposal relies heavily on using content based naming for declaration types and members. The proposal is going to focus on what items are included in the content name, it will not discuss the actual name produced. That is because the specific hashing algorithm used will be an implementation detail of the compiler. For an extension block the content name of the declaration type will be determined by using the following parts of the extension block declaration: - The fully qualified CLR name of the type. This will include namespaces, type constraints, named constraints (like `new`). - Type parameter names will be normalized to `T0`, `T1`, etc ... based on the order they appear in the type declaration. This means an extension block with a type parameter `Dictionary` will result in the fully qualified name being `System.Collections.Generic.Dictionary`. - The fully qualified name will not include the containing assembly. It is common for types to be moved between assemblies and that should not break the public API. - Constraints will included and sorted such that reordering them in source code does not change the name. Specifically: - Type parameter constraints will be listed in declaration order. The constraints for the Nth type parameter will occur before the Nth+1 type parameter. - Base type and interface constraints will be sorted by comparing the full names ordinally - Non-type constraints will be sorted by comparing the C# text ordinally - This will not include any C# isms like tuple names, nullability, etc ... This will achieve the goal that the name of declaration types will only change when the underlying CLR type of an extension block parameter changes. Any change to C# parts of the type such as adding nullable annotations or tuple names will impact the declaration type name. The marker method for an extension block will be a `private` method in the declaration type. This method will have a single parameter which mirrors the extension block `this` parameter. Specifically it will have the same attributes, ref declaration, parameter name and C# type. The name of this marker method will be a content name that is determined by using the following parts of the extension block parameter declaration: - The fully qualified C# name of the type. This will include items like nullable annotations, tuple names, etc ... The constraints will have the same ordering as CLR types for the marker type name. - The name of the extension `this` parameter - The ref-ness of the extension `this` parameter - The fully qualified name + attribute arguments for any attributes applied to the extension `this` parameter. For every member in an extension block, the generated declaration member will have an attribute which contains the name of the marker method that represents the original extension `this` parameter. This allows the compiler to fully rehydrate the C# extension `this` parameter for any declaration member. Here is an example of how this approach would look in real code: ```cs class E { extension(IEnumerable source) { public bool IsEmpty => !source.Any(); public int Count => sourec.Count(); } extension(ref IEnumerable p) { public bool AnyNull => p.Any(x => x == null); public bool NullToEmpty() => this ??= []; } extension(IEnumerable source) where T : IEquatable { public bool IsPresent(U value) => source.Any(x => x.Equals(value)); } } ``` This would generate the following: ```cs class E { public class <>ContentName_For_IEnumerable_T { private void <>ContentName1(IEnumerable source) { } private void <>ContentName2(ref IEnumerable p) { } [MarkerMethodName("ContentName1")] public bool IsEmpty => throw null!; [MarkerMethodName("ContentName1")] public int Count => throw null!; [MarkerMethodName("ContentName2")] public bool AnyNull => throw null!; [MarkerMethodName("ContentName2")] public bool NullToEmpty() => throw null!; } public class <>ContentName_For_IEnumerable_T_With_Constraint where U : IEquatable { private void <>ContentName3(IEnumerable source) { } [MarkerMethodName("ContentName3")] public static bool IsPresent(U value) => throw null!; } } ``` This approach will result in stable names for our declaration types and members that will make it much more consumable for the existing C# ecosystem. The content hash algorithm will be left as an implementation detail to the compiler. The _recommendation_ is picking a hash that has the following properties: 1. Resilient to collisions from common elements in C# type names. 2. Has a bounded length on generated names as unbounded names at scale could negatively contribute to metadata limitations on names. The only _requirement_ of the hash is that it cannot be a cryptographic hash. Because this is a non-cryptographic hash the compiler will be responsible for doing collision detection. Specifically: - When the extension block parameter type for two extension blocks have different CLR types but produce the same declaration type name an error must be produced. - When the extension block parameter type for two extension blocks have different C# types but produce the same marker method name an error must be produced. This design will result in the following restrictions for extension blocks: - All extension blocks that map to the same declaration type must have type parameters with the same name. This mirrors the existing restrictions that we have for `partial` types. - All extension blocks which generate a declaration type name X must refer to the same CLR type. Essentially there cannot be two extension blocks in the same container which map to the same declaration type but different CLR types for the extension parameter. This is not resolvable with `extern alias`. Instead such extension block declarations must be put into different containing types. - Changing constraints on an extension block will result in breaking changes for existing CREF in the ecosystem ## Alternatives ### Reduce scope of constraint breaking changes to non-methods The biggest challenge with constraints and breaking changes is that for member types like properties the only place constraints can exist is on the type. That means changing constraints means that constraints on the containing type must change. That, combined with other requirements of the design, force the name of the marker type to change as constraints change. This problem does not _inherently_ exist for methods. Those can declare their own type parameters and hence be independent of the type parameter / constraints on the containing type. This means that we could do the following to make constraint changes for extension methods only non-breaking. Extension blocks would generate into two marker types: 1. A declaration type which contained all extension members that were methods. This declaration type would be non-generic. Type parameters on the original extension type would be copied to each declaration method. 2. A declaration type which contained all extension members that were not methods. This declaration type would be as previously described in this document. This design has a few downsides: - Significantly increases the complexity of the compiler implementation. - Yes it does reduce scope of breaking changes but at the expense of creating a decoder ring for understanding the subtleties of what does / doesn't break. The working group does not feel the extra complexity is justified by the limited relief of CREF breaking changes. ### Emit everything as methods in the declaration types As mentioned above when dealing with methods it's easier to avoid breaking changes around constraints. One strategy could be to simply emit everything in an extension block as a method and move all the type parameters to these methods. For example: when emitting the declaration member for a property named `Example` declared in an extension block we could find a strategy where: 1. Pick a naming scheme that identifies it as property like prefix with `<>Property_Example` 2. Emit the accessors with a naming scheme like `<>Property_Example_get` The exact details would be involved but we could identify a naming scheme that let us map back and forth to the original property name. It would increase the complexity of the compiler implementation but it seems reasonable we could create a name mapping scheme here. This design though would require us to generate _invalid_ metadata. For example we'd need to map attributes from the property declaration to the generated method. In the case these were marked as `AttributeTargets.Property` that would be invalid. This is a case where the binary would execute as the CLR does not verify attribute targets are correct but it is likely that it would cause friction in the ecosystem for tools that assume such target are correct. That is the biggest issue with this approach. There are several aspects like this where there is no clean mapping. Also there a lot of items like this were are likely missing. We'd need to go through every aspect of metadata and language, find every part that is specific to properties and rationalize them with this approach. This is why the working group discarded this idea (previously and in the context of this specific discussion). ### Remove the matching type parameter name restriction The design could be altered such that the restriction on all extension blocks that map to the same marker type must have type parameters with the same name is removed. This would roughly mean that the code generation would do the following: 1. Type parameter names would be normalized to `T0`, `T1`, etc ... in the marker type name. 2. Type parameter names would be part of the source type marker method content hash. 3. An attribute would be generated on the source type method that contains the original type parameter names. This would allow us to remove this restriction but at the extra cost to the compiler code generation strategy. ## Miscellaneous ### Why not use a Cryptographic Hash? The generated names must be the same through the lifetime of this feature in C#. This is not compatible with any use of cryptography which must be agile. Specifically the compiler must assume there is a future where SHA-256, the current defacto crypto algorithm, will be broken and banned in applications. That would leave the compiler in a place where it's using a banned cryptographic algorithm. This is just not compatible with our current security posture. The only downside to using a non-cryptographic hashing algorithm is that the compiler cannot take uniqueness for granted. It instead must be verified during compilation time to ensure there are no accidental collisions which is straight forward to implement in the compiler. ## Open Questions ### Type Parameter Names This design normalizes type parameter names to `T0`, `T1`, etc ... The original names are encoded in an attribute on the source type method. Need to validate from the compiler team if this is a workable solution for rehydrating the original type parameter names. If this is not viable then we will need to consider adding the following restriction: - All extension blocks that map to the same declaration type must have type parameters with the same name. At a glance this may seem like an unreasonable restriction but there is precedence as this is what we require for `partial` types today. ### Categorizing the breaking change Changes to the source code that result in only changes to the declaration types are a new kind of breaking change. Let's consider concretely a case where a constraint on a type parameter is removed. ```cs // Before class E { extension(C source) where T : IDisposable { public void M() { } } } // After class E { extension(C source) { public void M() { } } } ``` This source change will only result in a change to the declaration types. That means it is **not** a runtime binary breaking change as that would bind against the implementation methods. This change to the declaration type is also not a source breaking change (yes modifying constraints can break source but there is nothing about the declaration types / methods itself that are used in source). This is a breaking change for the following items: - CREF: changing the declaration type / method can break generated CREF in the wild. These would be fixed upon recompilation against the new assembly. - Design Time: the Roslyn API can be used to observe the declaration types, CREF being one case. Time should be taken to better outline and categorize these type of breaks so teams like .NET libraries can make informed decisions about changing aspects of declaration types between releases. ### The MarkerMethodName name This proposal uses `[MarkerMethodName]` as the attribute to map between the declaration method and its marker method. This name is a place holder and likely a better name should be considered. ================================================ FILE: meetings/working-groups/extensions/disambiguation-syntax-examples.md ================================================ ## Declarations ``` c# public static class MyExtensions { extension(IEnumerable source) { public bool IsEmpty => !source.Any(); public IEnumerable Where(Func predicate) => ...; public T this[int index] => source.ElementAt(index); public static IEnumerable Empty => []; public static implicit operator ReadOnlySpan(IEnumerable sequence) => sequence.ToArray().AsSpan(); } extension(IEnumerable source) { public IEnumerable(int element, int count) => Enumerable.Repeat(element, count); public static IEnumerable Range(int start, int count) => Enumerable.Range(start, count); public static IEnumerable operator +(IEnumerable sequence, int value) => sequence.Select(i => i + value); } } ``` ## Example code ``` c# // Static extension members var range = IEnumerable.Range(0, 10); var empty = IEnumerable.Empty; range += 5; ReadOnlySpan span = range; // Instance extension members var query = range.Where(i => i < 10); var isEmpty = query.IsEmpty; var first = query[0]; var repetition = new IEnumerable(first, 10); ``` ## Qualified members ``` c# // Static extension members var range = IEnumerable.(MyExtensions.Range)(0, 10); var empty = IEnumerable.(MyExtensions.Empty); range (MyExtensions.+=) 5; /* or */ (MyExtensions.+)(range, 5); ReadOnlySpan span = (MyExtensions.implicit)(range); // Instance extension members var query = range.(MyExtensions.Where)(i => i < 10); var isEmpty = query.(MyExtensions.IsEmpty); var first = query.(MyExtensions.this)[0]; var repetition = (MyExtensions.new) IEnumerable(first, 10); ``` - Hard to come up with consistent approach to qualifying different members ## Cast-operator ``` c# // Static extension members var range = ((MyExtensions)IEnumerable).Range(0, 10); var empty = ((MyExtensions)IEnumerable).Empty; (MyExtensions)range += 5; ReadOnlySpan span = (MyExtensions)range; // Instance extension members var query = ((MyExtensions)range).Where(i => i < 10); var isEmpty = ((MyExtensions)query).IsEmpty; var first = ((MyExtensions)query)[0]; var repetition = new ((MyExtensions)IEnumerable)(first, 10); ``` - Casting a type - Casting to a non-type - Casting LHS of compound assignment ## Invocation syntax ``` c# // Static extension members var range = MyExtensions(IEnumerable).Range(0, 10); var empty = MyExtensions(IEnumerable).Empty; MyExtensions(range) += 5; ReadOnlySpan span = MyExtensions(range); // Instance extension members var query = MyExtensions(range).Where(i => i < 10); var isEmpty = MyExtensions(query).IsEmpty; var first = MyExtensions(query)[0]; var repetition = new MyExtensions(IEnumerable)(first, 10); ``` - Matches the parameter syntax for receiver types - Invoking a type ## Indexing syntax ``` c# // Static extension members var range = MyExtensions[IEnumerable].Range(0, 10); var empty = MyExtensions[IEnumerable].Empty; MyExtensions[range] += 5; ReadOnlySpan span = MyExtensions[range]; // Instance extension members var query = MyExtensions[range].Where(i => i < 10); var isEmpty = MyExtensions[query].IsEmpty; var first = MyExtensions[query][0]; var repetition = new MyExtensions[IEnumerable](first, 10); ``` - Indexing a type ## As-operator ``` c# // Static extension members var range = (IEnumerable as MyExtensions).Range(0, 10); var empty = (IEnumerable as MyExtensions).Empty; (range as MyExtensions) += 5; ReadOnlySpan span = range as MyExtensions; // Instance extension members var query = (range as MyExtensions).Where(i => i < 10); var isEmpty = (query as MyExtensions).IsEmpty; var first = (query as MyExtensions)[0]; var repetition = new (IEnumerable as MyExtensions)(first, 10); ``` - `as` on a type - `as` a non-type - `as` on LHS of compound assignment ## At-operator ``` c# // Static extension members var range = (IEnumerable at MyExtensions).Range(0, 10); var empty = (IEnumerable at MyExtensions).Empty; (range at MyExtensions) += 5; ReadOnlySpan span = range at MyExtensions; // Instance extension members var query = (range at MyExtensions).Where(i => i < 10); var isEmpty = (query at MyExtensions).IsEmpty; var first = (query at MyExtensions)[0]; var repetition = new (IEnumerable at MyExtensions)(first, 10); ``` ## @-operator ``` c# // Static extension members var range = (IEnumerable @ MyExtensions).Range(0, 10); var empty = (IEnumerable @ MyExtensions).Empty; range @ MyExtensions += 5; ReadOnlySpan span = range @ MyExtensions; // Instance extension members var query = (range @ MyExtensions).Where(i => i < 10); var isEmpty = (query @ MyExtensions).IsEmpty; var first = (query @ MyExtensions)[0]; var repetition = new (IEnumerable @ MyExtensions)(first, 10); ``` ## In-operator ``` c# // Static extension members var range = (IEnumerable in MyExtensions).Range(0, 10); var empty = (IEnumerable in MyExtensions).Empty; (range in MyExtensions) += 5; ReadOnlySpan span = range in MyExtensions; // Instance extension members var query = (range in MyExtensions).Where(i => i < 10); var isEmpty = (query in MyExtensions).IsEmpty; var first = (query in MyExtensions)[0]; var repetition = new (IEnumerable in MyExtensions)(first, 10); ``` ## Using-operator ``` c# // Static extension members var range = (IEnumerable using MyExtensions).Range(0, 10); var empty = (IEnumerable using MyExtensions).Empty; (range using MyExtensions) += 5; ReadOnlySpan span = range using MyExtensions; // Instance extension members var query = (range using MyExtensions).Where(i => i < 10); var isEmpty = (query using MyExtensions).IsEmpty; var first = (query using MyExtensions)[0]; var repetition = new (IEnumerable using MyExtensions)(first, 10); ``` ## Speakable lowering ``` c# // Static extension members var range = MyExtensions.Range(0, 10); var empty = MyExtensions.Empty; range = MyExtensions.op_addition(range, 5); ReadOnlySpan span = MyExtensions.op_implicit(range); // Need target typing? // Instance extension members var query = MyExtensions.Where(range, i => i < 10); var isEmpty = MyExtensions.get_IsEmpty(query); var first = MyExtensions.get_Item(query, 0); var repetition = MyExtensions.__ctor_IEnumerable(first, 10); ``` ## Using clauses as statements ``` c# using static MyExtensions { // Static extension members var range = IEnumerable.Range(0, 10); var empty = IEnumerable.Empty; range += 5; ReadOnlySpan span = range; // Instance extension members var query = range.Where(i => i < 10); var isEmpty = query.IsEmpty; var first = query[0]; var repetition = new IEnumerable(first, 10); } ``` - Doesn't address bypassing instance member ``` c# // Static extension members var range = MyExtensions.(IEnumerable).Range(0, 10); var empty = MyExtensions.(IEnumerable).Empty; MyExtensions.(range) += 5; ReadOnlySpan span = MyExtensions.(range); // Instance extension members var query = MyExtensions.(range).Where(i => i < 10); var isEmpty = MyExtensions.(query).IsEmpty; var first = MyExtensions.(query)[0]; var repetition = new MyExtensions.(IEnumerable)(first, 10); ``` ``` c# // Static extension members var range = IEnumerable.(MyExtensions).Range(0, 10); var empty = IEnumerable.(MyExtensions).Empty; range.(MyExtensions) += 5; ReadOnlySpan span = range.(MyExtensions); // Instance extension members var query = range.(MyExtensions).Where(i => i < 10); var isEmpty = query.(MyExtensions).IsEmpty; var first = query.(MyExtensions)[0]; var repetition = new IEnumerable.(MyExtensions)(first, 10); ``` ``` c# // Static extension members var range = IEnumerable.as(MyExtensions).Range(0, 10); var empty = IEnumerable.as(MyExtensions).Empty; range.as(MyExtensions) += 5; ReadOnlySpan span = range.as(MyExtensions); // Instance extension members var query = range.as(MyExtensions).Where(i => i < 10); var isEmpty = query.as(MyExtensions).IsEmpty; var first = query.as(MyExtensions)[0]; var repetition = new IEnumerable.as(MyExtensions)(first, 10); ``` ``` c# // Static extension members var range = IEnumerable.MyExtensions.Range(0, 10); var empty = IEnumerable.MyExtensions.Empty; range.MyExtensions += 5; ReadOnlySpan span = range.MyExtensions; // Instance extension members var query = range.MyExtensions.Where(i => i < 10); var isEmpty = query.MyExtensions.IsEmpty; var first = query.MyExtensions[0]; var repetition = new IEnumerable.MyExtensions(first, 10); ``` - Doesn't allow giving the full name of `MyExtensions` ``` c# // Static extension members var range = IEnumerable.in(MyExtensions).Range(0, 10); var empty = IEnumerable.in(MyExtensions).Empty; range.in(MyExtensions) += 5; ReadOnlySpan span = range.in(MyExtensions); // Instance extension members var query = range.in(MyExtensions).Where(i => i < 10); var isEmpty = query.in(MyExtensions).IsEmpty; var first = query.in(MyExtensions)[0]; var repetition = new IEnumerable.in(MyExtensions)(first, 10); ``` ``` c# // Static extension members var range = IEnumerable.at(MyExtensions).Range(0, 10); var empty = IEnumerable.at(MyExtensions).Empty; range.at(MyExtensions) += 5; ReadOnlySpan span = range.at(MyExtensions); // Instance extension members var query = range.at(MyExtensions).Where(i => i < 10); var isEmpty = query.at(MyExtensions).IsEmpty; var first = query.at(MyExtensions)[0]; var repetition = new IEnumerable.at(MyExtensions)(first, 10); ``` ================================================ FILE: meetings/working-groups/extensions/extending-extensions-a-guide-to-relaxation.md ================================================ # Extending Extensions: A Guide to Relaxation ## Summary This proposal aims to describe a series of possible relaxations to the design presented by [anonymous extension declarations](https://github.com/dotnet/csharplang/blob/ac07118129334241b6a1dfa53a7e8c715b9ec0b1/meetings/working-groups/extensions/anonymous-extension-declarations.md). Because that document lays out key design principles that this proposal builds upon, it should be considered required reading for this one. ## Motivation The anonymous extension declarations proposal provides an excellent solution to many of the challenges described in [the design space for extensions](https://github.com/dotnet/csharplang/blob/ac07118129334241b6a1dfa53a7e8c715b9ec0b1/meetings/working-groups/extensions/the-design-space-for-extensions.md). By introducing the concept of an "extension declaration" declared anonymously within a static class to serve as a grouping for extension members, it elegantly avoids the "type explosion" problem that has plagued other designs. This new design ensures that extension members can be declared with a simple, familiar syntax while removing any rough edges that would complicate consumption. The design is based on a set of assumptions that are not necessarily agreed upon, but impose restrictions that bring clarity and simplicity to the design space. It is the belief of this author that some of these assumptions will not be true for all and the feature will feel restrictive in its current form. Generally, the restrictions will be felt by those declaring extension members, not those consuming extension members. Classic extension methods offer a tremendous amount of flexibility with regard to the declaration and organization of methods. In contrast, anonymous extension declarations design imposes limits that may serve as speed bumps to existing extension method authors. It is the goal of this proposal to identify opportunities to relax some of those restrictions with the goal of providing a similar level of flexibility for anonymous extension declarations that classic extension methods already enjoy. This is approach is not unlike C# records. Positional records provide a simple and straightforward syntax that offer type authors a broad set of functionality. However, during the design of records, it was realized that type authors needed ways to customize records in various ways. And so, many features were introduced for type authors to serve as "knobs" when declaring records, such as init-only properties, Deconstruct methods, value-based equality customization, required properties, mutable and immutable struct-based records, etc. ## Detailed design ### Optional parameter name on an extension declaration The anonymous extension declarations proposal makes the following assumption: > - Parameter names for underlying values aren't important and people will resent the forced verbosity of having to specify them. While this leads to a clear design, that assertion will not be true for all. Parameter names provide important value to a member’s signature and the code within. - Parameter names are an important form of self-documentation when the type system isn’t expressive enough to clearly state the author's intent. For example, it is helpful when a parameter of type `Person` has the parameter name `teacher`. This is a clear indicator to the caller what sort of `Person` to pass and helps make the code within the member more readable. - Parameter names can synergize semantically with other parameter names. For example, the parameter name `source` might be linked semantically to another parameter named `destination`. - Parameter names can be used to disambiguate overloads by applying a named argument. - Parameter names are important for XML documentation. Our own API documentation for the classic extension methods defined on [`System.Linq.Enumerable`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable) often have different documentation for the `source` parameter. In addition, parameters are referenced by name via the `` XML doc comment tag. By disallowing parameter names for an extension member’s "receiver parameter", programmers must use `this` to refer to the underlying value. However, in some cases, that may result in code that uses `this` in ways that are less natural to most users’ mental model of `this`. Consider the example taken from the anonymous extension declarations proposal below. ``` c# public static class Bits { extension(ref ulong) // underlying value is passed by ref { public bool this[int index] { get => (this & Mask(index)) != 0; set => this = value ? this | Mask(index) : this & ~Mask(index); // mutates underlying value } } static ulong Mask(int index) => 1ul << index; } ``` The code above uses `this` in a way that will be unfamiliar to most users. Most users aren’t aware that that `this` can be assigned within a mutable struct, and users who *are* aware largely view it as a bad practice. To relax this, consider an optional parameter name on the anonymous extension declaration. ``` c# public static class Bits { extension(ref ulong number) // underlying value is passed by ref and called "number" { public bool this[int index] { get => (number & Mask(index)) != 0; set => number = value ? number | Mask(index) : number & ~Mask(index); // mutates underlying value } } static ulong Mask(int index) => 1ul << index; } ``` The underlying type on the anonymous extension declaration corresponds strongly with a primary constructor, so it should be clear to the programmer that the parameter name is accessible with the extension members. Importantly, adding a parameter name would disable the ability to use `this` within an extension member body. Instead, the programmer must use the parameter name with all member bodies in the declaration. In addition, because `this` is no longer available, it won't be possible to reference `this` implicitly either. ``` c# public static class Enumerable { extension(IEnumerable source) { // Must use the parameter name to access the underlying value. public bool IsEmpty => !source.GetEnumerator().MoveNext(); // Removing the parameter name would make it possible to use this, implicitly and explicitly. // public bool IsEmpty => !this.GetEnumerator().MoveNext(); // public bool IsEmpty => !GetEnumerator().MoveNext(); } } ``` This proposed relaxation does not imply that attributes targeting the underlying value parameter would be moved to the extension declaration. Instead, they would continue to be declared on the parameter using the `param` attribute specifier just as they are in the base design. Unfortunately, adding an optional parameter name at the top of the extension declaration means there’s another axis that might force an extension author to need another declaration. If the author wants the `this` parameter name to change between extension members within the same underlying type, they’ll need a declaration for each variation of the parameter name. This can be solved with another relaxation. ### Mixing underlying types within an extension declaration While the anonymous extension declaration proposal protects the consumer from the "type explosion" problem, that problem is still very much alive for the extension author. The base proposal makes the following assumptions: > - Underlying types belong together with their type parameter declarations, and it would be confusing to separate them. > - People will resent the verbosity of having to repeat underlying types and accompanying type parameters for each member. These assumptions drive a design in which all members within an extension declaration must share the same underlying type for their `this` parameter, including nullable reference types. While it is possible that both of these assumptions may true, they will not be true for all. It _may_ be confusing to separate underlying types from the their type parameter declarations. However, that depends largely on the mental model a programmer develops for extensions. If they see extensions as a special form of type inheritance, it would indeed by confusing to separate type parameters from the type itself! However, if they view extensions as a new way to extension methods that allow for other members with a simpler syntax, they might not be so confused. It’s also true that people may resent having to repeat underlying types and accompanying type parameters for each member. However, if the first assumption above is relaxed to allow the underlying type to be separated from their type parameters, the second assumption becomes much more palatable. And, classic extension methods require that the underlying type be repeated for each method, and it is a massively popular feature. Consider the following classic extension methods from Roslyn’s public API: ```c# public static class CSharpExtensions { public static bool IsKind(this SyntaxToken token, SyntaxKind kind) => ...; public static bool IsKind(this SyntaxTrivia trivia, SyntaxKind kind) => ...; public static bool IsKind([NotNullWhen(true)] this SyntaxNode? node, SyntaxKind kind) => ...; public static bool IsKind(this SyntaxNodeOrToken nodeOrToken, SyntaxKind kind) => ...; public static bool ContainsDirective(this SyntaxNode node, SyntaxKind kind) => ...; } ``` If these were written using the base design, they would need to be declared across four extension declarations: ```c# public static class CSharpExtensions { extension(SyntaxToken) { public bool IsKind(SyntaxKind kind) => ...; } extension(SyntaxTrivia) { public bool IsKind(SyntaxKind kind) => ...; } extension(SyntaxNode?) { [param: NotNullWhen(true)] public bool IsKind(SyntaxKind kind) => ...; } extension(SyntaxNodeOrToken) { public bool IsKind(SyntaxKind kind) => ...; } extension(SyntaxNode) { public bool ContainsDirective(SyntaxKind kind) => ...; } } ``` It is this author’s belief that programmers will resent being forced to separate extension declarations by underlying type. This resentment might even deepen for programmers who realize that all of the extension declarations above would lower to the same nested static class. A common question might be, if the compiler generates extension declarations to the same type, why must they be declared in separate declarations? To relax this restriction, consider allowing the parenthesized underlying types to be moved to the member declarations. Making a correspondence with the reduced form of an extension method, the underlying types would be declared before the member names with a `.` token. ```c# public static class CSharpExtensions { extension { public bool (SyntaxToken).IsKind(SyntaxKind kind) => ...; public bool (SyntaxTrivia).IsKind(SyntaxKind kind) => ...; [param: NotNullWhen(true)] public bool (SyntaxNode?).IsKind(SyntaxKind kind) => ...; public bool (SyntaxNodeOrToken).IsKind(SyntaxKind kind) => ...; public bool (SyntaxNode).ContainsDirective(SyntaxKind kind) => ...; } } ``` This expansion to the base design provides a similar level of grouping flexibility that programmers enjoy with classic extension methods. In addition, the position chosen for a member-level underlying type works for all other extension member kinds, as well. ```c# public static class Extensions { extension { // instance extension property public bool (Digit).IsPrime => ...;     // instance extension indexer     public bool (Digit).this[int bit] => ...;     // instance extension event     public event EventHandler (Digit).BitFlipped     {     add => ...;     remove => ...;     } // static extension method public static int (int).FromBits(ReadOnlySpan bits) => ...; // static extension property public static Utf8StringComparer (StringComparer).OrdinalUtf8 => ...; // static extension event public static event EventHandler SystemEvents.NetworkConnected { add => ...; remove => ...; } // operator overloads     public static Digit operator +(Digit d) => ...;     public static Digit operator +(Digit d1, Digit d2) => ...;         // User-defined conversions     public static implicit operator byte(Digit d) => ...;     public static explicit operator Digit(byte b) => ...; } } ``` Interestingly, there’s no need to provide declare the underlying type for an operator overload or user-defined conversion. It should be implicit from the signature. It would still be possible to declare non-method extension members that use type parameters and constraints. However, such type parameters and constraints would go on the extension declaration. In addition, members within an extension declaration must use all type parameters on the declaration in order to be callable as an extension. ```c# public static class Extensions { extension { public bool (List).IsEmpty => this.Count == 0; } } ``` Finally, if an instance extension member is declared with its underlying type, the programmer can include a parameter name. ```c# public static class Extensions { extension { public bool (List list).IsEmpty => list.Count == 0; } } ``` ## Unresolved Questions - It seems that an extension member that declares its own own underlying type and doesn’t depend on an external type parameter could be declared directly in the body of a static class. Is this useful, or would it be confusing? - The base design uses a model that is arguably closer to extension members rather than extension types. After all, the "types" are fully anonymous and transparent. Given that, would it be helpful to rename the "extension" keyword to "extensions"? Does it matter? - The base design lowers all extension declarations to a nested static class, even if a declaration doesn’t declare any type parameters. If extension declarations are relaxed to allow the underlying type and parameter to be declared per member, it seems that binary compatibility with classic extension methods could be achieved if non-generic extension declarations lowered to generic members in the static class body. - If a programmer declares an underlying type on one member, this proposal implies that means that _all_ members within an extension declaration must declare their underlying type. Is it possible to allow both the extension declaration and members declare an underlying type? In this case, the member would win and "override" the extension declaration’s "default" underlying type. This might help avoid the "cliff" of switching the underlying type from the extension declaration to it’s members. Or would this be too confusing for authors? - Is there a way that an extension author could declare a parameter name per member without moving the underlying type? ================================================ FILE: meetings/working-groups/extensions/extension-member-disambiguation.md ================================================ # Extension member disambiguation It's possible for separate extensions to add identical extension members to the same underlying type. In those cases it's fairly tricky to pick one over the other. We've resolved to add a syntax to help with this, but we haven't decided what that syntax should be. Here are some requirements for a disambiguation syntax: - Usable with all kinds of extension members, not just those that use member access (`a.b`) - Specifies the static class where the desired extension member is declared The easiest type of syntax to imagine is one where a receiver or operand expression is augmented with a "start lookup here" annotation of some sort. An extension disambiguation syntax will also provide a way to target extension members that are otherwise hidden, e.g. by regular members or by other extension members that are "closer". ### Scope Extension members are not the only place in the language where it would be helpful to be able to specify where to begin lookup. For instance, interface members cannot be directly invoked on values of classes and structs that implement the interface, and, especially for structs, casting the receiver has impact on both semantics (copying) and performance (allocation). Again, workarounds are tedious. We should keep these broader scenarios in mind when selecting a syntax, even if we do not implement them yet beyond extensions. ### Existing or new Some features today, such as casts or `as`, come pretty close to addressing the problem. Could we just bend them to this new scenario? Risks when using an existing syntax include breaking changes and user confusion. But new syntax is a steeper price to pay. ## Candidates Shown here with extension property and extension operator examples. - **Cast**: `((MyExtensions)e).Prop`, `((MyExtensions)e1) + e2`. Existing syntax, may clash with existing uses or cause breaks. May seem odd since `MyExtensions` isn't really a type. - **As-operator**: `(e as MyExtensions).Prop`, `(e1 as MyExtensions) + e2`. Existing syntax, may clash with existing uses or cause breaks. May seem odd since `MyExtensions` isn't really a type. - **Invocation syntax**: `MyExtensions(e).Prop`, `MyExtensions(e1) + e2`. Existing syntax, but less likely to break. May clash with proposed feature of implicit `new` in object construction. May be a confusing syntax overload. But very short and can have high precedence (primary expression) minimizing extraneous parentheses. - **At-operator**: `(e at MyExtensions).Prop`, `(e1 at MyExtensions) + e2`. New syntax, analogous to `as`. Possibly slightly breaking in corner cases. - **@-operator**: `(e @ MyExtensions).Prop`, `(e1 @ MyExtensions) + e2`. New syntax, glyph version of `at`. Possibly slightly breaking in corner cases. - **In-operator**: `(e in MyExtensions).Prop`, `(e1 in MyExtensions) + e2`. New syntax, Existing token. Possibly slightly breaking in corner cases. - **Using operator**: `(e using MyExtensions).Prop`, `(e1 using MyExtensions) + e2`. New syntax, existing token. Bring using-clauses to mind, but may clash conceptually with using statements. Possibly slightly breaking in corner cases. - **Qualified member**: `e.(MyExtensions.Prop)`, `e1 (MyExtensions.+) e2`. Needs a syntax for denoting every single kind of (extension) member. What does that look like for e.g. indexers, constructors and conversion operators? - ... Let's get more proposals on the table and discuss pros and cons. ================================================ FILE: meetings/working-groups/extensions/extension-members-unified-proposal.md ================================================ # Extension members ## What's changed This proposal is an update on [Anonymous extension declarations](https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/extensions/anonymous-extension-declarations.md) in the following ways: - It leans into a parameter-style syntax for specifying the receiver. - It includes syntax for generating compatible extension methods. - It clarifies the goals of lowering, while leaving more details to implementation. ### Receiver specification: `this` vs parameter The strongest sentiment I've heard is that whichever approach we take should be done *consistently*. Of the two there's a lean towards the parameter approach. This proposal leans into the parameter-based approach as strongly as it can. ### Compatibility with classic extension methods There is a clear desire to be able to move classic extension methods forward into new syntax without breaking existing callers. This proposal lets extension methods be marked for compatibility, which will cause them to generate visible static methods in the pattern of classic extension methods. ### Lowering The proposal takes the stance that the declarations generated from lowering - static methods and/or types - should be hidden from the language level, making extension members a "real" abstraction. The implementation strategy needs to establish metadata and rules that can be followed and consumed by other compilers, and that ensure stability and compatibility for consuming code as APIs evolve. ## Declaration ### Static classes as extension containers Extensions are declared inside top-level non-generic static classes, just like extension methods today, and can thus coexist with classic extension methods and non-extension static members: ``` c# public static class Enumerable { // New extension declaration extension(IEnumerable source) { ... } // Classic extension method public static IEnumerable Cast(this IEnumerable source) { ... } // Non-extension member public static IEnumerable Range(int start, int count) { ... } } ``` ### Extension declarations An extension declaration is anonymous, and provides a _receiver specification_ with any associated type parameters and constraints, followed by a set of extension member declarations. The receiver specification may be in the form of a parameter, or - if only static extension members are declared - a type: ``` c# public static class Enumerable { extension(IEnumerable source) // extension members for IEnumerable { public bool IsEmpty { get { ... } } } extension(IEnumerable source) // extension members for IEnumerable { public IEnumerable Where(Func predicate) { ... } public IEnumerable Select(Func selector) { ... } } extension(IEnumerable) // static extension members for IEnumerable where TElement : INumber { public static IEnumerable operator +(IEnumerable first, IEnumerable second) { ... } } } ``` The type in the receiver specification is referred to as the _receiver type_ and the parameter name, if present, is referred to as the _receiver parameter_. ### Extension members Extension member declarations are syntactically identical to corresponding instance and static members in class and struct declarations (with the exception of constructors). Instance members refer to the receiver with the receiver parameter name: ``` c# public static class Enumerable { extension(IEnumerable source) { // 'source' refers to receiver public bool IsEmpty => !source.GetEnumerator().MoveNext(); } } ``` It is an error to specify an instance extension member (method, property, indexer or event) if the enclosing extension declaration does not specify a receiver parameter: ``` c# public static class Enumerable { extension(IEnumerable) // No parameter name { public bool IsEmpty => true; // Error: instance extension member not allowed } } ``` ### Refness By default the receiver is passed to instance extension members by value, just like other parameters. However, an extension declaration receiver in parameter form can specify `ref`, `ref readonly` and `in`, as long as the receiver type is known to be a value type. If `ref` is specified, an instance member or one of its accessors can be declared `readonly`, which prevents it from mutating the receiver: ``` c# public static class Bits { extension(ref ulong bits) // receiver is passed by ref { public bool this[int index] { set => bits = value ? bits | Mask(index) : bits & ~Mask(index); // mutates receiver readonly get => (bits & Mask(index)) != 0; // cannot mutate receiver } } static ulong Mask(int index) => 1ul << index; } ``` ### Nullability and attributes Receiver types can be or contain nullable reference types, and receiver specifications that are in the form of parameters can specify attributes: ``` c# public static class NullableExtensions { extension(string? text) { public string AsNotNull => text is null ? "" : text; } extension([NotNullWhen(false)] string? text) { public bool IsNullOrEmpty => text is null or []; } extension ([NotNull] T t) where T : class? { public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(t); } } ``` ### Compatible extension methods By default, extension members are lowered in such a way that the generated artifacts are not visible at the language level. However, if the receiver specification is in the form of a parameter and specifies the `this` modifier, then any extension instance methods in that extension declaration will generate visible classic extension methods. Specifically the generated static method has the attributes, modifiers and name of the declared extension method, as well as type parameter list, parameter list and constraints list concatenated from the extension declaration and the method declaration in that order: ``` c# public static class Enumerable { extension(this IEnumerable source) // Generate compatible extension methods { public IEnumerable Where(Func predicate) { ... } public IEnumerable Select(Func selector) { ... } } } ``` Generates: ``` c# public static class Enumerable { public static IEnumerable Where(this IEnumerable source, Func predicate) { ... } public static IEnumerable Select(this IEnumerable source, Func selector) { ... } } ``` This does not change anything else about how the declared extension method works. However, adding the `this` modifier may lead to a binary break for consumers because the generated artifact may change. ### Constructors Constructors are generally described as an instance member in C#, since their body has access to the newly created value through the `this` keyword. This does not work well for the parameter-based approach to instance extension members, though, since there is no prior value to pass in as a parameter. Instead, extension constructors work more like static factory methods. They are considered static members in the sense that they don't depend on a receiver parameter name. Their bodies need to explicitly create and return the construction result. The member itself is still declared with constructor syntax, but cannot have `this` or `base` initializers and does not rely on the receiver type having accessible constructors. This also means that extension constructors can be declared for types that have no constructors of their own, such as interfaces and enum types: ``` c# public static class Enumerable { extension(IEnumerable) { public static IEnumerable(int start, int count) => Range(start, count); } public static IEnumerable Range(int start, int count) { ... } } ``` Allows: ``` var range = new IEnumerable(1, 100); ``` ### Operators Although extension operators have explicit operand types, they still need to be declared within an extension declaration: ``` c# public static class Enumerable { extension(IEnumerable) where TElement : INumber { public static IEnumerable operator *(IEnumerable vector, TElement scalar) { ... } public static IEnumerable operator *(TElement scalar, IEnumerable vector) { ... } } } ``` This allows type parameters to be declared and inferred, and is analogous to how a regular user-defined operator must be declared within one of its operand types. ## Checking __Inferrability:__ All the type parameters of an extension declaration must be used in the receiver type. This makes it always possible to infer the type arguments when applied to a receiver of the given receiver type. __Uniqueness:__ Within a given enclosing static class, the set of extension member declarations with the same receiver type (modulo identity conversion and type parameter name substitution) are treated as a single declaration space similar to the members within a class or struct declaration, and are subject to the same [rules about uniqueness](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#153-class-members). ``` c# public static class MyExtensions { extension(IEnumerable) // Error! T1 not inferrable { ... } extension(IEnumerable) { public bool IsEmpty { get ... } } extension(IEnumerable?) { public bool IsEmpty { get ... } // Error! Duplicate declaration } } ``` The application of this uniqueness rule includes classic extension methods within the same static class. For the purposes of comparison with methods within extension declarations, the `this` parameter is treated as a receiver specification along with any type parameters mentioned in that receiver type, and the remaining type parameters and method parameters are used for the method signature: ``` c# public static class Enumerable { public static IEnumerable Cast(this IEnumerable source) { ... } extension(IEnumerable source) { IEnumerable Cast() { ... } // Error! Duplicate declaration } } ``` ## Consumption When an extension member lookup is attempted, all extension declarations within static classes that are `using`-imported contribute their members as candidates, regardless of receiver type. Only as part of resolution are candidates with incompatible receiver types discarded. A full generic type inference is attempted between the type of the actual receiver and any type parameters in the declared receiver type. The inferrability and uniqueness rules mean that the name of the enclosing static type is sufficient to disambiguate between extension members on a given receiver type. As a strawman, consider `E @ T` as a disambiguation syntax meaning on a given expression `E` begin member lookup for an immediately enclosing expression in type `T`. For instance: ``` c# string[] strings = ...; var query = (strings @ Enumerable).Where(s => s.Length > 10); public static class Enumerable { extension(IEnumerable) { public IEnumerable Where(Func predicate) { ... } } } ``` Means lookup `Where` in the type `Enumerable` with `strings` as its receiver. A type argument `string` can now be inferred for `T` from the type of `strings` using standard generic type inference. A similar approach also works for types: `T1 @ T2` means on a given type `T1` begin static member lookup for an immediately enclosing expression in type `T2`. This disambiguation approach should work not only for new extension members but also for classic extension methods. Note that this is not a proposal for a specific disambiguation syntax; it is only meant to illustrate how the inferrability and uniqueness rules enable disambiguation without having to explicitly specify type arguments for an extension declaration's type parameters. ## Lowering The lowering strategy for extension declarations is not a language level decision. However, beyond implementing the language semantics it must satisfy certain requirements: - The format of generated types, members and metadata should be clearly specified in all cases so that other compilers can consume and generate it. - The generated artifacts should be hidden from the language level not just of the new C# compiler but of any existing compiler that respects CLI rules (e.g. modreq's). - The generated artifacts should be stable, in the sense that reasonable later modifications should not break consumers who compiled against earlier versions. These requirements need more refinement as implementation progresses, and may need to be compromised in corner cases in order to allow for a reasonable implementation approach. ## Order of implementation We do not need to implement all of this design at once, but can approach it one or a few member kinds at a time. Based on known scenarios in our core libraries, we should work in the following order: 1. Properties and methods (instance and static) 2. Operators 3. Indexers (instance and static, may be done opportunistically at an earlier point) 4. Anything else ## Future work ### Disambiguation We still need to settle on a disambiguation syntax. Per the above it needs to be able to take a receiver expression or type, as well as the name of a static class from which to begin member lookup. However, the feature does not have to be extension-specific. There are several cases in C# where it's awkward to get to the right member of a given receiver. Casting often works, but can lead to boxing that may be too expensive or lead to mutations being lost. It would probably be unfortunate to ship extension members without a disambiguation syntax, so this has high priority. ### Shorter forms The proposed design avoids per-member repetition of receiver specifications, but does end up with extension members being nested two-deep in a static class _and_ and extension declaration. It will likely be common for static classes to contain only one extension declaration or for extension declarations to contain only one member, and it seems plausible for us to allow syntactic abbreviation of those cases. __Merge static class and extension declarations:__ ``` c# public static class EmptyExtensions : extension(IEnumerable source) { public bool IsEmpty => !source.GetEnumerator().MoveNext(); } ``` This ends up looking more like what we've been calling a "type-based" approach, where the container for extension members is itself named. __Merge extension declaration and extension member:__ ``` c# public static class Bits { extension(ref ulong bits) public bool this[int index] { get => (bits & Mask(index)) != 0; set => bits = value ? bits | Mask(index) : bits & ~Mask(index); } static ulong Mask(int index) => 1ul << index; } public static class Enumerable { extension(IEnumerable source) public IEnumerable Where(Func predicate) { ... } } ``` This ends up looking more like what we've been calling a "member-based" approach, where each extension member contains its own receiver specification. ================================================ FILE: meetings/working-groups/extensions/extensions-an-evolution-of-extension-methods.md ================================================ # Extensions: An Evolution of Extension Methods * [x] Proposed * [ ] Prototype * [ ] Implementation * [ ] Specification ## Summary [summary]: #summary This proposal presents a design for "extension everything" as an evolution of classic extension methods and allows for future expansion. ### Goals - Describe a new syntax for extension members that scales to member kinds beyond just instance methods. - Provide support for all functionality available to classic extension methods. - Guarantee binary and source compatibility for classic extension method scenarios in the new syntax. - Design support for the highest value extension member kinds that have the best chance of being implemented in the C# 14 timeframe. - Lay the design groundwork for further extension member kinds, if or when the scenarios are compelling enough to pursue. ### Non-Goals - Do not consider syntax for a possible future “roles” or "explicit extensions" feature. This proposal assumes that “extensions” and “roles” have no dependency on one another. However, it allows for future synergy through additional language features. - Do not consider syntax to allow type parameters on member kinds that do not support them today. Although, this could be supported by future work. ## Motivation [motivation]: #motivation [Extension methods](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) are wildly popular! They first appeared in C# 3 as a smallish feature intended to support the syntactic rewrites of LINQ’s query expression syntax[^1]. However, over the past 15+ years, extension methods have cemented themselves as a crucial part of the C# developer toolkit. A key reason for extension methods' popularity is the their inherent discoverability. Once a set of extension methods are brought into scope, they can be discovered in IntelliSense simply by typing `.` after an expression. This discoverability makes extension methods a powerful way to define important helpers within a code base or provide public API surface for a library. Extension methods have been used to simplify interface implementation[^2], as a tool to layer public API surface area[^3], and even to implement domain-specific languages (DSLs)[^4]. In the ten(!) C# releases since their introduction, extension methods have received some small improvements. For example, C# 6 added the `static` modifier for using directives, allowing a type’s static members to be brought into scope, including extension methods. C# 7.2 introduced support for `this ref` on extension methods targeting value types. However, in all that time, there haven’t been any new extension members kinds added to C#, though there has certainly been a consistent stream of requests for them. Over the years, the C# language design team has received many requests for *extension properties*. Initially, extension properties might seem straightforward but they present several challenges. The most glaring issue is that properties don’t have a parameter list, so there isn’t an obvious place to declare a `this` parameter. However, even if that were solved, C# properties cannot declare generic type parameters. Without a way to define a generic type parameter on an extension property, it would be impossible to declare an `IsEmpty` property for `IEnumerable`. And of course, once those issues are addressed (along with generic property type inference and the inevitable overload resolution work), why would we stop at extension properties? The next step would clearly be to add instance and static generic properties. Given that, extension properties trigger a bit of an avalanche of design issues that lead to a much larger C# feature that feels a bit niche and only saves the programmer a pair of empty parentheses. This has never seemed worth the investment. In addition, there a long-standing request from the .NET libraries team for *static extension methods*. Extension methods that are accessible through member access on a type open up new API composition scenarios. Imagine how powerful it would be to add a package reference to a .NET project and have new static methods accessible from `string` related to that package's domain! Unfortunately, a natural syntax to declare a static extension method that derives from classic extension method syntax has proven elusive. After all, extension methods are already declared as static methods. Would a static extension method be `static static` or maybe require an extra attribute? Also, the `this` parameter wouldn’t make sense, since a static extension method wouldn’t be passed an instance of the type. Then where would the target type go? Clearly, a new syntax is needed to support static extension methods but what happens to classic extension methods? Are there two radically different syntaxes? This proposal aims to solve these issues and more through a new declaration syntax specially tailored for extension members. ## Detailed design [design]: #detailed-design ### The Extension Container Extension members are declared in a new type declaration called an *extension container*. ```antlr extension_container_declaration : attributes? extension_container_modifier* 'partial'? 'extensions' identifier type_parameter_list? for_clause? type_parameter_constraints_clause* extension_container_body ';'? ; extension_container_modifier : 'public' | 'internal' | unsafe_modifier // unsafe code support ; for_clause : 'for' type ; extension_container_body : '{' extension-container-member* '}' ; extension-container-member : extension_member_declaration | class_member_declaration ; extension_member_declaration : extension_method_declaration | extension_property_declaration ; ``` Below are a few examples of extension containers: ```C# extensions E { } extensions E { } extensions E for string { } extensions E for T where T : IEquatable { } ``` An extension container compiles to a type that is similar to a [static class](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/classes#15224-static-classes), which is necessary for binary compatibility. It can't inherit from a base type, implement interfaces, and can only be referenced in the same ways that a static class can. Using the plural `extensions` as a keyword is an important distinction from other proposals that prefer `extension`. The `extensions` keyword makes it clear that this is a container for members and not an entity that a programmer generally needs to be concerned with, except for situations where disambiguation is required[^5]. Within an extension container body, there can be both extension members and regular static members (the same set that are supported by static classes). There is no restriction on the type that an extension member can target (i.e. the "receiver type") unless the programmer provides an optional _for-clause_. A _for-clause_ specifies a type that applies to all extension members declared within the container. If a _for-clause_ type references a type parameter (e.g. `IEnumerable`), that type parameter must be declared on the extension container[^6]. ### Instance Extension Methods The syntax used to declare a classic extension method starts with a static method on a static class and adds a `this` keyword to the first parameter that anoints it as the receiver type. From a conceptual point of view, the programmer sees an extension method for what it really is: a static method that can be reduced syntactically when invoked to appears as if it were an instance method. Here's an example of a set of classic extension methods from Roslyn: ```C# public static partial class Extensions { public static SourceTextContainer AsTextContainer(this ITextBuffer buffer) => TextBufferContainer.From(buffer); internal static TextLine AsTextLine(this ITextSnapshotLine line) => line.Snapshot.AsText().Lines[line.LineNumber]; } ``` The syntax for instance extension methods within an extension container take the opposite approach. They are declared as instance methods, but the receiver parameter is moved *before* the method name and no longer requires the `this` modifier. In this way, the receiver parameter is given more importance and the syntax looks similar to how it is expected to be invoked. ```C# public partial extensions Extensions { public SourceTextContainer (ITextBuffer buffer).AsTextContainer() => TextBufferContainer.From(buffer); internal TextLine (ITextSnapshotLine line).AsTextLine() => line.Snapshot.AsText().Lines[line.LineNumber]; } ``` When a _for-clause_ is included on the extension container, an instance extension method does not need to restate the type from the _for-clause_. And, if there aren't any attributes or modifiers, the parentheses aren't needed, making a syntactic connection to the single parameter form of a lambda expression. ```C# internal partial extensions ProjectExtensions for Project { public Document project.GetRequiredDocument(DocumentId documentId) => project.GetDocument(documentId) ?? throw new ...; public Document project.GetRequiredDocument(SyntaxTree tree) => project.GetDocument(tree) ?? throw new ...; public TextDocument project.GetRequiredAdditionalDocument(DocumentId documentId) => project.GetAdditionalDocument(documentId) ?? throw new ...; } ``` For compatibility, all of the examples above compile to the same metadata as their equivalent classic extension method syntax. > [!NOTE] > This proposal suggests a succinct syntax that allows just the receiver parameter name followed by `.`. There are > other possibilities called out [below](#unresolved-questions). The following grammar describes the syntax for an instance extension method declared in an extension container. ```antlr extension_method_declaration : attributes? method_modifiers return_type extension_method_header method_body | attributes? ref_method_modifiers ref_kind ref_return_type extension_method_header ref_method_body ; extension_method_header : receiver_parameter '.' member_name '(' parameter_list? ')' | receiver_parameter '.' member_name type_parameter_list '(' parameter_list? ')' type_parameter_constraints_clause* ; receiver_parameter : '(' attributes? receiver_mode_modifier? type? identifier ')' | identifier | type ; receiver_mode_modifier | 'ref' | 'ref readonly' | 'in' ``` ### Static Extension Methods The ability to declare a static extension method for a type is a long-standing ask from the .NET libraries team. This provides new API layering possibilities and could provide new avenues for offering APIs down-level. Consider the [`string.Create(...)` method](https://learn.microsoft.com/en-us/dotnet/api/system.string.create?view=net-8.0#system-string-create-1(system-int32-0-system-buffers-spanaction((system-char-0)))) that was added in .NET Core 2.1. ```C# public sealed partial class String { public static string Create(int length, TState state, SpanAction action) { } } ``` If static extension methods had been available, it would have been possible to define this method in a .NET package that included down-level support[^7] like so. ```C# public partial extensions Extensions { public static string string.Create(int length, TState state, SpanAction action) { } } ``` Like an instance extension method's receiver parameter, it is necessary to state the target type of the static extension method before the method name[^8]. This allows the declaration syntax to align with the calling syntax. A downside is that the type must be restated even if an optional _for-clause_ is defined, but this seems a small price to allow regular static members alongside static extension methods. ### Extension Properties The design for instance and static extension properties largely fall out of the design framework described used for extension methods above. ```antlr extension_property_declaration : attributes? property_modifier* type extension_property_header property_body | attributes? property_modifier* ref_kind type extension_property_header ref_property_body ; extension_property_header : receiver_parameter '.' name ; ``` Here are a couple of examples selecting from existing Roslyn extension methods that could be declared as extension properties. ```C# internal extensions IComparerExtensions for IComparer { public IComparer comparer.Inverse => new InverseComparer(comparer) } internal extensions ISymbolExtensions for ISymbol { public bool ([NotNullWhen(true)] ISymbol? symbol).IsImplicitValueParameter => ...; } internal extensions CompilationExtensions for Compilation { public INamedTypeSymbol? compilation.AttributeType => compilation.GetTypeByMetadataName(typeof(Attribute).FullName!); public INamedTypeSymbol? compilation.ExceptionType => compilation.GetTypeByMetadataName(typeof(Exception).FullName!); public INamedTypeSymbol? compilation.EqualityComparerOfTType => compilation.GetTypeByMetadataName(typeof(EqualityComparer<>).FullName!); public INamedTypeSymbol? compilation.ActionType => compilation.GetTypeByMetadataName(typeof(Action).FullName!); } ``` Static extension properties may prove to be less common as static properties are less common in general. However, there are still interesting cases! Consider the following extension method defined by the [Fluent Assertions library](https://fluentassertions.com/typesandmethods/). ```C# public static class AssertionExtensions { public static TypeAssertions Should(this Type subject) { return new TypeAssertions(subject); } } ``` That extension method is intended to be called like so: ``` C# typeof(MyBaseClass).Should().BeAbstract(); ``` If static extension properties were available when this library were defined, the Fluent Assertions DSL could have been designed to require less ceremony. ```C# public static class AssertionExtensions for T { public static TypeAssertions T.Should { return new TypeAssertions(typeof(T)); } } // Usage: MyBaseClass.Should.BeAbstract; ``` ### Inference For scenarios where the extension container doesn't declare a type parameter, existing type inference for extension methods should be sufficient. If the extension container does declare a type parameter, an additional inference step will be required to determine what extension containers apply for a given receiver. Consider the following code. ```C# var numbers = new List(); foreach (var text in numbers.ToFormattedStrings("x8")) { Console.WriteLine(text); } extensions EnumerableExtensions for IEnumerable where T : IFormattable { public IEnumerable source.ToFormattedStrings(string format) => source.Select(x => x.ToString(format, formatProvider: null)); } ``` In this example, the compiler would need to first determine that `EnumerableExtensions` is an appliable extension type for `List`. Then, applicable extension methods could be chosen and normal overload resolution would continue.[^9] ### Disambiguation For static extension methods and properties, disambiguation falls out. The programmer can simply call the member on the extension container directly. For instance extension methods, it is possible to disambiguate by using the same static invocation syntax as classic extension methods. However, it will be necessary to add a general unifying disambiguation syntax at the call site to account for other scenarios. Below are a few strawman proposals: #### Cast-style syntax Because an extension container is really a type, it seems reasonable to allow it to be used with a _cast-expression_ to disambiguate: ```C# ((Extensions)instance).Prop = 42; Console.WriteLine(((Extensions)instance).Prop); ``` #### Invocation-style syntax Since an extension container cannot have instance constructors, it seems reasonable to consider a syntax based on a normal invocation. ```C# Extensions(instance).Prop = 42; Console.WriteLine(Extensions(instance).Prop); ``` This _might_ have some problems to sort out, but it seems possible. #### Alias-qualified syntax An underused C# why to qualify C# types is the `::` operator. Currently, this can be used whenever the left-hand side is a namespace alias, extern alias, or the `global` alias. We could consider allowing the left-hand side to also be an extension container. ```C# Extensions::instance.Prop = 42; Console.WriteLine(Extensions::instance.Prop); ``` Currently, the left-hand side can identifier. To support this, that operator would need to allow a fully-qualified generic type name, which might also be alias-qualified. However, perhaps this idea might lead to others? ## Future Work ### More Member Kinds! The goal of this proposal was to cover instance and static methods and properties, providing a design framework that could be used to add other member kinds if the scenarios requiring them are important. Using a similar approach, it should not be too difficult to provide syntax for other member kinds. For example: ``` C# extensions E for int { public bool number.this[int bit] => ...; public event Action number.NonsenseEvent { add => ...; remove => ...; } public static event Action int.NonsenseEvent { add => ...; remove => ...; } public static operator +(int x, string y) => ...; public static implicit operator int(string x) => ...; } ``` Instance indexers are straightforward and would likely be useful. Events are possible, but similar to extension properties, they would require `add` and `remove` accessors to avoid creating state that wouldn't flow with the receiver. Note that operators overloads and user-defined conversions are declared using the same syntax as always. It is not legal to write an operator overload for an extension type, so the syntax is open to be used. However, one of operands must match the type in the _for-clause_. ### Roles and Interfaces! An alternate proposal for extensions explores a type-based approach in two related flavors: implicit and explicit extensions. Recently, these have been re-renamed back to "extensions" (implicit extensions) and "roles" (explicit extensions) to avoid confusion and might their conceptual differences clearer. This proposal provides a new design for "extensions" as an evolution of classic extension methods that is fully disconnected from "roles". However, it's still possible to bring some synergy back in the future if roles do become part of C#. If roles manifest as light weight wrappers around an instance of an underlying type and eventually allow interface implementation, it's possible that an extension container could leverage roles to provide a future "extension interface". This is bit hand-wavey it's unclear what shape roles will ultimately take, but there is at least a _possible_ universe where something like the following code could be made to work, possibly by generating an anonymous role under-the hood. ``` C# public extensions E for string : IDisposable { public void s.Dispose() { } } ``` The purpose here is not to promise anything or solve all of the potential issues (like ambiguity with explicit interface implementation). The intention is to show that there can still be a path to synergy between extensions, as presented by this proposal, and roles, if and when they become a part of the C# language. ## FAQ ### What about allowing `this` for member access? Other proposals allow the programmer to use `this` within an instance extension member body to refer to the receiver type. Additionally, unqualified member accesses implicitly binds to `this` to create the illusion that the user is really typing in an instance method inside of a type. Unfortunately, that approach is incongruent with classic extension methods and loses important semantic detail that might be provided by a name. For example, it's useful to know that the `string` being operated on is actually `articleText` and not just any `string`. In this proposal, it is assumed that extension methods are well-understood by C# programmers as fancy static methods. So unqualified member access in an extension member should has static access within the extension container, allowing access to all other static members and extension members. In addition, the programmer can always access the receiver parameter in an instance extension method by name. ### Can non-method extension members declare generic type members? No! As mentioned in the non-goals section, this proposal does not attempt to allow type parameters to be added to extension members that can't already support them. A key reason for this is that doing the work to support type parameters on, say, extension properties would be strange if weren't adding them for normal instance and static properties as well. And, doing that is well-beyond the scope of extensions. If we ever allow type parameters to be declared on regular properties, we should also allow them for extension properties. ## Drawbacks [drawbacks]: #drawbacks ## Alternatives [alternatives]: #alternatives ## Unresolved questions [unresolved]: #unresolved-questions - **Should the receiver parameter _always_ be parenthesized rather than allowing `.`**? This is definitely something to consider. It seems reasonable to parenthesize the receiver parameter and remove the '.' for instance extension members. ```C# extensions StringExtensions for string { public bool ([NotNullWhen(false)] string? s) IsNullOrEmpty => ...; public int (text) CountWord(string word) => ...; } ``` - **Should the receiver parameter _always_ be required to state the type**? This is related to the question above. To some, it might seem too irregular in a method declaration to declare a parameter with just the name and no type. To be more regular with other top-level declarations, the `.` for instance extension members as well. Merging that with the syntax above would look something like this: ```C# extensions StringExtensions for string { public bool ([NotNullWhen(false)] string? s) IsNullOrEmpty => ...; public int (string text) CountWord(string word) => ...; } ``` - **What syntax should be used for disambigation?** As shown [above](#Disambiguation), there are many syntactic possibilities for disambiguating an extension and this proposal only suggests a few. Conceptually, it feels like the invocation-style syntax aligns best for instance extensions by embracing the importance this design places on the receiver parameter. However, this is yet undecided. - **Can an extension container declare generic type parameters without a _for-clause_**? This should be feasible but would need a restriction that all extension members use all of the type parameters somewhere in their receiver parameter or remaining parameter list. Otherwise, it will not be possible to infer the type arguments for the extension container from the call site, making an extension member uncallable in reduced form. It seems reasonble to issue a warning if an instance extension member could would not be callable with instance syntax. ```C# extensions E { public void (IComparable obj) M1(); // Firne public int (string s) M2(IEnumerable items); // Fine public T M3(); // Works with disambiguation, but issue a warning. } ``` ## Design meetings [^1]: For example, the query expression, `from x in Enumerable.Range(1, 10) select x * x` is rewritten syntactically at compile-time as `Enumerable.Range(1, 10).Select(x => x * x)`. The `Enumerable.Select(...)` extension method ensure that this rewrite compiles. [^2]: Consider the [`ILogger`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger) interface, which only has three interface members that need to be implemented. A much larger API surface is available for an `ILogger` implementation by the 29 (as of this writing) extension methods defined by [`LoggerExtensions`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loggerextensions) in the same `Microsoft.Extensions.Logging` namespace. [^3]: Roslyn's public API uses extension methods to provide separate API sets for C# and Visual Basic across a common set of types. [^4]: For an example of a domain-specific language implemented almost entirely with extensions, consider [Fluent Assertions](https://fluentassertions.com/). [^5]: One possible expansion of this proposal would be to allow for nameless extension containers, though such extension containers would have no means of disambiguation. [^6]: Taking a cue from classic extension methods, this proposal assumes that an extension container can't be nested within another type. If that restriction is loosened, it would be possible for a _for-clause_ to reference a type parameter from an enclosing type. [^7]: Adding `string.Create(...)` as a static extension method might not have been advisable when it was introduced. The point made by this proposal is that it would have been _possible_. [^8]: This is syntactic similarity to explicitly-implemented interface members. However, since an extension container cannot implement interfaces, this approach does not introduce an ambiguity. [^9]: I'm pretty sure this is a *gross* oversimplification. `#notacsharpcompilerengineer` ================================================ FILE: meetings/working-groups/extensions/extensions-as-static-types.md ================================================ # Extensions as static types * [x] Proposed * [ ] Prototype: Not Started * [ ] Implementation: Not Started * [ ] Specification: Not Started ## Summary [summary]: #summary Disallow use of an extension as an instance type. Just like static classes this means that it cannot be the type of a value or variable, and cannot be a type parameter. ## Motivation [motivation]: #motivation A large part of the complexity of the roles and extensions pair of features comes from allowing them as types of values. For roles, that is the core of the feature; an indispensable part of the design. For extensions, however, their "typeness" is a more peripheral aspect, mainly to do with disambiguation. If we can live without those aspects of the extension feature, we can ship it faster and with less implementation complexity and risk. This does not prevent us from adding roles later, and in the process "upgrading" extensions to also be instance types. It allows us to further stratify the work across multiple waves of effort. ## Detailed design [design]: #detailed-design Separate out the design for extensions from that for roles. Disallow the use of extension types as the type of values and variables, including in variable and member declarations, cast expressions, and as type arguments. Inside extension declarations, change the type of `this` to be the underlying type rather than the extension type. Other members of the extension can still be accessed on (implicit or explicit) `this`, as they show up as extension members on the underlying type. ## Drawbacks [drawbacks]: #drawbacks ### The type of `this` in extension declarations In the current design, the type of `this` in an extension declaration is the extension type itself. This enables inheritance-like lookup behavior, where the members of the extension take precedence over the members of the underlying type. With `this` having the underlying type, that would no longer be the case - members of the underlying type would win over extension members, even ones from the enclosing declaration. This does not seem like a big loss in practice. Why would an extension declare a member that the underlying type would hide? Such an extension member would be effectively unusable, given the other restrictions of this proposal. In fact, such a declaration might warrant a warning: ``` c# public extension StringExtensions for string { public string Length => ...; // Useless - warning? public bool IsUtf8 => ... Length ...; // Would bind to string.Length } ``` It is still the case that the extension's members would compete on equal terms with other extensions and could clash with them. This is a special case of the more general ambiguity issue, that we will address next. In this particular case, ambiguities would be quite rare. If the other extension is imported with a `using` it would lose due to existing "closeness" rules. If it is defined on a base type, it would lose due to existing overload resolution rules. If we still think it is a problem, we could consider an additional closeness rule that an extension is closer than others inside its own declaration. ``` c# public extension E1 for string { public void M() => ...; } extension E2 for string { public void M() => ...; public void P => ... M() ...; // Ambiguity? Closeness rule? } ``` It would likely be a breaking change if at a later point (when extensions were allowed as instance types) we changed the type of `this` to the extension type. ### Disambiguation The other place where the type-ness of extensions plays a part in the current design is in disambiguation between members of two imported extensions. ``` c# using E1; // Brings in void M() on string; using E2; // Also brings in void M() on string; "Hello".M(); // Ambiguous ((E1)"Hello").M(); // No longer allowed ``` Given current rules, ambiguities like this would have to be handled by playing tricks with using clauses. E.g. putting `using` inside vs outside of the namespace (the one inside is nearer). Or defining a helper extension member in a separate file that only imports one of the extensions, and simply redirects to it: ``` c# // OtherFile.cs using E1; extension HelperExtensions for string { public void E1_M() => M(); } ``` We know from extension methods that ambiguities are rare, but do occur. If the above is not satisfactory, we could recognize very specific patterns in code (such as `((E1)"Hello").M()`) or consider new syntax for disambiguation. ## Alternatives [alternatives]: #alternatives These possible supplementary mitigating features were mentioned in the Drawbacks section: - A warning when an extension declares a member that would be hidden by a member of the underlying type - An additional closeness rule preferring members of a given extension within the declaration of that extension - Syntax or special rules to help with disambiguation between extension members ## Unresolved questions [unresolved]: #unresolved-questions ## Design meetings ================================================ FILE: meetings/working-groups/extensions/extensions-lookup.md ================================================ There are two parts to this proposal: 1. adjust how we find compatible substituted extension containers 2. align with current implementation of extension methods # Finding a compatible substituted extension container The proposal here is to look at `extension(receiverParameter)` like a method signature, and apply current [type inference](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1263-type-inference) and [receiver applicability](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128103-extension-method-invocations) rules to it, given the type of a receiver. The type inference step infers the extension type parameters (if possible). The applicability step tells us whether the extension works with the given receiver, using the applicability rules of `this` parameters. This can be applied both when the receiver is an instance or when it is a type. Re-using the existing type inference and conversion algorithm solves the variance problem we'd discussed in LDM. It makes this scenario work as desired: ```cs IEnumerable.M(); static class E { extension(IEnumerable) { public static void M() { } } } ``` Note: this change was made in the new extensions feature branch. # Aligning with implementation of classic extension methods Decision: we're resolving new instance extension members exactly like classic extension methods. The above should bring the behavior of new extensions very close to classic extensions. But there is still a small gap with the current implementation of classic extension methods, when arguments beyond the receiver are required for type inference of the type parameters on the extension container. The spec for classic extension methods specifies 2 phases (find candidates compatible with the receiver, then complete the overload resolution), but the implementation only has 1 phase (find all candidates and do overload resolution with all the arguments including one for the receiver value). Example we had [discussed](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-02.md#extensions): ```cs public class C { public void M(I i, out object o) { i.M(out o); // infers E.M i.M2(out o); // error CS1503: Argument 1: cannot convert from 'out object' to 'out string' } } public static class E { public static void M(this I i, out T t) { t = default; } extension(I i) { public void M2(out T t) { t = default; } } } public interface I { } ``` We have a few options: 1. Bend the implementation of new extension member lookup to work the same way as classic extensions (1-phase for invocation scenarios, 2-phases for others) We can either update the spec for classic extension methods, or document a spec deviation for both classic and new extension methods. 2. Introduce a subtle difference in behavior between new and old extension methods (and figure out how we then resolve when both kinds are candidates) This means that migrating from a classic extension method to a new extension method is not quite 100% compatible. 3. Make new and old extension methods both work the new way, thus breaking existing uses of existing extension methods Note: depending on this choice, still need to solve how to mix classic and new extension methods in invocation and function type scenarios. ## Details on option 1 (1 phase design to match implementation of classic extension methods) If we choose to do 1-phase lookup for invocation scenarios, we would: 1. we collect all the candidate methods (both classic extension methods and new ones, without excluding any extension containers) 2. we combine all the type parameters and the parameters into a single signature 3. we apply overload resolution to the resulting set The transformation at step 2 would take a method like the following: ```cs static class E { extension(receiverParameter) { void M(methodParameters); } } ``` and produce a signature like this: ``` static void M(this receiverParameter, methodParameters); ``` Note: for static scenarios, we would play the same trick as in the above section, where we take a type/static receiver and use it as an argument. Note: This approach solve the mixing question. # Overload resolution for extension methods Decision: we're going for maximum compatibility between new instance extension methods and classic extension methods. We previously concluded that we should prefer more specific extensions members. But classic extension methods don't follow that. Instead they use betterness rules ([better function member](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12643-better-function-member)). We remove less specific applicable candidates of instance methods (type-like behavior) ``` new Derived().M(new Derived()); // Derived.M public class Base { public void M(Derived d) { } } public class Derived : Base { public void M(Base b) { } } ``` [sharplab](https://sharplab.io/#v2:C4LglgNgPgdgpgdwAQBE4CcwDc4BMAUAlAHQCy+8yamOBhhA3EgPTOobZ5kCwAUHwAEAzEgEAmJACEAhgGc4SPgG8+SNaJECALEnLVOuJLkJIlSAL59L/XsNET9tJCClyFy1ervbd+GfKQAIxMza3MgA) But we rely on betterness for classic extension methods (parameter-like behavior) ``` "".M(); // E.M(string) public static class E { public static void M(this object o) { } public static void M(this string s) { } } ``` [sharplab](https://sharplab.io/#v2:C4LglgNgPgRDB0BZAFASgNwAID03MFElkABARgAZUBYAKFuIGZMyA2ZgJgM1oG9bMBzJq2YAWTCmAALMAGdMAewBGAKwCmAY2CLUmHpgC+/QY2ak2xcZJnyy5TLN36jNA0A=) ``` "".M(""); // ambiguous public static class E { public static void M(this object o, string s) { } public static void M(this string s, object o) { } } ``` [sharplab](https://sharplab.io/#v2:C4LglgNgPgRDB0BZAFHAlAbgAQHodYEMBbAIzAHMBXAe0oGcBYAKGYAEBmLVgRgDYuATFgCiWZgG9mWaV049+rACxYUwABZg6WaiQBWAUwDGwbQBou3AAxY6aLOKwBfKTI4WFy1Rq09rdczoGxtp2Ds5M4UA) Which should we do for new extension methods? We have competing goals: 1. we want to align extension methods with classic extension methods (portability/compat, parameter-like behaviors) and with instance methods (type-like behaviors) 2. we want to align other extension members (properties) with extension methods 3. we want to align instance and static scenarios Options for methods: 1. maximum compatibility with classic extension methods 2. maximum alignment with instance methods ## Extension methods proposal Gather candidates (no applicability involved) Pruning more candidates: 1. by type inference (including the type parameters on the extension declaration) 2. by applicability to arguments (including the extension parameter) 3. Remove based on inaccessible type arguments (apply, see RemoveInaccessibleTypeArguments) 4. Remove less specific or hidden applicable candidates (doesn't apply, see RemoveLessDerivedMembers and RemoveHiddenMembers) 5. Remove static-instance mismatches (apply) 6. Remove candidates with constraints violations (apply) Figure out best candidate: 1. Remove lower priority/ORPA members (apply) 2. Remove worse members (better function member) (including the receiver parameter) Note: the other pruning steps in overload resolution not sdon't apply to extension receiver parameter scenarios (RemoveDelegateConversionsWithWrongReturnType, RemoveCallingConventionMismatches, RemoveMethodsNotDeclaredStatic) # Resolution for static methods Decision: match what we do for instance methods Do we want the same semantics for static extension methods (ie. we pretend like we have a receiver/value of the given type) or do we want some new semantics? I assume that we want the old semantics. This also makes it clear what to expect in a "Color Color" scenario, where we don't know whether the receiver is an instance or static. # Resolution for properties Decision: match what we do for instance/static methods as long as it makes sense. In particular, the "prefer more specific" step won't apply to properties either. We'll want to revisit the details of betterness we want for properties. We're okay keeping the betterness step after the member kind determination, for now. We have similar questions for extension properties. We're going to cover three questions: - what kind of pruning should be applied? - what kind of betterness should be applied? - how should properties be resolved together with methods? ## Pruning candidates ### Prefer more specific Yes, we'd previously agree that we want to prefer more specific members. ```csharp _ = "".P; // should pick E(string).P public static class E { extension(object o) { public int P => throw null; } extension(string s) // more specific parameter type { public int P => 0; } } ``` ### Static/instance mismatch If we try to follow the behavior of regular instance or static methods, then the resolution of extension properties should prune based on static/instance mismatch: ```csharp _ = 42.P; static class E1 { extension(int i) { public int P => 0; } } static class E2 { extension(int) { public static int P => throw null; } } ``` ```csharp _ = int.P; static class E1 { extension(int i) { public int P => throw null; } } static class E2 { extension(int) { public static int P => 0; } } ``` ## Betterness ### Better conversion from expression ```csharp IEnumerable iEnumerableOfC2 = null; _ = iEnumerableOfC2.P; // should we prefer IEnumerable because it is a better conversion? (parameter-like behavior) public static class E { extension(IEnumerable i) { int P => 0; } extension(IEnumerable i) { int P => throw null; } } public class C1 { } public class C2 : C1 { } ``` ```csharp _ = IEnumerable.P; // should we prefer IEnumerable because it is a better conversion? (parameter-like behavior) public static class E { extension(IEnumerable) { static int P => 0; } extension(IEnumerable) { static int P => throw null; } } ``` [classic extension analog](https://sharplab.io/#v2:C4LglgNgPgAgDAAhgRgCwG4CwAoFBmAHgGEAmAPgTAQF4EA7AVwgi2xzADoBZACgEp0CAPRCEAUW498xZGT44cMPEmQA2JCXE4A3jgT6kylOpioEvYAAswAZxWEishAA8+CbQgC+eg0pUmzC2s7aQB7ACMAKwBTAGNgCld3LxxvXCNNR2S0vxhMzRAELI9PIA===) Should both extensions be applicable both when the receiver is an instance or a type? If yes, should we have some preference between those two? ### Prefer non-generic over generic If we follow the parameter-like behavior of classic extension methods, then we'd probably want more better member rules: ```csharp _ = 42.P; public static class E { extension(T t) { public int P => throw null; } extension(int i) // non-generic, so better function member { public int P => 0; } } ``` ### Prefer by-value parameter ```csharp _ = 42.P; public static class E { extension(in int i) { public int P => throw null; } extension(int i) // better parameter-passing mode { public int P => 0; } } ``` ### Issue with betterness in static scenarios But those betterness rules don't necessarily feel right when it comes to static extension methods: ```csharp int.M2(); public static class E1 { extension(in int i) { public static void M() { } } } public static class E2 { extension(int) { public static void M() { } } } ``` ## Resolving properties and methods together Following last LDM's decision to use old semantics for new extension methods, we have to find a new way to resolve properties and methods together. Previously, we would only gather candidates from compatible extensions, then we would decide the winning member kind and proceed with resolving (either overload resolution for methods, or picking the single property). Now that we're gathering all candidates (without regards to compatibility of extensions), we're thinking to delay the determination of the member kind. The process that we've brainstormed: 1. gather candidates 2. prune candidates by type inference and applicability to arguments 3. prune candidates by other rules (static/instance mismatch, prefer more specific, ...) 4. determine member kind If all the remaining candidates are methods, the member kind is method and we resolve to the best method. If the only remaining candidate is a property, the member kind is property and we resolve to that property. Otherwise we have an ambiguity. Note: I don't think there's a scenario for removing lower priority members based on ORPA here. ### Remove static/instance mismatches The following example illustrates the relevance of step 3 above, when we have a method and a property, but one has a static/instance mismatch. ```csharp object.M(); static class E1 { extension(object) { public static string M() => throw null; } } static class E2 { extension(object o) { public string M() => throw null; } } ``` ### Prefer more specific The following example illustrates the relevance of step 3 above, when we have a method and a property, but one is more specific. The problem is that we've decided not to apply the "more specific" pruning step to extension methods, for compatibility with classic extension methods. ```csharp string.M(); static class E1 { extension(string) { public static string M() => throw null; } } static class E2 { extension(object) { public static System.Action M => throw null; } } ``` ## Function types Note: the determination of function types starts with the applicable candidates, with candidates pruned, but some of the pruning rules weren't applicable (static/instance mismatch). I assume we'll want all possible applicable pruning steps to apply: ```csharp var x = C.M; // binds to static method public class C { } public static class E1 { extension(object) { public static void M() { } } extension(object o) { public void M(int i) { } } } ``` ================================================ FILE: meetings/working-groups/extensions/extensions_v2.md ================================================ # Modern Extensions * [x] Proposed * [ ] Prototype: Not Started * [ ] Implementation: Not Started * [ ] Specification: Not Started Many thanks to those who helped with this proposal. Esp. @jnm2! ## Summary [summary]: #summary Modern Extensions introduce a new syntax to produce "extension members", greatly expanding on the set of supported members including properties, static methods, operators and constructors (and more), in a clean and cohesive fashion. This new form subsumes C# 3's "classic extension methods", allowing migration to the new modern form in a semantically *identical* fashion (both at a source and ABI level). Note: this proposal is broken into two parts. A core kernel needed to initially ship with perfect forward and backwards compatibility with classic extension methods, and then potential enhancements on top of this kernel to make certain common designs more succinct and pleasant. This does not preclude the feature shipping with any or all of these enhancements at launch. It simply allows the design to be broken into separable concerns, with a clearer ordering of dependencies. A rough strawman of the syntax is as follows. In all cases, the extended type is shown to be generic, to indicate handling that complex case: Note: the strawman is not intended to be final form. That said, it is useful to see any proposed form with all members to ensure that it's generally comprehensible. For example, a form that is only good for properties, but not for other members, it likely not appropriate. ```c# extension E { // Instance method form, replaces `public static int M(this SomeType val, ...) { } public int M(...) for SomeType val { } // Property form: // Auto-properties, or usage of 'field' not allowed as extensions do not have instance state. public int Count for SomeType val { get { ... } } // Event form: // Note: would have to be the add/remove form. // field-backed events would not be possible as extensions do not have instance state. public event Action E for SomeType val { add { } remove { } } // Indexer form: public int this[int index] for SomeType val { get { ... } } // Operator form: // note: no SomeType val, an operator is static, so it is not passed an instance value. public static SomeTypeX operator+ (SomeType s1, SomeType s2) for SomeType { ... } // Conversion form: // note: no SomeType val, an operator is static, so it is not passed an instance value. public static implicit operator SomeTypeX(int i) for SomeType { ... } // Constructor form: // note: no SomeType val, an operator is static, so it is not passed an instance value. public SomeType() for SomeType { } // *Static* extension method (not possible today). Called as `Assert.Boo("", "")` // note: no `Assert val`, a static method is not passed an instance value. public static bool Boo(string s1, string s2) for Assert { } // Static extensions properties, indexers and events are all conceptually supportable. // Though we can decide which are sensible to have or not. // Static extensions can having backing static fields. Static extension properties can use `field`. // Nested types must be supported to maintain compatibility with existing static classes with extension members in them. } ``` Without specifying the full grammar changes, the intuition is that we are making the following changes: ```g4 for-clause | 'for' parameter ; parameter (no change) | attributes? modifiers? type identifier ... ; extension | attributes? modifiers? 'extension' identifier { member_declaration* } ; // For method/property/indexer/operator/constructor/event declarations // we are augmenting its syntax to allow type-parameters // (if not already allowed) and a for-clause. For example: property-declaration | attributes? modifiers identifier type-parameters for-clause property-body ; compilation-unit-member | ... | extension ; namespace-declaration-member | ... | extension ; ``` The use of `parameter` means all of the following are legal, with the same semantics that that classic extension methods have today: ```c# for ref Span span for ref readonly Span span for in Span span for scoped ref Span span for scoped Span span ``` Modern extensions continue to not allow adding fields or destructors to a type. Not all of these *new* extension member forms need be supported. For example, we may decide that a `static extension indexer` or `static extension event` is just too esoteric, and can be cut. The proposal shows them all though to demonstrate completeness of the idea. All *classic* forms must be supported of course. ## Migration and compatibility Given an existing static class with extensions, a straightforward *semantically identical* (both at the source and binary level) translation to modern extensions is done in the following fashion. ```c# // Existing style static class E { static TField field; static int Property => ... static void NonExtensionHelperMethod() { } static int ExtensionMethod(this string x, ...) { } static T GenericExtensionMethod(this U u, ...) { } } // New style extension E { // Non extensions stay exactly the same. static TField field; static int Property => ... // Note the lack of a 'for-clause'. This is a normal static method. // An *modern static extension method* will have a 'for-clause' on it static void NonExtensionHelperMethod() { } // Migrated *instance* extension members int ExtensionMethod(...) for string x { } T GenericExtensionMethod(...) for U u { } } ``` In other words, all existing extension methods drop `static` from their signature, and move their first parameter to a `for-clause` placed within the method header (currently strawmanned as after the parameter list). The strawman chooses this location as it already cleanly supports clauses, being where the type parameter constraint clauses already go. Note: the syntax of a `for-clause` is `'for' parameter`, allowing things like a parameter name to be specified. `parameter` is critical in this design to ensure the classic extension method `this` parameter can always cleanly move. The extension itself (E) will get emitted exactly as a static class would be that contains extension methods (allowing usage from older compilers and other languages without any updates post this mechanical translation). New extension members (beyond instance members) will need to have their metadata form decided on. Consumption from older compilers and different languages of these new members will be specified at a later point in time. A full example of this translation with a real world complex signature would be: ```c# static class Enumerable { public static TResult Sum(this IEnumerable source, Func selector) where TResult : struct, INumber where TAccumulator : struct, INumber { // Original body } } extension Enumerable { public TResult Sum(Func selector) for IEnumerable source where TResult : struct, INumber where TAccumulator : struct, INumber { // Exactly the same code as original body. } } ``` This form supports *non* extension static methods. For example `Enumerable.Range` would migrate like so: ```c# static class Enumerable { public static IEnumerable Range(int start, int count) { ... } } extension Enumerable { // Exact same signature. No 'for-clause'. public static IEnumerable Range(int start, int count) { ... } } ``` Perfect source and binary compatibility are goals here. That means any other features that work with static classes and extensions are expected to migrate over to extensions without change. This includes, but it not limited to other features like 'attributes'. For example, all attributes that might be present on a `static class` should migrate unchanged to an `extension`. Other features not mentioned here should not be inferred to not be part of this design. By default all features should continue being compatible, and only explicitly specified deviations should be allowed. ## Disambiguation Classic extension methods today can be disambiguated by falling back to static-invocation syntax. For example, if `x.Count()` is ambiguous, it is possible to switch to some form of `StaticClass.Count(x)` to call the desired method. A similar facility is needed for modern extension members. While the existing method-invocation-translation approach works fine for methods (where the receiver can be remapped to the first argument of the static extension method call), it is ungainly for these other extension forms. As an initial strawman this proposal suggests reusing `cast expression` syntax for disambiguation purposes. For example: ```c# var v1 = ((Extension)receiver).ExtensionMethod(); // instead of Extension.ExtensionMethod(receiver) var v2 = ((Extension)receiver).ExtensionProperty; var v3 = ((Extension)receiver)[indexerArg]; var v4 = (Extension)receiver1 + receiver2; ``` Constructors and static methods would not need any special syntax as the extension can cleanly be referenced as a type where needed. ```c# var v3 = new Extension(...); // Makes instance of the actual extended type. var v4 = Extension.StaticExtensionMethod(...); ``` Note 1: while the cast syntax traditionally casts or converts a value, that would not be the case for its use here. It would only be used as a lookup mechanism to indicate which extension gets priority. Importantly, even with this syntax, extensions themselves are not types. For example: ```c# Extension e1; // Not legal. Extension is not a type. Extension[] e2; // Not legal. Extension is not a type. List e3; // Not legal. Extension is not a type. var v1 = (Extension)receiver; // Not legal. Can't can't have a value of extension type. ``` This is exactly the same as the restrictions on static-types *except* with the carve out that you can use the extension in a cast-syntax or new-expression *only* for lookup purposes, or where a static-class could be used, and nothing else. Usage in places like `nameof(Extension)` or `typeof(Extension)` would still be fine, as those are places where a static type is allowed. Note 2. If cast syntax is not desirable here (especially if confuses the idea if extensions are types), we can come up with a new syntactic form. We are not beholden to the above syntax. # Future expansion The above initial strawman solves several major goals for we want for the extensions space: 1. Supporting a much broader set of extension member types. 2. Having a clean syntax for extension members that matches the regular syntax form (in other words, an extension proeprty still looks like a property). 3. Ensuring teams can move safely to modern extensions *especially* in environments where source *and* binary compatibility is non-negotiable. However, there are parts of its core design that are not ideal in the long term which we would like to ensure we can expand on. These expansions could be released with extensions if time and other resources permit. Or they could come later and cleanly sit on top of the feature to improve the experience. These areas are: ## Expansion 1: Syntactic clumsiness and repetition The initial extension form considers source and binary compatibility as core requirements that must be present to ensure easy migration, allowing codebases to avoid both: 1. bifurcation; where some codebases adopt modern extensions and some do not. 2. internal inconsistency; where some codebases must keep around old extensions and new extensions, with confusion about the semantics of how each interacts with the other. Because classic extension methods have very few restrictions, modern extension methods need to be flexible enough to support all the scenarios which they support. However, many codebases do not need all the flexibility that classic extension methods afforded. For example, classic extension methods allow disparate extension methods in a single static class to target multiple different types. For use cases where that isn't required, we forsee a natural extension (pun intended) where one can translate a modern extension like so: ```c# extension E { // All extension members extend the same thing: public void M() for SomeType str { ... } public int P for SomeType str { get { ... } } public static operator+(...) for SomeType str { ... } // etc } // Can be translated to: extension E for SomeType str { public void M() { ... } public int P { get { ... } } public static operator+(...) { ... } } TODO: Do an ecosystem check on what percentage of existing extensions could use this simpler form. TODO: It's possible someone might have an extension where almost all extensions extend a single type, and a small handful do something slightly different (perhaps extending by `this ref`). Would it be beneficial here to *still* allow the extension members to provide a `for-clause` to override that default for that specific member. For example: ```c# extension StringExtensions for string str { // Lots of normal extension methods on string ... // Override here to extend `string?` public bool MyIsNullOrEmpty() for [NotNullWhen(false)] string? str { } } ``` It seems like this would be nice to support with little drawback. ## Expansion 2: Optional syntactic components As above, we want modern extensions to completely subsume classic extension methods. As such, a modern extension method must be able to support everything a classic extension method supported. For example: ```c# static class Extensions { // Yes, this is legal public static void MakeNonNull([Attr] this ref int? value) { if (value is null) value = 0; } } ``` For this reason, the strawman syntax is: ```g4 for-clause | 'for' parameter` ; parameter (unchanged) | attributes? modifiers? type identifier | attributes? modifiers? type identifier '=' expression ; ``` Fortunately, extension methods today don't support a default value for the `this` parameter, so we don't have to support migrating the second `= value` form forward, and we would consider writing a default value in a `for-clause` to be an error. However, for many extensions no name is really required. All non-static extension members (instance methods, properties, indexers and events) are conceptually a way to extend `this` with new functionality. This is so much so the case that we even designed classic extension methods to use the `this` keyword as their designator. As such, we forsee potentially making the name optional, allowing one to write an extension like so: ```c# extension Enumerable { public TResult Sum(Func selector) for IEnumerable // no name where TResult : struct, INumber where TAccumulator : struct, INumber { // Use 'this' in here to represent the value being extended } } ``` This would have to come with some default name chosen by the language for the parameter in metadata. But that never be needed by anyone calling it from a modern compiler. ## Expansion 3: Generic extensions. The initial design allows for extending generic types through the use of generic extension members. For example: ```c# extension IListExtensions { public void ForEach(Action act) for IList list { foreach (var value in list) act(list); } public long LongCount for IList list { get { long count = 0; foreach (var value in list) count++; return count; } } } ``` Ideally with the optional first expansion we could 'lift' `List` up to `extension IListExtensions`. However, this doesn't work as we need to define the type parameter it references. This naturally leads to the following idea: ```c# extension IListExtensions for IList list { public void ForEach(Action act) { ... } public long LongCount { ... } } ``` This has a few new, but solvable, design challenges. For example, say one has the code: ```c# List ints = ...; var v = ints.ForEach(i => Console.WriteLine(i)); ``` This naturally raises the question of how does this extension get picked for this particular receiver, and how does its type parameter get instantiated to the `int` type. Conceptually (and trying to keep somewhat in line with classic extension methods), we really want to think of the 'receiver' as an 'argument' to some method where normal type inference occurs. Morally, we could think of there being a `IListExtension Infer(IList list)` function whose shape is determined by the extension and its type-parameters and the extended receiver parameter. Then, when trying to determine if an extension applies to a receiver, it would be akin to calling that function with the receiver and seeing if inference works. In the above example that would mean performing type inference on `Infer(ints)` seeing that `T` then bound to `int`, which then gives you back `IListExtensions`. At that point, lookup would then find and perform overload resolution on `ForEach(Action)` with the lambda parameter. This approach does fundamentally expand on the initial extension-members approach, as now, calling extensions is done in two phases. An initial phase to determine and infer extension type parameters based on the receiver, and a second phase to determine and perform overload resolution on the member. We believe this is very powerful and beneficial. But there are deep design questions here which may cause this to be scheduled after the core extension members work happens. ## Expansion 4: Extensions as actual types We are very undecided on if we actually want this. Currently, our view is that it feels like 'roles' fits this goal much better, especially if roles have the ability to be 'implicit' or 'explicit'. Extensions exist very much in the space where they are erased and really are just delicious delicious sugar over calling effectively static helpers to augment a type or value. Roles, on the other hand, seem more fitted to the type space where they are truly part of the type system, intended to appear in signatures, generics, and the like, potentially with strong enforcement about values moving into or out of the role. This warrants deep discussion about the path taken here and the future we are envisioning, to ensure we're happy with any paths this current approach may close off or make more difficult. ## Detailed design [design]: #detailed-design ================================================ FILE: meetings/working-groups/extensions/implicit-compatibility-for-ported-extension-methods.md ================================================ # Implicit compatibility for ported extension methods The new extension syntax introduces a separation between the receiver specification and the member declaration itself. The natural semantics accompanying this leads to several differences from how classic extension methods behave; places where the fact that classic extension methods are really just static methods bleeds through to their behavior. If we don't address this discrepancy, many classic extension methods will not be compatibly portable to the new syntax, and there will be an observable behavior misalignment between those that aren't ported and extension methods in the new syntax. There are two strategies for addressing this: Explicit and implicit compat. With *explicit* compat, a syntactic marker signals that a given extension method in the new syntax should remain compatible with its corresponding classic declaration, and behavior is suitably adjusted to achieve that. With *implicit* compat, all extension methods in the new syntax have behavior that makes all (or nearly all) existing consumption code continue to work the same way, even as new behavior is also embraced. This document pursues **implicit compat**. It looks at each of the behavior discrepancies we know of, and suggests ways to address them. ## Static methods Classic extension methods are static methods on the enclosing static class, and they may be invoked as such. To achieve implicit compat, a new extension instance method generates a static method that mimics the corresponding classic extension method. It uses: - the attributes, accessibility, return type, name and body of the declared instance extension method, - a type parameter list concatenated from the type parameters of the extension declaration and the extension method, in that order, and - a parameter list concatenated from the receiver parameter of the extension declaration and the parameter list of the extension method, in that order. ``` c# public static class Enumerable { extension(IEnumerable source) { public IEnumerable Where(Func predicate) { ... } public IEnumerable Select(Func selector) { ... } } } ``` Generates ``` c# public static class Enumerable { public static IEnumerable Where(this IEnumerable source, Func predicate) { ... } public static IEnumerable Select(this IEnumerable source, Func selector) { ... } } ``` ## Type arguments When type arguments are explicitly given to a classic extension method, they must correspond to all the method's type parameters, including those that are used in the receiver type. This may make sense when the classic extension method is invoked as a static method, but it is not a great experience when it is called as an extension method. By contrast, with new extensions any type parameters on the extension declaration are inferred from the receiver, and type arguments on invocation correspond only to those declared on the extension method itself. It is not uncommon for classic extension methods to have type parameters both for use in the receiver parameter and in subsequent parameters or return types. An example is `System.Linq.Enumerable.Select` where a rewrite to new syntax would put `TSource` on the extension declaration, and `TResult` on the method declaration. It is also not that uncommon for explicit type arguments to be given. A rough GitHub code search suggests that 1.3% of `Select` calls do pass type arguments explicitly. So this is a significant existing scenario. With implicit compat, both "versions" of the type argument list are allowed. This could introduce ambiguities if there are overloads of the extension method with different number of type parameters. That situation is not uncommon in e.g. `System.Linq.Enumerable` (e.g. `SelectMany`) or `System.MemoryExtensions` (e.g. `Contains`). However, those overloads do seem to be distinguishable by parameter list. This is not surprising, since they would have been authored to not clash in the common case where type arguments are inferred. Thus, the scenario for true ambiguity seems very limited in practice. We need to make a determination as to whether we believe significant code exists that currently relies on generic arity to disambiguate extension methods. Based on that we can choose to either take a breaking change or introduce some sort of preference system, where the "classic" arities win over the new "method-only" ones. Given: ``` c# public static class Enumerable { extension(IEnumerable source) { public IEnumerable Select(Func selector) { ... } } ``` The call `myList.Select(...)` would provide type arguments for `TSource` and `TResult`, foregoing the separate inference of `TSource` from the type of `myList`. The call `myList.Select(...)` would provide a type argument for `TResult` in the above `Select` method, with `TSource` being inferred from the type of `myList`. Given that it is non-breaking (enough), we could "backport" this behavior to existing extension methods as well. ## Overload resolution Classic extension methods get excluded from consideration if there isn't an identity, reference or boxing conversion from the receiver to the this-parameter. However, after that point, the receiver gets treated as just yet another argument in determining which method overload wins. This can lead to ambiguities such as this: ``` c# "Hello".M("World!"); // Ambiguous! public static class MyExtensions { public static void M(this object o, string s) { ... } public static void M(this string s, object o) { ... } } ``` For new extension methods, it seems much more in line with expectations that applicable methods "on" more specific types shadow (and thus eliminate) applicable methods "on" base types. After all, that's how lookup works in type hierarchies: As soon as we find an applicable method, we look no further up the chain. It seems such elimination would lead to _fewer_ ambiguities, without causing different results when overload resolution does succeed. Thus it wouldn't be breaking behavior for ported classic extension methods. This claim needs to be investigated for counterexamples of course. ``` c# "Hello".M("World!"); // Picks string.M(object) because receiver is more specific public static class MyExtensions { extension(object o) { publicvoid M(string s) { ... } } extension(string s) { public void M(object o) { ... } } } ``` Given that it is non-breaking, we could "backport" this behavior to existing extension methods as well. ## Type inference When type arguments are inferred for a given classic extension method, any argument may impact the inference of any type parameter. By contrast, with new extensions, type arguments for the extension declaration type parameters are inferred from the receiver, whereas arguments to the extension method may only impact type arguments for the extension method's own type parameters. While we can construct examples where this makes a difference, we have not yet encountered such examples in the wild. If we find this to be very rare, we may choose not to do anything to mitigate it. If we _do_ choose to address it, we would continue to use classic inference for extension methods, lumping in the type parameters and method parameters from both the extension declaration and extension method. For modern usage, this would be unlikely to produce observably different results; only slightly fewer errors. However, it would allow any occurrences of this pattern to continue to compile as well. ``` c# void M(I i, out object o) { i.M1(out o); // infers E.M1 i.M2(out o); // infers E.M2 } public static class E { public static void M1(this I i, out T t) { ... } extension(I i) { public void M2(out T t) { ... } } } public interface I { } ``` ## Other differences There are some more special discrepancies between instance methods and classic extension methods, which warrant explicit design decisions for the new extension syntax. ### Out-of-order type parameters Classic extension methods can have type parameters that do not occur in the receiver type precede ones that do in the type parameter list. There is no direct way to port such an extension method compatibly to the new syntax. We do not know of examples of this in the wild, and the best way forward is probably to accept that such methods, should they exist, will have to stay in classic syntax in order to remain fully compatible. ### InterpolatedStringHandlerArgumentAttribute In instance methods this attribute can use the empty string to denote the name of the receiver. This doesn't currently work for classic extension methods. Should it work for new extension methods? Should it also be made to work for old ones? In both cases the receiver already has a name, as it is expressed as a parameter. ### CallerArgumentExpressionAttribute This attribute cannot be used to refer to the receiver in instance methods. However, in classic extension methods it can, because the receiver is expressed as a parameter. For implicit compat it should remain able to do so in the new extension method syntax. ## Next steps There are several open questions, assumptions about existing code and lacking details in this proposal. If LDM approves of the direction, we need to drill down on the details through spec and implementation, and validate our assumptions about potential for breaks. ================================================ FILE: meetings/working-groups/extensions/metadata-names.md ================================================ The structure for docID for extension blocks is `E.GroupingName.MarkerName` and that for extension members is `E.GroupingName.Member`. But there is an issue when it comes to dealing with arity on the grouping name. The convention for metadata names is to add an arity suffix to the type name. For `List`, the name is "List" and the name in metadata is "List\`1". This allows for overloading on arity while avoiding name conflicts. You can have "List" (with arity zero) and "List\`1" (with arity one). For extension grouping types, the name is unique enough that we use it directly as the metadata name, without adding an arity suffix. But this is causing some issues. The way we produce docIDs for types is to take the name and append the arity suffix. For extensions, we added special handling to use the grouping and marker names. Let's consider this example: ```csharp static class E { extension(T t) { public void M() { } } } ``` with corresponding metadata: ``` .class E { .class '$8048A6C8BE30A622530249B904B537EB' // grouping type { .class '$65CB762EDFDF72BBC048551FDEA778ED' // marker type { .. marker method .. } public void M() => throw; // extension member without implementation } public static void M(this int i) { } // implementation method } ``` **If we do include an arity suffix** when producing docIDs for extensions, then: - the docIDs don't match the metadata names. - if someone makes metadata for an extension type using some other tool, and they do include the arity suffix in the metadata names of grouping types, then docIDs won't match metadata names again. To illustrate the first bullet, the docIDs for the example would differ from the names in metadata: - extension block: "E.$8048A6C8BE30A622530249B904B537EB\`1.$65CB762EDFDF72BBC048551FDEA778ED" - extension member: "E.$8048A6C8BE30A622530249B904B537EB\`1.M" To illustrate the second bullet, if some other tool cooks up extension metadata including arity suffixes like this: ``` .class E { .class 'GroupingType`1' // grouping type { .class 'MarkerType' // marker type { .. marker method .. } public void M() => throw; // extension member without implementation } public static void M(this int i) { } // implementation method } ``` Then the docIDs would not match the metadata names: - extension block: "E.GroupingName\`1\`1.MarkerName" - extension member: "E.GroupingName\`1\`1.M" And when producing docIDs for constructed symbols, we'd end up with both type arguments and arity suffixes: - extension block: "E.GroupingName\`1{System.Int32}.MarkerName" - extension member: "E.GroupingName\`1{System.Int32}.M" **If we don't include an arity suffix**, then: - docIDs produced from VB symbols on extension metadata will diverge from those produced from C# symbols. VB doesn't have the concept of extension, so will have regular handling for types (which include an arity suffix) To illustrate that issue, the docIDs from C# source or metadata for the example would be: - extension block: "E.$8048A6C8BE30A622530249B904B537EB.$65CB762EDFDF72BBC048551FDEA778ED" - extension member: "E.$8048A6C8BE30A622530249B904B537EB.M" But the docIDs from VB metadata would differ from those from C#: - extension grouping type: "E.$8048A6C8BE30A622530249B904B537EB\`1" - extension marker type: "E.$8048A6C8BE30A622530249B904B537EB\`1.$65CB762EDFDF72BBC048551FDEA778ED" - extension member: "E.$8048A6C8BE30A622530249B904B537EB\`1.M" # Proposal We're proposing to compose the metadata name for grouping types by appending an arity suffix to `ExtensionGroupingName`. `ExtensionGroupingName` should reflect names of the emitted grouping types from language perspective, not its emitted names. That would be more conventional. No change to `ExtensionMarkerName` (name and metadata names match). Then we'd produce the docIDs as described above, by appending an arity suffix to `ExtensionGroupingName` in a way consistent to the current handling of regular generic types. For the above example, the compiler would produce: ``` .class E { .class '$8048A6C8BE30A622530249B904B537EB`1' // grouping type with arity suffix { .class '$65CB762EDFDF72BBC048551FDEA778ED' // marker type { .. marker method .. } public void M() => throw; // extension member without implementation } public static void M(this int i) { } // implementation method } ``` The `ExtensionGroupingName` would remain "$8048A6C8BE30A622530249B904B537EB" (both for source and metadata symbols). The `ExtensionMarkerName` would remain "$65CB762EDFDF72BBC048551FDEA778ED" (both for source and metadata symbols). Then the docIDs for C# symbols would be: - extension block: "E.$8048A6C8BE30A622530249B904B537EB\`1.$65CB762EDFDF72BBC048551FDEA778ED" - extension member: "E.$8048A6C8BE30A622530249B904B537EB\`1.M" And the docIDs for VB metadata symbols would be: - extension grouping type: "E.$8048A6C8BE30A622530249B904B537EB\`1" - extension marker type: "E.$8048A6C8BE30A622530249B904B537EB\`1.$65CB762EDFDF72BBC048551FDEA778ED" - extension member: "E.$8048A6C8BE30A622530249B904B537EB\`1.M" Everything aligns. And if some other tool cooks up extension metadata without arity suffix like this: ``` .class E { .class 'GroupingType' // grouping type (without arity suffix) { .class 'MarkerType' // marker { .. marker method .. } public void M() => throw; // extension member without implementation } public static void M(this int i) { } // implementation method } ``` then the docIDs would be: - extension block: "E.GroupingType\`1.MarkerType" - extension member: "E.GroupingType\`1.M" Those docIDs are not ideal (they differ from metadata names), but that's not a new problem. # Practical considerations If we're going to make this change, we have about 1 week to get this into the RC2 compiler. But the RC2 SDK/BCL will be compiled using the RC1 compiler, so the change would only be visible in the GA SDK/BCL assemblies. The new compiler will still be able to consume extensions produced by the RC1 compiler: when loading metadata symbols, the unmangling is optional (if there is no arity suffix in the metadata name, we just use the whole metadata name as the name). Given that only the compiler and docIDs make use of grouping types, there would be no binary breaking change. Only implementation methods are referenced in IL, and those are unaffected by the change. # Alternative proposals We also brainstormed a design where docIDs for extensions would be produced by taking `ExtensionGroupingName`, would strip any arity suffix (to deal with metadata from another tool), then would proceed as usual (add an arity suffix). # References Some pointers to roslyn codebase for reference: ## Producing docIDs All implementations use `Name` from the symbol (as opposed to `MetadataName`) and append a suffix for generics (either an arity suffix for docIds or type arguments for reference Ids on constructed symbols) 1. `ISymbol.GetDocumentationCommentId()` (implemented as part of C# and VB symbols) This API also produces the docIDs emitted in xml output (see `DocumentationCommentCompiler.WriteDocumentationCommentXml` and `GenerateDocumentationComments`) Uses `Name`, either appends an arity suffix (for definitions) or type arguments (for constructed symbols). (see `DocumentationCommentIDVisitor.PartVisitor.VisitNamedType`) 2. `DocumentationCommentId.CreateDeclarationId(ISymbol)` (implemented in core compiler layer) Uses `Name`, appends an arity suffix (see `DocumentationCommentId.PrefixAndDeclarationGenerator.DeclarationGenerator.VisitNamedType`) 3. `DocumentationCommentId.CreateReferenceId()` (implemented in core compiler layer) Uses Name (see `BuildDottedName`), either appends an arity suffix or type arguments (see `DocumentationCommentId.ReferenceGenerator`) ## Reading docIDs 1. `DocumentationCommentId.GetSymbolsForDeclarationId(string id, Compilation compilation)` `DocumentationCommentId.Parser.ParseDeclaredId` Splits identifier into name and arity, then searches symbols with matching Name and Arity (exact). 2. CREF binding `BindNameMemberCref`, `ComputeSortedCrefMembers` Takes a name followed by type argument list (which provides arity), looks up by name and arity (exact, for types), then constructs with type arguments ## Metadata names and metadata loading The C# and VB compilers generally appends the arity suffix for generic types, to make the metadata name for a named type. There's some exceptions (notably EE named type symbols). When loading types from metadata, the C# and VB compilers remove the arity suffix when it matches the arity found in metadata (see `MetadataHelpers.UnmangleMetadataNameForArity`). This means that we can load a type whose name doesn't include a suffix. ================================================ FILE: meetings/working-groups/extensions/rename-to-roles-and-extensions.md ================================================ # Re-rename to Roles and Extensions * [x] Proposed * [ ] Prototype: Not Started * [ ] Implementation: Not Started * [ ] Specification: Not Started ## Summary [summary]: #summary Rename `explicit extension` back to `role` and `implicit extension` back to simply `extension`. ## Motivation [motivation]: #motivation The proposed [Extensions](https://github.com/dotnet/csharplang/blob/main/proposals/extensions.md) feature design eliminates the previously proposed distinction between "roles" and "extensions", in recognition that they are in fact two flavors of the same feature. Instead it introduces modifiers `explicit` and `implicit` onto the shared declaration keyword `extension`. This makes sense from a language designer economy-of-concepts point of view. However, the intended mainline *use* of the two kinds of extension differs considerably: One is explicitly used _as_ a type, whereas the other implicitly adds members to an _existing_ type. The overlap in intended usage is small, boiling down to using an implicit extension explicitly as a type for disambiguation purposes. We've now had some time to get experience with the design. In practice, anyone but a language designer or implementer rarely has to discuss the overall feature as a whole. Any given declaration is either an "explicit extension" or an "implicit extension", and the intended mode of use follows from that. The terms are not only long but deceptively similar, causing confusion as well as verbosity. We should go back to clear, separate nouns for the two features. The previously used nouns "role" and "extension" are great candidates, though the choice of terms is less important than the choice of having two of them. This will help people understand them separately, in a manner specific to their main usage patterns. It allows developers to adopt them one at a time, and not worry much about the connections between them. Going back to a narrower use of the term "extension" also keeps it better aligned with the meaning developers are already used to from extension methods. There have been demands for "extension members" and "extension everything" ever since extension methods were added, and this use of the term matches the intuition reflected in these asks. This is not a proposal to turn roles and extensions into completely separate features. With the rename an extension will still *also* be a role. The two features benefit hugely from having a shared core. The syntax of declarations, the type of `this` inside them, the conversions to and from the underlying type, the code generation strategies, etc., etc., all entirely overlap. Additionally, envisioned future feature evolution such as inheritance and interface implementation apply to and have compelling scenarios for both features. It is conceivable that this step opens up opportunities where more differences between roles and extensions would be possible and beneficial. That is totally fine but is not implied here. ## Detailed design [design]: #detailed-design In the [proposal grammar](https://github.com/dotnet/csharplang/blob/main/proposals/extensions.md#design) change the `extension_declaration` production as follows: ``` antlr role_declaration : role_modifier* ('role' | 'extension') identifier type_parameter_list? ('for' type)? type_parameter_constraints_clause* role_body ; ``` Rename all productions named `extension_NNN` to `role_NNN`. Throughout the proposal, update code snippets accordingly. In prose, change "implicit extension" to "extension" and unqualified "extension" to "role". There are only two occurrences of "explicit extension" in prose; they can easily be rewritten (to e.g. "non-extension role"), and "role" can safely be used as the overarching term. It turns out that it is rare for the specification to have to talk about explicit extensions *only*, since everything that applies to them applies to implicit extensions also. In the [Implementation Details](https://github.com/dotnet/csharplang/blob/main/proposals/extensions.md#implementation-details) section we will want to adjust the naming of emitted attributes and members, and there are probably a few other places that would benefit from a rewording after this change. ### Examples ``` c# // Roles intended to be used directly as types public role Order for JsonElement { public string Description => GetProperty("description").GetString()!; } public role Customer for JsonElement { public string Name => GetProperty("name").GetString()!; public IEnumerable Orders => GetProperty("orders").EnumerateArray(); } // Extensions intended to provide new function members to existing types public extension JsonString for string { private static readonly JsonSerializerOptions s_indentedOptions = new() { WriteIndented = true }; public JsonElement ParseAsJson() => JsonDocument.Parse(this).RootElement; public static string CreateIndented(JsonElement element) => element.ValueKind != JsonValueKind.Undefined ? JsonSerializer.Serialize(element, s_indentedOptions) : string.Empty; } ``` ## Drawbacks [drawbacks]: #drawbacks It becomes less directly clear from syntax that extensions are just roles with more behavior. ## Alternatives [alternatives]: #alternatives ### Alternative nouns While "extension" is very likely the right term for "implicit extensions" because of the connection to the existing extension methods, there is more room for debate about "role". While that term does occur in literature, it is not particularly established among developers. We can essentially pick whichever term we like, and many others have been proposed. ### Allowing `extension role` If we want the language to more directly reflect that every extension is a role, we could make the declaration syntax for extensions allow an optional `role` keyword to occur in the declaration: ``` c# public extension role JsonString for string { ... } // equivalent to public extension JsonString for string { ... } ``` This would be somewhat similar to `record class` which is allowed to be abbreviated to `record`. However, for records the use of `record class` might be motivated by wanting to highlight that it is not a `record struct`. For `extension role` on the other hand there is no `extension something-else` to distinguish it from. We don't have great scenarios at the moment (outside disambiguation) where an extension is *also* intended to be used as a role (i.e. an explicit type), but such scenarios may come up. In those cases, it might be useful to stress this intent by explicitly putting the word `role` in the declaration, even if it doesn't have semantic impact. This is something that could be added anytime. We could ship the feature without it, and add the ability to say `extension role` as a long form of `extension` add any future point if it seems warranted. By analogy we also shipped `record` first and then retcon'ed it to be an abbreviation of `record class` in a subsequent release when `record struct` was added. ### Implementing extensions before roles Using separate nouns as proposed here, it may become an option to ship extensions first and roles later. While `this`-access and disambiguation for extensions use them as a named type, that is only going to be needed locally in a function body. By contrast, roles are likely to be used in signatures, and so require quite an elaborate metadata scheme for implementation. Perhaps there's a stage where we leave out role declarations, as well as disallow extension types from occurring in signatures. The latter restriction would be similar to the one that applies to anonymous types in today's C#. ## Unresolved questions [unresolved]: #unresolved-questions ## Design meetings https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-18.md#extensions-naming ================================================ FILE: meetings/working-groups/extensions/the-design-space-for-extensions.md ================================================ # The design space for extensions Let's put the current proposals for extensions in a broader context, and compare their pros and cons. ## Background: How we got here The competing approaches and philosophies around how to express the declaration of extension members trace their roots all the way back to when extension methods were first designed. ### C# 3: The start of extension methods C# 3 shipped with the extension methods we know today. But their design was not a given: An alternative proposal was on the table which organized extension methods in type-like declarations, each for one specific extended (underlying) type. In this model, extension method declarations would look like instance method declarations, and future addition of other member kinds - even interfaces - would be syntactically straightforward. I cannot find direct references to this first type-based proposal, but here is a slightly later version from 2009 that was inspired by it: ``` c# public extension Complex : Point { public Point Scale(double s) { return new Point(X * s, Y * s); } public double Size { get { return Math.Sqrt(X*X + Y*Y); } } public static Point operator +(Point p1, Point p2) { return new Point(p1.X + p2.X, p1.Y + p2.Y); } } ``` Ultimately the current design was chosen for several reasons. Importantly it was much simpler: a syntactic hack on top of static methods. C# 3 was already brimming with heavy-duty features - lambdas, expression trees, query expressions, advanced type inference, etc. - so the appetite to go big on extension methods was limited. Moreover, the static method approach came with its own disambiguation mechanism - just call as a static method! - and allowed convenient grouping of extension methods within one static class declaration. The extension methods of `System.Linq.Enumerable` would have needed to be spread across about 15 extension type declarations if they had been split by underlying type. But perhaps most significantly, we didn't know extension methods were going to be such a hit. There was a lot of skepticism in the community, especially around the risks of someone else being able to add members to your type. The full usefulness of the paradigm was not obvious even to us at the time; mostly we needed them for the query scenario to come together elegantly. So betting on them as a full-fledged new feature direction felt like a risky choice. Better to keep them a cheap hack to start with. ### C# 4: Foundered attempts at extension members Of course extension methods _were_ a huge success in their own right, and the community was immediately asking for more; especially extension properties and extension interfaces. The LDM went to work on trying to generalize to all member kinds, but felt captive to the choices made in C# 3. We felt extension members would have to be a continuation, not just philosophically but _syntactically_, of the extension methods we'd shipped. For instance, extension properties would have to either use property syntax and take an extra `this` parameter somehow, or we'd need to operate at the lowered level of `set` and `get` methods representing the accessors of properties. Here is an example from 2008: ``` c# public extension E { public static string Name(this Foo myself){ get { … } set { … } } public static V this(this Dict dict)[K index] { get { … } } public static event Handler OnExplode(this Foo it) { add { … } remove { … } } public static operator + (BigInteger i, Complex c) { … } public static implicit operator Complex(BigInteger i) { … } } ``` These explorations led to proposals of unbearable complexity, and after much design and implementation effort they were abandoned. At the time we were not ready to consider rebooting extensions with an alternative syntax, one that would leave the popular classic extension methods behind as a sort of legacy syntax. ### The return of type-based extensions The Haskell programming language has _type classes_, which describe the relationships within groups of types and functions, and which, crucially, can be applied after the fact, without those types and functions participating. A proposal from Microsoft Research in Cambridge for adding type classes to C# triggered a string of proposals that eventually led back to extension interfaces: If extension members could somehow help a type implement an interface without the involvement of that type, this would facilitate similar adaptation capabilities to what type classes provide in Haskell, and would greatly aid software composition. Extension interfaces fit well with the old alternative idea that extensions were a form of type declaration, so much so that we ended up with a grand plan where extensions were types, and where such types would even be a first class feature of their own - separate from the automatic extension of underlying types - in the form of _roles_. This approach ran into several consecutive setbacks: We couldn't find a reasonable way to represent interface-implementing extensions in the runtime. Then the implementation of the "typeness" of extensions proved prohibitively expensive. In the end, the proposal had to be pared back to something much like the old alternative design from above: extensions as type declarations, but with no "typeness" and no roles. Here's a recent 2024 example: ``` c# extension E for C { // Instance members public string P { get => f; set => f = value.Trim(); } // Property public T M() where T : IParsable => T.Parse(f, default); // Method public char this[int index] => f[index]; // Indexer public C(string f) => this.f = f; // Constructor // Static members public static int ff = 0; // Static field public static int PP { get; set => field = Abs(value); } // Static property public static C MM(string s) => new C(s); // Static method public static C operator +(C c1, C c2) => c1.f + c2.f; // Operator public static implicit operator C(string s) => new C(s); // UD conversion } ``` We will refer to the resulting flavor of design as "_type-based extensions_", because the underlying type of the extension is specified on the extension type itself, and the members are just "normal" instance and static member declarations, including providing access to the underlying value with the `this` keyword rather than a parameter. ### The return of member-based extensions Now that the bigger story of extensions as types with interfaces has been put on hold with its future prospects in question, it is worth asking: Are we still on the right syntactic and philosophical path? Perhaps we should instead do something that is more of a continuation of classic extension methods, and is capable of bringing those along in a compatible way. This has led to several proposals that we will collectively refer to as "_member-based extensions_". Unlike most of the abandoned C# 4 designs of yore, these designs do break with classic extension methods _syntactically_. Like the type-based approach they embrace an extension member declaration syntax that is based on the corresponding instance member declaration syntax from classes and structs. However, unlike type-based extensions, the underlying type is expressed at the member level, using new syntax that retains more characteristics of a parameter. Here are a few examples from [this recent proposal](https://github.com/dotnet/csharplang/pull/8525): ``` c# public partial extensions Extensions { public SourceTextContainer (ITextBuffer buffer).AsTextContainer() => TextBufferContainer.From(buffer); internal TextLine (ITextSnapshotLine line).AsTextLine() => line.Snapshot.AsText().Lines[line.LineNumber]; } internal extensions IComparerExtensions for IComparer { public IComparer comparer.Inverse => new InverseComparer(comparer) } ``` The motivation is not just a closer philosophical relationship with classic extension methods: It is an explicit goal that existing classic extension methods can be ported to the new syntax in such a way that they remain source and binary compatible. This includes allowing them to be called as static methods, when their declarations follow a certain pattern. We've had much less time to explore this approach. There are many possible syntactic directions, and we are just now beginning to tease out which properties are inherent to the approach, and which are the result of specific syntax choices. Which leads us to the following section, trying to compare and contrast the two approaches. ## Comparing type-based and member-based proposals Both approaches agree on a number of important points, even as the underlying philosophy differs in what currently feels like fundamental ways: - __Member syntax:__ In both approaches the member declaration syntax is based on the corresponding instance member declaration syntax. They may be adorned or modified in different ways, but neither attempts to use the naked static method syntax of classic extension methods, or otherwise embrace the lowered form in declaration syntax. - __Type syntax:__ Both also introduce a new form of type declaration (with the keyword `extension` or `extensions`) to hold extension member declarations. Neither approach keeps extension members in static classes. - __Abstraction:__ Both generally hide the low-level representation of the declaration from language-level use (with one exception in the member-based approach for compatibility purposes). This means that both have the same need for a disambiguation mechanism to replace classic extension methods' ability to be called directly as static methods. - __Lookup:__ Both are amenable to pretty much the same range of design options regarding extension member lookup, inference of type arguments, and overload resolution. And of course both approaches share the same overarching goal: to be able to facilitate extension members of nearly every member kind, not just instance methods. Either now or in the future this may include instance and static methods, properties, indexers, events, operators, constructors, user-defined conversions, and even static fields. The only exception is members that add instance state, such as instance fields, auto-properties and field-like events. The similarities make it tempting to search for a middle ground, but we haven't found satisfactory compromise proposals (though [not for lack of trying](./compromise-design-for-extensions.md)). Most likely this is because the differences are pretty fundamental. So let's look at what divides the two approaches. ### Relationship to classic extension methods The core differentiating factor between the two approaches is how they relate to classic extension methods. In the member-based approach, it is a key goal that existing classic extension methods be able to migrate to the new syntax with 100% source and binary compatibility. This includes being able to continue to call them directly as static methods, even though they are no longer directly declared as such. A lot of design choices for the feature flow from there: The underlying type is specified in the style of a parameter, including parameter name and potential ref-kinds. The body refers to the underlying value through the parameter name. Only instance extension methods declared within a non-generic `extensions` declaration are compatible and can be called as static methods, and the signature of that static method is no longer self-evident in the declaration syntax. The type-based approach also aims for comparable expressiveness to classic extension methods, but without the goal of bringing them forward compatibly. Instead it has a different key objective, which is to declare extension members with the same syntax as the instance and static members they "pretend" to be, leaving the specification of the underlying type to the enclosing type declaration. This "thicker" abstraction cannot compatibly represent existing classic extension methods. People who want their existing extension methods to stay fully compatible can instead leave them as they are, and they will play well with new extension members. While the type-based approach looks like any other class or struct declaration, this may be deceptive and lead to surprises when things don't work the same way. The member-based approach is arguably more contiguous with classic extension methods, whereas the type-based approach is arguably simpler. Which has more weight? ### Handling type parameters An area where the member-based approach runs into complexity is when the underlying type is an open generic type. We know from existing extension methods that this is quite frequent, not least in the core .NET libraries where about 30% of extension methods have an open generic underlying type. This includes nearly all extension methods in `System.Linq.Enumerable` and `System.MemoryExtensions`. Classic extension methods facilitate this through one or (occasionally) more type parameters on the static method that occur in the `this` parameter's type: ``` c# public static class MemoryExtensions { public static Span AsSpan(this T[]? array); } ``` The same approach can be used to - compatibly - declare such a method with the member-based approach: ``` c# public extensions MemoryExtensions { public Span (T[]? array).AsSpan(); } ``` We should assume that open generic underlying types would be similarly frequent for other extension member kinds, such as properties and operators. However, those kinds of member declarations don't come with the ability to declare type parameters. If we were to declare `AsSpan` as a property, where to declare the `T`? This is a non-issue for the type-based approach, which always has type parameters and underlying type on the enclosing `extension` type declaration. For the member-based approach there seem to be two options: 1. Allow non-method extension members to also have type parameters. 2. Allow the type parameter and the specification of the underlying type on the enclosing type. Both lead to significant complication: #### Type parameters on non-method extension members Syntactically we can probably find a place to put type parameters on each kind of member. But other questions abound: Should these be allowed on non-extension members too? If so, how does that work, and if not, why not? How are type arguments explicitly passed to each member kind when they can't be inferred - or are they always inferred? ``` c# public extensions MemoryExtensions { public Span (T[]? array).AsSpan { get; } } ``` This seems like a big language extension to bite off, especially since type parameters on other members isn't really a goal, and current proposals don't go there. #### Allow type parameters and underlying type to be specified on the enclosing type declaration If the enclosing `extensions` type declaration can specify type parameters and underlying type, that would give members such as properties a place to put an open generic underlying type without themselves having type parameters: ``` c# public extensions MemoryExtensions for T[]? { public Span (array).AsSpan { get; } } ``` This is indeed how current member-based proposals address the situation. However, this raises its own set of complexities: 1. Classic extension methods are purely method-based, and the enclosing static class is just a plain container contributing nothing but its name. Here, though, the enclosing `extensions` declaration starts carrying crucial information for at least some scenarios. 2. There are now two distinct ways of providing the underlying type for an extension member, and figuring out which to use becomes a bit of a decoder-ring situation: - For a compatible port of a classic extension method, the underlying type and any type parameters must be on the _member_. - For non-method extension members with an open generic underlying type, the underlying type and the type parameters must be on the enclosing _type_. - For extension methods that do not need to be compatible and have an open generic underlying type, the underlying type and the type parameters can be specified _either_ on the method or the type, but the generated code will be incompatible between the two. - For extension members with a closed underlying type, the underlying type can be defined _either_ on the method or the type, and the two are interchangeable. 3. Once an `extensions` declaration specifies an underlying type, it can no longer be shared between extension members with different underlying types. The grouping of extension members with different underlying types that is one of the benefits of the member-based approach doesn't actually work when non-method extension members with open generic underlying types are involved: You need separate `extensions` declarations with separate type-level underlying types just as in the type-based approach! ``` c# public extensions ArrayMemoryExtensions for T[]? { public Span (array).AsSpan { get; } } public extensions ArraySegmentMemoryExtensions for ArraySegment? { public Span (segment).AsSpan { get; } } ``` In summary, classic extension methods rely critically on static methods being able to specify both parameters and type parameters. A member-based approach must either extend that capability fully to other member kinds, or it must partially embrace a type-based approach. ### Tweaking parameter semantics An area where the type-based approach runs into complexity is when the default behavior for how the underlying value is referenced does not suffice, and the syntax suffers from not having the expressiveness of "parameter syntax" for the underlying value. This is a non-issue for the member-based approach, which allows all this detail to be specified on each member. There are several kinds of information one might want to specify on the underlying value: #### By-ref or by_value for underlying value types In classic extension methods, the fact that the `this` parameter _is_ a parameter can be used to specify details about it that real instance methods don't get to specify about how `this` works in their body. By default, `this` parameters, like all parameters, are passed by value. However, if the underlying type is a value type they can also be specified as `ref`, `ref readonly` and `in`. The benefit is to avoid copying of large structs and - in the case of `ref` - to enable mutation of the receiver itself rather than a copy. The use of this varies wildly, but is sometimes very high. Measuring across a few different libraries, the percentage of existing extension methods on value types that take the underlying value by reference ranges from 2% to 78%! The type-based approach abstracts away the parameter passing semantics of the underlying value - extension instance members just reference it using `this`, in analogy with instance members in classes and structs. But classes and structs have different "parameter passing" semantics for `this`! In classes `this` is by-value, and in structs `this` is by `ref` - or `ref readonly` when the member or struct is declared `readonly`. There are two reasonable designs for what the default should be for extension members: 1. Follow classes and structs: When the underlying type is a reference type, pass `this` by value, and when it is a value type pass `this` by `ref` (or perhaps `ref readonly` when the member is `readonly`). In the rare case (<2%) that the underlying type is an unconstrained type parameter, decide at runtime. 2. Follow classic extension methods: Always pass `this` by value. Either way, the default will be wrong for some significant number of extension members on value types! Passing by value prevents mutation. Passing by reference is unnecessarily expensive for small value types. In order to get to reasonable expressiveness on this, the type-based approach would need to break the abstraction and get a little more "parameter-like" with the underlying type. For instance, the `for` clause might optionally specify `ref` or `in`: ``` c# public extension TupleExtensions for ref (int x, int y) { public void Swap() => (x, y) = (y, x); // `this` is by ref and can be mutated public readonly int Sum => x + y; // `this` is ref readonly and cannot me mutated } ``` #### Attributes This-parameters can have attributes. It is quite rare (< 1%), and the vast majority are nullability-related. Of course, extension members can have attributes, but they would need a way to specify that an attribute goes on the implicit `this` parameter! One way is to introduce an additional attribute target, say `this`, which can be put on instance extension members: ``` c# [this:NotNullWhen(false)] public bool IsNullOrEmpty => this is null or []; ``` #### Nullable reference types A classic extension method can specify the underlying type as a nullable reference type. It is fairly rare (< 2%) but allows for useful scenarios, since, unlike instance members, extension members can actually have useful behavior when invoked on null. Anotating the receiver as nullable allows the extension method to be called without warning on a value that may be null, in exchange for its body dealing with the possibility that the parameter may be null. A type-based approach could certainly allow the `for` clause to specify a nullable reference type as the underlying type. However, not all extension members on that type might want it to be nullable, and forcing them to be split across two `extension` declarations seems to break with the ideal that nullability shouldn't have semantic impact: ``` c# public extension NullableStringExtension for string? { [this:NotNullWhen(false)] public bool IsNullOrEmpty => this is null or []; } public extension StringExtension for string { public string Reverse() => ...; } ``` It would be better if nullability could be specified at the member level. But how? Adding new syntax to members seems to be exactly what the type-based approach is trying to avoid! The best bet may be using an attribute with the `this` target as introduced above: ``` c# public extension StringExtension for string { [this:AllowNull][this:NotNullWhen(false)] public bool IsNullOrEmpty => this is null or []; public string Reverse() => ...; } ``` This would allow extension members on nullable and nonnullable versions of the same underlying reference type to be grouped together. ### Grouping Classic extension methods can be grouped together in static classes without regard to their underlying types. This is not the case with the type-based approach, which requires an `extension` declaration for each underlying type. Unfortunately it is also only partially the case for the member-based approach, as we saw above. Adding e.g. extension properties to `MemoryExtensions`, which has a lot of open generic underlying types, would lead to it having to be broken up into several `extensions` declarations. This is an important quality of classic extension methods that unfortunately neither approach is able to fully bring forward. ### Non-extension members Current static classes can of course have non-extension static members, and it is somewhat common for those to co-exist with extension methods. In the member-based approach a similar thing should be easy to allow. Since the extension members have special syntactic elements, ordinary static members wouldn't conflict. In the type-based approach, ordinary member syntax introduces extension members! So if we want _non_-extension members that would have to be accommodated specially somehow. ### Interface implementation The type-based syntax lends itself to a future where extensions implement interfaces on behalf of underlying types. For the member-based syntax that would require more design. ## Summary All in all, both approaches have some challenges. The member-based approach struggles with open generic underlying types, which are fairly common. They can be addressed in two different ways, both of which add significant syntax and complexity. The type-based approach abstracts away parameter details that are occasionally useful or even critical, and would need to be augmented with ways to "open the lid" to bring that expressiveness forward from classic extension methods when needed. ================================================ FILE: meetings/working-groups/field-keyword/FK-2024-06-26.md ================================================ # `field` keyword working group notes for June 26, 2024 Working group met today at the request of the LDM to look into potential options on how to mesh the `field` keyword and [Nullable Reference Types](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/nullable-reference-types-specification.md) in a complimentary fashion. Through our own investigations and discussions with the community we've identified many areas where we would like the `field` keyword to be available where arguments can be made that the backing field's type should be nullable, even if the property's type is not. A non-exhaustive list of these cases is at: https://gist.github.com/jnm2/df690bc7278371d735c546c57e787b6e In particular, we would like users to take idiomatic code cases that already use a field+property, and move those over to using `field`, ideally with no extra effort. In other words, we'd like them to be able to remove their field, and replace references to that field in the property with `field` without any extra cruft or mess. There are several approaches we can consider, with varying user experience outcomes depending on the approach and the specific example we are looking at. These working group notes will outline the 5 potential paths we believe have merit, along with some small examinations of interesting outcomes. A followup document will go into these in depth, showing the outcome of each path against every use case we're aware of. ## Clarifications All of the examples deal with a property signature of the form: ```C# T Prop { ...field... } // or T Prop => ...field...; ``` Where `T` is not known to be a value type. For example, a reference type like `string`, or an unconstrained type parameter. Importantly, none of this document applies to when `T` is a value type (like `int`). In that case, the backing field's type is exactly the same as the property type. We do not want the runtime representation of the two member's types to be different. For all followup examples, we'll just use the `T Prop { ...field... }` form as `T Prop => ...field...;` is already equivalent to `T Prop { get { return ...field...; } }` Similarly, the examples will often use `_field` when translating the above form to an actual field+property pair. This does not mean that the field's name is actually `_field`. It is just a convenient way to indicate it without clutter. ## Approach A. Backing field has the same type as the property type. ### Approach A1. Ship as is with no new rules/understanding of this case. With this approach: ```c# T Prop { ...field... } // Would be the same as T _field; T Prop { ..._field... } ``` This is, by far, the simplest approach we can take. It is also safe to do in C#13, as we can relax this approach in a post launch if we want. This approach is also likely one of the easiest to explain to users, and how users may immediately think about this feature space from the start. However, there are repercussions for how certain use cases will work here. For example, if you had: ```c# string Prop { get => field ?? ""; private set; } // or string Prop { get => field ??= ComputeInitialValue(); } ``` You would get a warning in each constructor that the backing field `_field` was not initialized through all code paths. This would then require the user to have to suppress the warning, or write something like the following: ```c# string Prop { get => field ??= ComputeInitialValue(); } = null!; // or // in constructor: this.Prop = null!; ``` So, while simple, migrating a trivial, idiomatic case like: ```c# string? _prop; string Prop { get => _prop ??= ComputeInitialValue(); } ``` would involve a non-trivial transformation, with potentially annoying suppressions. ### Approach A2. Control backing field warnings with attributes. With this approach: ```c# [field: MaybeNull] string Prop { ...field... } // Would be the same as [MaybeNull] string _field; string Prop { ..._field... } ``` This has the benefit of now both suppressing any warnings about the field being `null` in any constructor, while also providing appropriate warnings if `field` was not used safely. For example: ```c# [field: MaybeNull] string Prop { get => field; } // will appropriately warn [field: MaybeNull] string Prop { get => field ??= ComputeValue(); } // will appropriately not warn. ``` Note: the above approach would also allow for the case where the setter wanted to reset the value back to `null` by using `AllowNull`. Specifically: ```c# // will appropriately not warn. [field: MaybeNull, AllowNull] string Prop { get => field ??= ComputeValue(); set => field = value is "" ? null : value.Trim(); } ``` Like with `A1` though, this whole approach comes with the annoying nuisance of having to annotate the field in this fashion to avoid warnings in the constructor. Even if it seems like something the language/compiler could understand. ## Approach B. Backing field has the nullable version of the property type. ### Approach B1. Use existing rules about initializing an auto-prop from a constructor. With this approach: ```c# string Prop { ...field... } // Would be the same as the following for the purpose of analyzing the accessors string? _field; string Prop { ..._field... } ``` However, like with auto-properties today, one would still be required to initialize the property in the constructor, even if not necessary. So: ```c# public C() { // would warn, the auto-prop is not considered initialized Console.WriteLine(this.Prop); } ``` Whereas: ```c# public C() { this.Prop = ""; // would no longer warn, the auto-prop is considered initialized Console.WriteLine(this.Prop); } ``` Note that this analysis is not accurate. Consider a setter that resets the backing field back to null: ```c# string Prop { get; set => field = value is "" ? null : value } public C() { this.Prop = ""; // would not warn, even though you could get null. Console.WriteLine(this.Prop); } ``` The benefit of this approach is the simplicity. But false negatives might unfortunately emerge for some of these cases. ### Approach B2. Perform flow analysis between constructors and accessors. This approach is the most extensive, and would prove to have the most precise understanding of nullable flow between a constructor and the auto-prop accessor. The general idea would be perform *nullable analysis* of: ```c# T Prop { get => ...field...; set => ...field... } = optional_initializer; public C() { // ... constructor statements ... } ``` as if it had been written like so: ```c# public C() { T? _field = default; _field = optional_initializer; // if present // ... constructor statements ... // ... any calls to this.Prop become calls to Prop_get and Prop_set ... while (true) { if (rand()) Prop_get(); else Prop_set(...non-null-value...) } T Prop_get() { ...getter body... } void Prop_set(T value) { ...setter body... } } ``` This looks a bit daunting at first, but comes with a simple general intuition: 1. The language/compiler already support precise NRT flow analysis of locals and local functions within a method. 1. The backing field is rethought of as just a nullable local field. 1. If initialized at the property declaration site, there is an upfront assignment to that backing field solely for the purpose of updating the initial flow state. 1. The normal constructor statements are analyzed, with any calls to the auto-props in the type now being calls to the local functions corresponding to the accessors. This ensures precise NRT analysis at every call site. 1. After the constructor statements finish, the property is analyzed again in a fashion that simulates how the getter/setter can be called randomly in any order after the constructor concludes. With this formalization, precise NRT analysis just "falls out". For example, if the user had: ```c# string Prop { get => field ?? ""; private set; } // or string Prop { get => field ??= ComputeInitialValue(); } ``` then they would have to do nothing else (no need for any updating of the constructor). Similarly, the following would be known to be safe without warnings: ```c# string Prop { get => field; set => field = value.Trim(); } public C() { this.Prop = ""; } ``` However, this would properly report a warning: ```c# // warning that `field` in the getter might be null. string Prop { get => field; set => field = value is "" ? null : value.Trim(); } public C() { this.Prop = ""; } ``` Everything is precise here because we are performing real NRT analysis across the constructor statements, and for after the constructor finishes up. The downside here though is complexity of implementation and potential computational costs having to perform this analysis for each constructor in the type. It would also be critical for diagnostic messages to clearly state both where a null-ref issue might exist, and from what constructor or post-constructor path was potentially causing it. ## Approach C. Backing field's type may or may not be the nullable version of the property's type. The final approach we are exploring is one where the backing field is *conditionally* nullable. The intuition here is to *first* do an initial analysis pass of the get-accessor starting with the assumption that the backing field *is* nullable. If the conclusion of that analysis is that no exit points of the accessor return a maybe-null expression, then the backing field will be considered nullable. Otherwise, it will be considered non-nullable, and the existing language rules will take effect. With this formalization, the following cases work without the need to do anything in the constructor: ```c# string Prop { get => field ?? ""; private set; } // or string Prop { get => field ??= ComputeInitialValue(); } ``` With both properties, the initial analysis is done with the backing field being `string?`. The analysis proves that the get accessor never returns a maybe-null expression. As such, it's "safe" to have a nullable backing field. And because it is safe, we don't need a constructor initializer. However, if you had: ```c# // this will appropriate warn if the constructor or initializer // does not assign to the property as the backing field is `string` // and thus must be assigned before the constructors exit. string Prop { get => field; set => field = value.Trim(); } ``` This seems like a very simple, and limited, form of flow analysis that can make many cases work nicely without difficulty. ## Next Steps 1. Compiler to give rough estimates on the above approaches. 1. Rough specs for these options. 1. Take every example we have and check the experience of each option against them. ================================================ FILE: meetings/working-groups/field-keyword/FK-2024-08-07 Nullability analysis with the `field` keyword.md ================================================ # Nullability analysis with the `field` keyword Table of contents: - [Motivation](#motivation) - [Introduction](#introduction) - [Constructor initialization](#constructor-initialization) - [Field-targeted nullability attributes](#field-targeted-nullability-attributes) - [Property-targeted nullability attributes](#property-targeted-nullability-attributes) - [Null-resilient getters](#null-resilient-getters) - [Alternatives](#alternatives) ## Motivation Some vastly common patterns which are served by the `field` feature are lazy initialization, and defaulting. When a field-backed property is non-nullable, it's required to be initialized during construction. This flies in the face of these kinds of properties. They are well-formed. Warnings requiring constructor initialization would be noisy and unwelcome in these properties and in many similar forms. ```cs public List LazyProp => field ??= new(); public string DefaultingProp { get => field ?? GetDefault(); private set; } ``` These are not speculative use cases. They've been highly requested by the community and have been a core part of the design from the beginning. People have been expecting these scenarios to work. It would be a bitter pill to swallow if the scenarios were onerous to use. First, we'll walk through enabling the user to provide existing nullability attributes explicitly for the backing field just as for manually declared fields, such as `[field: MaybeNull]`. Then, we'll walk through how the compiler builds on this capability to apply these attributes automatically in a simple and intuitive fashion. ## Introduction This proposal brings together a few building blocks: 1. Properties can be considered initialized to non-null in the constructor by calling the setter, just as with `required` and normal flow analysis today. 1. `field`-targeted nullability attributes work just as with manually declared fields. (E.g. `[field: MaybeNull]`) 1. Null-resilient getters, a simple concept that allows maintaining a property's contract of not returning null, while flexibly allowing idiomatic patterns that use a nullable backing field. The following sections will walk through how these concepts play out. ## Constructor initialization In C# today, a non-nullable auto property which has no property initializer and is not marked `required` is forced to be initialized in the constructor. We will preserve this enforcement for all field-backed properties. This enforcement is in the form of the existing warning "CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor." Initializing the property this way, by calling the setter in the constructor, will appease the requirement. This is the case even though a manually implemented setter of a field-backed property does not guarantee that the backing field is actually initialized: ```cs class C { public C() { Prop = "..."; } public string Prop { get; set { if (condition) field = value; } } } ``` While this introduces a possibility for null to end up being returned with no warnings, this level of trust in the setter's implementation is consistent with how C# 8 analyzes property assignments of non-null values: ```cs obj.NullableProp.ToString(); // Warning obj.NullableProp = "..."; // This could be calling `set { }` obj.NullableProp.ToString(); // No warning ``` This is also the same level of trust that is necessary when the burden of initializing is moved via `required`, from the constructor to the caller of the constructor: ```cs new C() { Prop = "..." }; class C { public required string Prop { get; set { if (condition) field = value; } } } ``` See [Constructor initialization alternatives](#constructor-initialization-alternatives) for alternatives that were considered. ## Field-targeted nullability attributes The nullability attributes which are applicable to regular fields may be applied to the backing field of a property using the existing `field:` attribute target. These are the preconditions `AllowNull` and `DisallowNull` and the postconditions `MaybeNull` and `NotNull`. When applied, they affect the nullability analysis of the `field` keyword within the accessors just as they would affect a manually declared field. In cases where the manually-declared field would be required to be assigned in constructors, the field-backed property will be required to be assigned in constructors. ### Postconditions (MaybeNull, NotNull) When `[field: MaybeNull]` is applied on a non-nullable field-backed property, the property will not be required to be initialized in all construction paths. This is consistent with how a manually declared non-nullable field would no longer be required to be initialized when this attribute is applied to it. The reverse is true for `[field: NotNull]` on a *nullable* property. Just like how this would cause warnings requiring a manually declared field to be initialized in all construction paths, applying `[field: NotNull]` to a field-backed property will cause the property to be required to be initialized in all construction paths. #### Examples This will give "CS8603: Possible null reference return" on `field` just as with a manually declared field: ```cs // CS8603: Possible null reference return [field: MaybeNull] public string Prop { get => field; set => field = value; } ~~~~~ ``` The same warning will be shown here on 'get', since `get;` expands to `get => field;`: ```cs // CS8603: Possible null reference return [field: MaybeNull] public string Prop { get; set => field = value; } ~~~ ``` The next example is not real code we expect people to write, but for consistency its behavior will change. In C# 12, the attribute is ignored. In C# 13, applying this attribute to an auto-property will show the same warning as in the previous example, instead of showing "CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor." ```cs // CS8603: Possible null reference return [field: MaybeNull] public string Prop { get; set; } ~~~ ``` ### Preconditions (AllowNull, DisallowNull) When `[field: AllowNull]` is applied on a non-nullable field-backed property, the property will be able to assign maybe-null values to the field without a warning. Here's an example: ```cs [field: AllowNull] public string ResetIfSetToDefault { get => field ?? GetDefault(); set => field = (value == GetDefault() ? null : value); } ``` Automatic `field` nullability is covered by the [null-resilient getter proposal](#null-resilient-getters) below. `[field: AllowNull, MaybeNull]` will be automatically applied due to the `?? GetDefault();` in the example above, and will not be applied if the `?? Default();` is removed. This is safer than manually declaring `[field: AllowNull]` because there is an existing hole in C#'s nullability analysis that allows null to be returned from the property with no warning: ```cs // C# 12. No warning to initialize 'field' and no warning in the getter. [AllowNull] private string field; public string ResetIfSetToDefault { get => field; // Returns null! `?? GetDefault()` is missing set => field = value == GetDefault() ? null : value; } ``` No use cases are known for `[field: DisallowNull]`, but it would be part of the automatic set of behaviors inherited from how manually-declared fields work. ```cs // CS8601 Possible null reference assignment [field: DisallowNull] public string? Prop { get; set => field = value; } ~~~~~ // CS8601 Possible null reference assignment [field: DisallowNull] public string? Prop { get => field; set; } ~~~ // With C# 13, same warning as the previous example. // With C# 12, the attribute is ignored. [field: DisallowNull] public string? Prop { get; set; } ~~~ ``` However, if the property also has `DisallowNull` applied, then there is no warning: ```cs [DisallowNull] [field: DisallowNull] public string? Prop { get; set => field = value; } // No warning ``` ## Property-targeted nullability attributes Nullability attributes applied to the property itself will not be automatically inherited by the backing field. The intended scenario for applying `[AllowNull]` to a property is where the property setter sanitizes nulls to other values before storing in the field. ```cs [AllowNull] public string Prop { get; set => field = value ?? ""; } ``` If the user actually wants to store `null` in the field, the user can either rely on the automatic `field` nullability granted by the [null-resilient getter proposal](#null-resilient-getters) below: ```cs [AllowNull] public string Prop { get => field ?? GetDefault(); set; } ``` Or if that version is not taken, the user will have to add `[field: AllowNull]` as well: ```cs [AllowNull] [field: AllowNull] public string Prop { get => field ?? GetDefault(); set; } ``` ## Null-resilient getters A central scenario for the `field` keyword is lazily-initialized properties. Without automatic nullability, users would have to manually place `[field: MaybeNull]` on every lazily-initialized property in order for the compiler to stop telling the user that the _lazily_-initialized property should be initialized in the _constructor_! ```cs // This is ungainly. [field: MaybeNull] public List Prop => field ??= new(); ``` A null-resilient getter is a getter which continues to fulfill the property's contract of not returning a `null` value, even when `field` is maybe-null at the start of the getter. For properties with a null-resilient getter, the backing field does not need to be initialized, and it may be assigned a maybe-null value at any point, all without risk of returning `null` from a non-nullable property. Null resilience is further extended to mean that there are no nullability warnings when `field` is maybe-null. This is inclusive of checking that no exit point returns a maybe-null expression. We would determine null resilience by analyzing the getter with a pass that starts `field` out as maybe-null and checks to see that there are no nullability warnings. (A less inclusive alternative is considered in [Definition of null resilience](#definition-of-null-resilience).) These getters are null-resilient because there are no nullability warnings when `field` is maybe-null: ```cs get => field ?? ""; get => field ??= new(); get => LazyInitializer.EnsureInitialized(ref field, ...); get => field ?? throw new InvalidOperationException(...); get => throw new NotImplementedException(); get => field!; ``` These getters are not null-resilient because there are nullability warnings when `field` is maybe-null: ```cs get; get => field; get => field ?? SomethingNullable; get => (T[]?)field?.Clone(); get => (T[])field.Clone(); get { string unrelated = null; // Warning return field ?? ""; } ``` On field-backed properties with a null-resilient getter, if the property type is non-nullable and not a value type, and no field-targeted nullability attributes are manually specified, `[field: AllowNull, MaybeNull]` will be automatically applied. Or, equivalently the field type may become nullable. Alternatively, AllowNull could be left off, which is considered in [Full or half nullability](#full-or-half-nullability). In terms of explaining this feature to users, users are more used to thinking in terms of `string?` more than working with attributes. ### Open question: unrelated nullability warnings Should two passes be done, so that the getter's null-resilience is only impacted by warnings that relate to the flow state of `field`? ```cs get { string unrelated = null; // Warning return field ?? ""; } ``` ### Open question: interaction with manually-applied attributes If the user directly applies `[field: MaybeNull]` or `[field: NotNull]`, the getter is not checked for null resilience because the user has already stated the outcome they intend. However, if the user directly applies only the _precondition_ `[field: AllowNull]` or `[field: DisallowNull]` and not the _postcondition_ `[field: MaybeNull]` or `[field: NotNull]`, should the postcondition still be automatically determined based on the null resilience of the getter? Or, between the reading non-nullable property type and the modification of `[field: AllowNull]`, should the ommission of `[field: MaybeNull]` be taken to mean the same as if `[field: NotNull]` had explicitly been stated? In other words, in the following example, does the user intend `[field: AllowNull, NotNull]`, or merely `[field: AllowNull]` with `MaybeNull` automatically applied as long as it is safe due to the getter being null-resilient? ```cs [field: AllowNull] // Manually specified, though it could also be inferred due to null-resilience public string Prop { get => field ?? GetDefault(); set; } ``` ## Alternatives ### Constructor initialization alternatives As proposed, a property assignment in the constructor will appease "CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor," even if the setter does not initialize the backing field. A more conservative alternative to this would be cross-body nullability analysis, analyzing each constructor body as though the setter bodies were inlined into the constructor bodies in order to establish whether initialization is statically guaranteed. This would bring extra safety, but also extra noise. There would be cases where the user would have to suppress initialization warnings on valid code, such as the following: ```cs class C { // With cross-body analysis, this still warns! // CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor public C() { Prop = "..."; } public string Prop { get; set { if (condition) field = value; } } } ``` The warning is due to the cross-body analysis seeing this inlined form, which does not assign on all construction paths: ```cs public C() { if (condition) k__BackingField = "..."; } ``` This requires a suppression, such as `= null!` which initializes the field directly. After adding such a suppression, the user is in a scenario where even totally removing the constructor assignment does not cause a warning. ```cs public string Prop { get; set { if (condition) field = value; } } = null!; ``` The cross-body analysis approach is complex both for implementation and for users understanding how to react to warnings. The working group recommends not pursuing cross-body analysis. A different alternative could be to ignore assignments to manually implemented setters. This would require users to generally add pragmas or `= null!` property initializers even when assigning the property in the constructor, which seems punishing and noisy. ### Definition of null resilience Alternatively, null resilience could be defined more directly around what it means to uphold a non-nullable contract, namely that a null value is never returned. Instead of collecting _all_ nullability warnings during the analysis pass where `field` is initially maybe-null, we would only examine exit points and ensure that every exit point is still returning a not-null expression in spite of `field` being maybe-null. Here's an example of where the difference would show. This would change from not being null-resilient, to being considered null-resilient. This is technically fulfilling the aspect of the property contract that a null value is never returned, even while it implicitly throws NullReferenceException. In the alternative where this getter is considered null-resilient, the user will immediately get a warning in the code which will be confusing. Furthermore, there's no good way to respond to the warning; even when the property is initialized, the getter will be considered null-resilient and thus will allow `field` to hold nulls: ```cs class C { public C(T[] prop) { Prop = prop; } // CS8602: Dereference of a possibly null reference. public T[] Prop { get => (T[])field.Clone(); private set; } ~~~~~ } ``` Not taking the alternative, and not considering this getter to be null-resilient, the warning is exchanged for a construction warning. This warning is desirable because it points the way to properly addressing it by making sure the property is initialized during construction, at which point the warning disappears. ```cs class C { // CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. public T[] Prop { get => (T[])field.Clone(); private set; } ~~~~ } ``` ### Full or half nullability Null resilience means that the backing field may always hold `null` at the start of the getter. The simplest way for the user to think of this is for the field type to be nullable. The field type being nullable could also be thought of as applying `[field: MaybeNull, AllowNull]`, which is what the user would have to manually specify in the absence of automatic `field` nullability. Alternatively, instead of `[field: MaybeNull, AllowNull]`, the automatic nullability could be weakened to `[field: MaybeNull]`. This would retain the benefits of not requiring constructor initialization, but it would require the user to manually add `[field: AllowNull]` in scenarios where the user wants to take full advantage of the null-resilience of the property and assign maybe-null values to the backing field: ```cs [field: AllowNull] // Required if we only go halfway. public string ResetIfSetToDefault { get => field ?? GetDefault(); set => field = value == GetDefault() ? null : value; } ``` The presence of `[field: AllowNull]` also opens up an existing kind of safety hole. Once it's specified, if you remove `?? GetDefault()`, there will be no nullability warnings in spite of the whole which this leaves. This can be shown in the language today: ```cs class C { [AllowNull] private string resetIfSetToDefault; public string ResetIfSetToDefault { // Removed `?? GetDefault()` without warnings! get => resetIfSetToDefault; set => resetIfSetToDefault = value == GetDefault() ? null : value; } } ``` ================================================ FILE: meetings/working-groups/field-keyword/FK-2024-08-07.md ================================================ # `field` keyword working group notes for August 7, 2024 The working group met to review the [Nullability analysis with the `field` keyword](FK-2024-08-07%20Nullability%20analysis%20with%20the%20`field`%20keyword.md) proposal to discuss the direction and come away ready to start on spec updates to present to the LDM. ## Takeaways There was agreement on the changes laid out in the document. This included taking the proposed version of null-resilient getters, instead of the alternatives: - We like the version of null-resilient getters that takes all nullability warnings into consideration, not just ensuring that exit points return non-null expressions. This should be very cheap to implement. - We see no reason to stop halfway and apply only `[field: MaybeNull]` without `[field: AllowNull]`. The latter would just be harder to explain to users. ### Motivation takeaway For the motivation to apply nullability automatically, we felt the motivation was much stronger than simply that it is too much code to write to manually specify `[field: MaybeNull]` in a common scenario. Rather, we feel that these nullability attributes are hard for users to understand and get right. It's much greater of a conceptual hurdle than we want for how commonplace it will be to write simple lazy or defaulting scenarios. ```cs [field: MaybeNull] public string Prop => field ?? GetDefault(); [field: MaybeNull] public List Prop => field ??= new(); [field: MaybeNull] public SomeComponent Prop => LazyInitializer.EnsureInitialized(ref field); ``` ### Additional NRT checking for setters The [constructor initialization](FK-2024-08-07%20Nullability%20analysis%20with%20the%20`field`%20keyword.md#constructor-initialization) section proposes that "CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor" will be appeased by calling the property setter in the constructor, trusting that the property getter will return a non-null value after that call. This has precedent in C#. This was accepted, but a suggestion was made to check the other half of this initialization scenario. The setter could be analyzed as though it had a `MemberNotNull` attribute guaranteeing that the backing field is not null by the time it exits. Applying MemberNotNull-style analysis to the setter would provide useful warnings for two kinds of issues. One, where the field was null during the initial call to the property setter: ```cs class C { public C() { Prop = "..." }; public string Prop { get; set { // CS8602 Dereference of a possibly null reference. Console.WriteLine(field.Length); ~~~~~ field = value; } } } ``` And two, where the property setter can return without initializing the backing field: ```cs class C { public C() { Prop = "..." }; public string Prop { get; set { if (condition) field = value; // CS8774 Member 'field' must have a non-null value when exiting. } ~ } } ``` ### Nullable backing field We preferred the idea of making the backing field nullable rather than keeping it matching the property type while applying `[field: AllowNull, MaybeNull]`. Users are by far more familiar with using nullable types than working with the attributes. The null-resilient getter concept is stronger when its usage and explanation is simpler for users. Making the field nullable at the symbolic level is a difference that would be visible through the IDE, where the user would see a question mark after the type when hovering over the `field` keyword. ### `[AllowNull]` nullability hole The proposal mentions that there is an existing nullability hole in the language when `[AllowNull]` is applied to a field, and that this hole will be inherited by `[field: AllowNull]`: ```cs // C# 12 class C { [AllowNull] private string resetIfSetToDefault; public string ResetIfSetToDefault { // Removed `?? GetDefault()` without warnings! get => resetIfSetToDefault; set => resetIfSetToDefault = value == GetDefault() ? null : value; } } ``` The point was raised that when it comes to `[field: AllowNull]`, we might have an opportunity to close that gap in warnings because the backing field is scoped to the property. For instance, perhaps we could simply create a nullability warning if `[field: AllowNull]` is used without `[field: MaybeNull]`. As mentioned elsewhere, `[field: AllowNull]` is likely only going to be specified in advanced scenarios where automatic backing field nullability (somewhat equivalent to AllowNull and MaybeNull together) isn't what the user wants. ### Open question: unrelated nullability warnings We considered whether unrelated nullability warnings should affect the nullability of the backing field: ```cs // Is this a null-resilient getter? get { string unrelated = null; // Warning return field ?? ""; } ``` Detecting the relatedness of a warning would likely be based on doing two nullability analysis passes, and considering the getter to be null-resilient if there are no _new_ warnings present in a maybe-null-field pass which were not present in a non-null-field pass. This diff would likely not be expensive to do. Any additional expense would also only kick in with new code, and even in new code, we expect complex getters to be quite rare. And in the event that a user needs an escape hatch to skip the null resilience evaluation, the user can apply nullability attributes manually. There could be occasional scenarios with inferred generic type arguments where there is a different error in each pass; however, what would fall out in that case would be a "not null-resilient" detection, which is a safe place to land, still closer to the mark than doing only a single pass. Finally, it might be harder to explain and understand "are there new warnings caused by `field` becoming nullable" compared to understanding "are there any warnings when `field` is nullable." We decided to implement only one pass for now and see how it does in practice during implementation. We can revisit this if it seems that the single pass is leaving something to be desired. ### Open question: interaction with manually-applied attributes We decided that if any nullability attribute is applied using a `field:` target, including the preconditions AllowNull and DisallowNull, then null resilience will not be considered. The user has already entered the space of taking control over the nullability of the backing field. Someone specifying a nullability attribute is likely using it as an escape hatch because the null resilience check wasn't what they wanted. ## Next steps We would like to bring three options to LDM: 1. No specific nullability support at all. Users would be forced to initialize all non-nullable properties, regardless of their bodies. 2. Only support field-targeted attributes. Users could suppress initialization warnings, through explicit addition of particular attributes for particular property bodies. 3. Support field-targeted attributes as well as null-resilient getters. Users would normally not get initialization warnings and would not need to add attributes for safe coding patterns. To get ready to present these options to LDM, a compiler team member will write the nullability spec changes and optionally do some implementation investigations. ================================================ FILE: meetings/working-groups/interceptors/IC-2023-03-20.md ================================================ # Interceptors working group notes for 20th March 2023 We explored the extension shadowing concept and compared with interceptors to determine where to invest to meet our short and long term goals. ## Extension shadowing https://github.com/dotnet/csharplang/issues/7061 The working group had several observations to make about this approach: ### Constant propagation While generalized compile-time constant propagation is intractible, there are ways to scope it down and make the "dispatch blocks" proposed in this approach work well. For example, we can mark specific methods or parameters to indicate that the AOT compiler should do some interprocedural analysis, e.g. `void M([ConstantExpected] string param)`. The AOT compiler already takes hints on whether to do interprocedural analysis based on knowledge of Roslyn compiler-generated symbols, and `[ConstantExpected]` actually already exists. The suggestion in this case would be not to inline these "dispatch block" methods, but instead to trim as though the arguments coming in are constant, eliminating any dead branches within the block. Roslyn has fairly sophisticated emit behavior when switching on strings which complicates the AOT compiler's ability to optimize in cases like these. We might want to give some hints to downstream compilers to make it easier for them. It helps that these extensions will usually be used in the same assembly they are declared in. We also gave some consideration to whether Roslyn could adjust its codegen based on presence of these attributes. For example, Roslyn hasn't really done method inlining to date, but we might introduce it in order to handle scenarios like these. ```cs // ASP.NET library implicit extension RouteBuilderExtension for IEndpointRouteBuilder { public void MapGet( string route, Delegate handler) { } } // Generated.cs implicit extension SpecializedRouteBuilder : RouteBuilderExtension { // produced by the source generator: public void MapGet( string route, Delegate handler, [ConstantExpected] [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = -1, [CallerCharacterNumber] int characterNumber = -1) // https://github.com/dotnet/csharplang/issues/3992 { switch (filePath, lineNumber, characterNumber) { case ("User.main.cs", 5, 11): MapProducts(handler); case ("User.main.cs", 6, 11): MapUsers(handler); // imagine there are many many more cases than these... default: throw new Exception("unknown route"); } } // roughly what the AOT compiler ends up generating: public void MapGet( string route, Delegate handler, [ConstantExpected] [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = -1, [CallerCharacterNumber] int characterNumber = -1) // https://github.com/dotnet/csharplang/issues/3992 { switch (filePath, lineNumber, characterNumber) { // The path which adds a handler for 'Products' is unreachable, so it is elided here. case ("User.main.cs", 6, 11): MapUsers(handler); // imagine there are many many more cases than these... default: throw new Exception("unknown route"); } } } ``` ### Denoting shadowing, and shadowing non-extensions If we went with this approach, we would need extensions to be able to shadow non-extension methods. This feels fairly risky. It isn't clear whether a "shadowable" modifier on base members is needed or not, or if some new usage of the "new" modifier in extensions is needed or not. It feels like requiring too much opt-in will be burdensome, but not enough opt-in might confuse users. Additionally, the actual lookup rules could be quite difficult to get right. Extensions will be able to be declared in complex hierarchies, with members varying in signature and potential "betterness". It's possible new overload resolution rules would be needed in order to make things work here, and it may be very difficult to formulate the right rule, build it and test it. We believe that extension shadowing would be more work across more teams than interceptors would be. ## Interceptors There are some toolability concerns for InterceptsLocationAttribute. It may be problematic if locations aren't normalized. Directory separators, etc. differ across platforms. This means that cross-platform projects would basically only work if their interceptors are created exclusively by source generators during the build. Of course, the "brittleness" problem with locations affects that as well. We're not certain to what extent to invest in IDE support. One particular concern we have is just how many editor features would need to be updated to indicate new information about the meaning of a call. Also, the possibility of interception may make things like incrementally analyzing method edits more expensive. This is what pushed us toward including a syntactic marker like `#Interceptable()` in earlier passes on the feature. However, in terms of program behavior and perf characteristics, we think this is easier to get right for the scenarios we care about than extension shadowing. The concerns about inlining and trimmability are removed. ## Conclusion We think that interceptors are the better of the two approaches to address key scenarios for .NET 8. We will treat interceptors as an *experimental compiler feature* for .NET 8. The feature will have diagnostics for misuse, but it will have a limited amount of design-time traceability. The feature may have breaking changes or be removed in a future release. ================================================ FILE: meetings/working-groups/interceptors/IC-2023-04-04.md ================================================ # Interceptors working group notes for 4th April 2023 The working group reviewed the design doc for the [interceptors compiler feature](https://github.com/dotnet/roslyn/blob/c16a87d4c58a8f6e64f5f6fbb7b885201c7c8dc0/docs/features/interceptors.md), and addressed some of the open questions. **Are the `[InterceptsLocation]` attributes dropped when emitting the methods to metadata?** No. Other attributes which are specially recognized by the compiler (which also are not pseudo-custom attributes) aren't dropped in this way. We don't think it's problematic to do the same here. **Should we permit interceptors declared in file types to refer to other files?** Yes. We think it's fine for the compiler to intercept with a call to an "effectively file-local" interceptor, even if the call is not in the same file as the interceptor. One concern that could come up here is: what if the runtime wanted to add a *reified* concept of `file` accessibility? Then, assuming the compiler adopted it, using file-local types in this way it might actually fail at runtime. However, we don't think adding such runtime enforcement is especially compelling, and feel comfortable blocking the ability to add it in the future. **What kind of user opt-in step should be required to use interceptors?** Interceptors are in an unusual spot compared to preview features we've shipped in the past. - We want to use them in *stable/supported* scenarios, including a stable ``. We think referring to the feature as *preview* isn't quite right, as preview features aren't used as components of *stable* features. This is part of why the label *experimental* was chosen. - We want them to be an implementation detail of certain source generators. If any opt-in step is necessary, we want generators to essentially be able to do it on behalf of users. - We want to be able to change them based on what we learn from their usage in .NET 8. In order to get a significant enough amount of usage, we want them to be used in the mainline "ASP.NET Minimal API with NativeAOT" scenario, without the user having to set an extra property in their project, for example. When discussing what kind of opt-in step to build, we also ended up discussing the entire conception of the feature as "experimental", and the associated set of risks. One problematic scenario we considered is: what happens when someone installs a .NET 9 SDK someday and uses `net8.0`. We might be in a situation where interceptor-based source generators from the .NET 8 targeting pack get used, while .NET 9 has removed or changed support for them. To keep things from failing in that case, we would need to either maintain support in the compiler for as long as .NET 8 is supported, or the generator author (basically, the runtime) would need to service the targeting pack. We think this wouldn't be a problem for generators which ship in the SDK, which are more naturally "synchronized" with the compiler version in use. It was suggested that it's not extremely problematic for this feature to stay around for the .NET 8 support cycle, or even stay around indefinitely, just because a feature is introduced later that handles the scenarios better. **Conclusion** There will be no opt-in step needed to use interceptors. We think that an opt-in step which is only performed by generators doesn't add enough *safety value* to be justified. Instead, we want to convey in documentation that early adoption of interceptors is risky. We will not *publicize* the feature (in blog posts, etc..) as something third-party generator authors should onboard to at this time. ================================================ FILE: meetings/working-groups/interceptors/interceptors-issues-2024-01.md ================================================ # Open issues for interceptors The following issues were identified during the interceptors preview, and should be addressed before the feature is transitioned to stable. Further down there is a "Future" section containing issues which do not block the move to stable, but could be addressed in the longer term. ## Checksums instead of file paths We identified a number of issues with using file paths to identify the location of a call. - Absolute paths, while simple and accurate, break portability (the ability to generate code in one environment and compile it in another). - Relative paths require generator authors to consume contextual information about "where the generated file will be written to" on disk. This complicates the public API design and feels arbitrary. - We considered also introducing "project-relative" paths for use in InterceptsLocationAttribute, which would reduce the amount of public API bloat. However, this effectively introduces a new requirement on compilations created through the public APIs: they need to have the path resolvers in compilation options properly configured to indicate where the "project root" is. Otherwise, the Roslyn APIs can't correctly interpret the meaning of source code in the compilation. We finally arrived at checksums as a more satisfactory way of identifying the file containing the intercepted call. This essentially does away with the portability and complexity concerns around paths. It also pushes us toward an opaque representation of "interceptable locations". In the design we have now, it's expected that InterceptsLocation attribute arguments are produced solely by calling the public APIs. We don't expect users to create or to interpret the arguments to InterceptsLocationAttribute. See https://github.com/dotnet/roslyn/issues/72133 for further details on the public API design. https://github.com/RikkiGibson/roslyn/blob/interceptablelocation/docs/features/interceptors.md includes more precise details on the location format. **Resolution**: The described design was approved by LDM on [2024-04-15](https://github.com/dotnet/csharplang/blob/d5bab90e846f9b3e9a7a4e552249643148f0194d/meetings/2024/LDM-2024-04-15.md#interceptors). ## Need for interceptor semantic info in IDE We added public API which allows determining if a call in a compilation is being intercepted, and if so, which method declaration is decorated with the InterceptsLocationAttribute referencing the call. See https://github.com/dotnet/roslyn/issues/72093 for further details. **Resolution**: No LDM decision here, this was informational to help LDM understand the larger context which the design sits in. ### `` property We introduced an MSBuild property `` as part of the experimental release of the interceptors feature. A compilation error occurs if an interceptor is declared outside of one of the namespaces listed in this property. We suggest keeping this property under the name ``, eventually phasing out the original name. This will help us get a better level of performance with the `GetInterceptorMethod` public API, by reducing the set of declarations which need to be searched for possible `[InterceptsLocation]` attributes. **Not yet resolved.** As of 2024-05-13 the property is still present in the implementation as ``. We'll seek confirmation with LDM whether to proceed with the suggested requirement that generators and/or users specify `` (perhaps phasing out the use of the original property name). ## Future (possibly post-C# 13) ### Intercepting properties Currently the interceptors feature only supports intercepting ordinary methods. Much like with partial methods and the upcoming partial properties, users want to be able to intercept usages of more kinds of members, such as properties and constructors. - UnsafeAccessor does not support properties directly. Instead, a given usage of UnsafeAccessor decorates a method which calls through to a single property accessor. - Consider: What happens when we want to intercept `obj.Prop += a`? We can't choose a single method to replace usage of `Prop`. - Conclusion: We should probably favor intercepting a property with an extension property. We are likely blocked here until we ship extensions. ### Intercepting constructors and other member kinds For all the member kinds we want to support, we will need to decide how usages of each member kind are denoted syntactically. In terms of signature matching requirements, we should try to be consistent with [UnsafeAccessorAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.unsafeaccessorattribute?view=net-8.0), which also works by referencing members in attribute arguments. For method invocations like `receiver.M(args)`, we use the location of the `M` token to intercept the call. Perhaps for property accesses like `receiver.P` we can use location of the `P` token. For constructors should use the location of the `new` token. For member usages such as user-defined implicit conversions which do not have any specific corresponding syntax, we expect that interception will never be possible. ### Interceptor signature variance and advanced generics cases In the experimental release of interceptors, we've used a fairly stringent [signature matching](https://github.com/dotnet/roslyn/blob/main/docs/features/interceptors.md#signature-matching) requirement which does not permit variance of the parameter or return types between the interceptor and original methods. We also have limited support for [generics](https://github.com/dotnet/roslyn/blob/main/docs/features/interceptors.md#arity). Essentially, an interceptor needs to either have the same arity as the original method, or it needs to not be generic at all. ASP.NET has a [use case](https://github.com/dotnet/aspnetcore/issues/47338), which in order to address, we would need to make adjustments in both the above areas. ```cs class Original { public void M1() { API.M2(() => this); } } class API { public static void M2(Delegate del) { } } class Interceptors { [InterceptsLocation(/* API.M2(() => this) */)] public static void M3(Func func) { } } ``` In `M3`, the generator author wants to be able to use the the type parameter `TCaptured` within the return value `=> this`. This means that we would need to permit a parameter type difference between interceptor and original methods where an implicit reference conversion exists from the interceptor parameter type (`Func`) to the original method parameter type (`Delegate`). We would need to start reporting errors when the argument of an intercepted call is not convertible to the interceptor parameter type. Any change we make in this vein, we should be careful has no possibility for "ripple effects", e.g. use of an interceptor changing the type of an expression, then changing the overload resolution of another call, changing the type of a `var`, and so on. We would also need to define how the type arguments to the `M3` call are determined. This might require doing a type argument inference on the intercepted "version" of the call. There are more specifics which need to be investigated here before we can be sure how to move forward. ASP.NET has indicated this can wait till later in the .NET 9 cycle, so we'd like to push this question out until we've addressed the more urgent aspects of the feature design. ### Design meetings - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-15.md#interceptors ================================================ FILE: meetings/working-groups/nullability-improvements/NI-2022-10-24.md ================================================ # Nullability improvements working group meeting for October 24th, 2022 The *nullability improvements* working group met to look at issues which were raised during LDM triage and/or have received significant community engagement. ## Task covariance https://github.com/dotnet/csharplang/issues/3950 The general scenario that people have complained about is: ```cs using System.Threading.Tasks; #nullable enable public class C { public Task M() // not async { return Task.FromResult(""); // type of return value is Task, which warns when converting to Task. } } ``` We saw that this was reported on a few different occasions. We think this is a fairly annoying papercut and that it would be nice to be able to make it "just work". A few options we considered to that end: ### Option 0: special-case Task IEquatable is already "special" in the compiler in that it is "nullable-contravariant", not fully contravariant: `IEquatable` is convertible to `IEquatable`. This limited form of contravariance is primarily present for compat reasons, though there are design reasons one might want it to work this way as well. ```cs IEquatable e1 = ...; IEquatable e2 = e1; // ok IEquatable e3 = e2; // warning CS8619: Nullability of reference types in value of type 'IEquatable' doesn't match target type 'IEquatable'. ``` Because only the same few types have been used in the samples where people complain about *nullable-invariance*, we think it might make sense to just expand on it. Let the compiler assume that `Task` and `ValueTask` are *nullable-covariant*. We wouldn't check the implementations of these well-known types to decide if they are safely handling the variant type parameter. We could also extend this to say that anything [task-like](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.0/task-types.md) is assumed to be *nullable-covariant*. ### Option 1: [NullableOut] and [NullableIn]? Introduce special attributes which denote that type parameters are *nullable-covariant* or *nullable-contravariant*. ```cs interface IEquatable<[NullableIn] T> { } class Task<[NullableOut] T> { } struct ImmutableArray<[NullableOut] T> { } ``` We can imagine other interfaces which might want the *nullable-contravariance* used by `IEquatable`, such as `IEqualityOperators`, where `TSelf` is meant to be constrained to exactly the type which implements the interface, but the type argument may still vary on its nullability for incidental reasons (i.e. due to other constraints which happen to be applied to handle all the things a type parameter needs to be able to do). ```cs public interface IEqualityOperators where TSelf : IEqualityOperators? { static abstract TResult operator ==(TSelf? left, TOther? right); static abstract TResult operator !=(TSelf? left, TOther? right); } ``` However, we think that a scenario where someone will actually be inconvenienced by this limitation is rare--to the point that we did not take the effort to actually sketch out such a scenario. We think that if we added these attributes to the platform, we would want to add a practical amount of enforcement to ensure that nulls do not sneak in unexpectedly. For example, when `[NullableOut]` is used on a type parameter, we might want a warning if inputs of that type might be non-nullable. We're not sure how extensive this enforcement would need to be. In practice the amount of effort needed to provide a good user experience with these attributes could be high. ```cs class Task<[NullableOut] T> { private T result; // maybe this needs a warning? internal void SetResult1(T t) // warning: input of type 'T' should be nullable because the type parameter is nullable-covariant. { result = t; // ok } internal void SetResult1(T? t) // ok { result = t; // warning: possible null reference assignment. } } ``` Additional concerns we have with this approach: 1. It may not be obvious for API authors when they need to add this attribute. Pretty much anything that exposes a read-only value of a parameterized type could be a good "candidate" for nullable covariance, for example. 2. Nullable-oblivious types, or types the end user doesn't control in general, won't have this attribute, and users will continue to get warnings on variant conversions in those cases. - To elaborate: Types whose original definitions are oblivious are still nullable-invariant in a nullable-enabled context. The constructed type `ObliviousGeneric` is not convertible to `ObliviousGeneric`. ### Option 2: `` in class types Eric Lippert's [StackOverflow answer](https://stackoverflow.com/questions/2733346/why-isnt-there-generic-variance-for-classes-in-c-sharp-4-0/2734070#2734070) sheds some light on why type parameter variance was not implemented for class types originally. Since then we have added `readonly struct` and have considered `readonly class`, and it might be interesting in the future to extend readonly types to permit covariant type parameters, for example. However, to address the case people are actually complaining about, it feels a bit like using a wrecking ball to hit a nail. `Task` is not readonly, and any change to actually make it covariant at the language level would be more of a hack. ### Option 3: status quo (suppression needed or explicit type arguments) The two workarounds currently recommended for the original scenario are to either suppress the conversion warning with `!` or to provide an explicit type argument when creating the object. ```cs Task x1 = ...; Task x2 = x1!; // could be doc'ed usage of suppression x2 = Task.FromResult(""); // ok ``` The current articles for `!` emphasize its use to assert that a variable is *not_null*. There's little coverage that the operator will suppress these conversion warnings as well. Adding this information, especially linked to the F1 service for the specific warning generated. ### Conclusion **Option 0 (special-casing)** would be most palatable, but we're not sure yet whether to prioritize making any change here. It will depend on how the cost/benefit of addressing this compares to other nullable issues on the docket. Ultimately, we think the main issue here is that when users encounter this, they will naturally think the compiler is telling them they're doing something wrong. It may take some research for users to conclude that actually, the conversion is fine, and they should just suppress the warning or provide the additional type information on the input side. Any way that we can make that process better (i.e. documentation, samples, editor support) would also help here. ### Postscript After meeting I wondered if it would be possible to reduce warnings in some cases by using the target-type of an expression as an input to the nullable reinference process. This is slightly similar to what is planned for `IList e = ["a", "b", "c"]` in [collection literals](https://github.com/dotnet/csharplang/issues/5354). Essentially, in `Task task = Task.FromResult("value")`, the type of `task` would be contributed to the inference of `TResult` in `Task Task.FromResult(TResult)`. - This step would probably only be done for nullable reference types, and would essentially be completely new, so it's difficult to say whether the design and implementation effort needed would be appropriate for the magnitude of the problem. - It's possible this would introduce a cyclic dependency in the reinference process which makes the approach unviable. - If it *did* work, it would address "factory method" issues in general, i.e. `ImmutableArray.Create()`. ## Next Our plan for the next working group is to do a higher-level overview of more nullable issues, and then dive into further depth in future sessions. - `[MemberNotNullWhenComplete]`: https://github.com/dotnet/csharplang/discussions/5657 - LINQ: https://github.com/dotnet/roslyn/issues/37468 - Defaultable value types: https://github.com/dotnet/csharplang/discussions/5337 ================================================ FILE: meetings/working-groups/nullability-improvements/NI-2022-11-01.md ================================================ # Nullability improvements working group meeting for November 1st, 2022 ## `[MemberNotNullWhenComplete]` https://github.com/dotnet/csharplang/discussions/5657 Users are requesting the ability to specify postconditions which are only fulfilled after a task-returning method is awaited. We're seeing a relatively high amount of engagement (upvotes and thumbs up) on this compared to many other issues in the nullable space. A community member observed that there are very few usages of `[MemberNotNull]` on async methods in open source code, which bodes well for the possibility of breaking the existing behavior if we think it's the right thing for our users. We also think that the existing behavior on async methods is just broken. Currently, when an async method throws an exception, we treat it as fulfilling the postcondition, much like for a non-async method. This is because we think the caller won't actually be able to use whatever variable was expected to be not-null after the call--thus, nothing to warn about. However, in reality, when an async method throws an exception, it is packaged up into a Task and returned to the caller. It is not thrown until the task is awaited. Because pretty much any code can throw an exception, **no async method is able to reliably fulfill nullability postconditions as currently designed**. This is similar to an issue we recently fixed in [definite assignment](https://github.com/dotnet/roslyn/issues/43697#issuecomment-1267566839). [SharpLab](https://sharplab.io/#v2:C4LgTgrgdgPgAgJgIwFgBQcAMACOSB0AIgJYCGA5lAPYDOwxAxjfgMJUAmApgIJSkA2ATxrEaAbnRZcSAKwS0kgMy4E2FugDe6bDumYA/NmLBOAW3m7s23QG0AsmYBGnMADkqwVxH78AFH1NOKgAzX2MzAEoIgF1rHThlOAAOXAA2bDtuGkEoBl8IuOwtNEtLAAlOfgAHF3yLUp1w02wAXmwAIlJ2+oa4AE40/ABNYkr2OuwAeknsamwAd1IwKGIochpCgF90QoTpdLgAFmwK6tqI1oA+bGAACzAqednOJ4BRAA8GTir6Kig6wq7BAIQrFBo6FjYBitZ5PFgAkrggBuSxuMIY+Ey2VyE2m2E4n2+vygRhoN3ujxJq3YxDAnAYwCE5IeEHItxOlRqYGwpCg7CMuX4EC4/NW5M4uAA7Jx+XBUoVLBimvgACpUADKwDAq3IuJmc0Wyx1ABpsK4AEqvHnAbCQKD0QJbdCbIA) ```cs public class C { string? item; [MemberNotNull(nameof(item))] public async Task MAsync() { Helper(); item = "a"; await Task.Yield(); // no warnings } public static void Helper() => throw new Exception(); public static void Main() { C c = new C(); var t = c.MAsync(); // exception is thrown indirectly through Helper and included in the returned Task c.item.ToString(); // no warning, NRE at runtime } } ``` We think the most viable option to fix the issue here is to change the existing meaning of `[MemberNotNull]`, as well as other postconditions like `[NotNull]`, on Task-returning methods (methods which return `await`-able things), such that the postconditions are only applied if the call is *immediately awaited*. The only safe alternative would be to warn if a nullable postcondition attribute is used *at all* on an async method, which would be unfortunate. Thus, the new "happy path" usage would look something like the following: ```cs public class C { string? item; [MemberNotNull(nameof(item))] public async Task MAsync() { await Task.Yield(); // no warning item = "a"; } public static void Main() { C c = new C(); await c.MAsync(); c.item.ToString(); // ok } } ``` The requirement to change behavior at call sites for any "awaitable" method call is because `async` is treated as an implementation detail, and it's not possible to determine whether a method from metadata is `async` or not. We'd prefer to have uniformity in usage across both source and metadata. The requirement to *immediately await* rather than being able to store the task in a variable and await it later, for example, is because we won't be able to be sure about the ordering of effects in such cases: ```cs var task = c.MAsync(); c.item = null; await task; c.item.ToString(); // ok? ``` Although we're not sure such methods would occur in real code, we also considered the behavior of an API like the following: ```cs namespace System { public static class File { public static async Task ExistsAsync([NotNull] string? path); // throws an exception if path is null } } public class C { public static async Task M(string? path) { if (await File.ExistsAsync(path)) { path.ToString(); // ok } } } ``` **Conclusion**: Overall, we are mildly favorable toward this feature request. We think it's possible to design the behavior our users actually want at a reasonable implementation cost--though if further examination reveals that cost is larger than expected, we may not be able to prioritize it. When we can, we'll prepare a full proposal for the behavior we want for the full language design meeting to consider. ## Defaultable value types https://github.com/dotnet/csharplang/discussions/5337 The "hero scenario" for this feature is: ```cs // ImmutableArray ImmutableArray arrOpt = default; foreach (var item in arr) // no warning, NRE at runtime { Console.Write(item); } ``` It would be nice if the language could understand when non-nullable reference type fields within structs could be null, and whether members of the struct are safe to use as a result. However, we did decide early on in the nullable reference types design to leave structs as a gap, just like we did with `new string[length]`--the language provides no assistance in determining whether an array of non-nullable reference types contains all non-null values. It's particularly unappealing to introduce new syntax and attributes just to support this. We're curious if the situation could be improved without actually adding those--for example, how far could we take things by just tracking flow states for structs which contain non-nullable reference types, and slightly extending the usage/meaning of existing nullability attributes. However, it's not clear if we would reach a complete and usable enough state through such a strategy, especially when considering the space of possible behaviors for properties when accessed on a `default` receiver--the property could throw an exception, return the default value of the property type, return a non-null "placeholder" value, and so on. It's not clear what set of behaviors would meet the "table stakes" of usability while also not requiring a large amount of conceptual and implementation overhead. **Conclusion**: We think it's understood at this point that users must use caution when working with structs, and especially when they expose structs in libraries. This fact, combined with the relatively low level of engagement/enthusiasm on the discussion, leads us to think that this feature would not be a good use of our resources at this time. ## LINQ https://github.com/dotnet/roslyn/issues/37468 We're seeing a fairly high upvote rate on issues tracking LINQ usability with nullable. The scenario where people are experiencing the most pain is: ```cs IEnumerable M(IEnumerable items) { return items .Where(i => i != null) .Select(i => Widget.Parse(i)); // false warning: converting 'i' to non-nullable } class Widget { public static Widget Parse(string item) => throw null!; } ``` We found that the equivalent scenario using query syntax appears to work at first glance. However, it is actually suppressing nullable warnings that we would expect. The query variable has oblivious nullability and its state is not tracked across operations in the query. It feels like the compiler should be able to flow nullable state information from the `where` to the `select` clause in examples like the following, but we haven't yet had the opportunity to make it work. Ideally, whatever solution we adopt to improve the LINQ experience will have parity between both the call-chaining and query forms. ```cs IEnumerable M(IEnumerable items) { return from i in items where i != null select Widget.Parse(i); // ok } IEnumerable M(IEnumerable items) { return from i in items select Widget.Parse(i); // should warn, but doesn't } ``` We had a few thoughts and concerns when considering expanding on the support here: LINQ is extensible. You can define your own operators, and you can also reimplement the LINQ "suite" on non-IEnumerable containers while getting support for the query syntax. We would probably want to ensure that any usage of the "query pattern" would also benefit from any special support we add here. We would have to assume that the implementation adheres to the standard expectations for those operators. It might be OK to just target the "90% case" versus providing full and rich support over all the standard operators. We also think this use case is valuable enough to introduce new attributes if necessary. We considered whether it would be good enough to have a *special case* just for `Where(x => x != null)`, where the return value is somehow inferred to be `IEnumerable` and that type information flows into subsequent usages. It seems unfortunate to "miss" even slightly more complex cases like the following: ```cs IEnumerable Transform(IEnumerable items) { return items .Where(i => i.field1 != null) .Select(i => new C2 { field2 = i.field1 }); // would still warn } class C1 { public string? field1; } class C2 { public required string field2; } ``` We also think it could be useful to somehow flow the state of collection elements across statements, like when using the Any or All operators: ```cs List items = ...; if (items.All(i => i != null)) { UseNotNullItems(items); } ``` However, much like the *immediately awaited Task* scenario, it seems difficult to know how to "invalidate" the state of a collection once it is tested in this manner--for example, if someone calls `items.Add(null)` on a mutable collection after checking `.All()`. We might be able to reach a worthwhile level of tracking in such scenarios, but it's not clear. **Conclusion**: We're interested in improving support here. We will hold a dedicated meeting for this topic in order to more thoroughly explore the problem and decide what level of support we want to provide here. ## Follow-up from last meeting After our last meeting we continued to think about the [Task covariance](https://github.com/dotnet/csharplang/issues/3950) issue, and have concluded that we should update the language documentation to capture the *suppress nullable conversion warnings* use case for the `!` suppression operator. Currently, we think this is the full extent that we want to invest in this specific area. ================================================ FILE: meetings/working-groups/nullability-improvements/NI-2022-11-07.md ================================================ # Nullability improvements working group meeting for November 7th, 2022 Our focus today was on [improving nullable analysis of LINQ queries](https://github.com/dotnet/roslyn/issues/37468). ## Special handling of `.Where(x => x != null)` We considered a solution where we specially handle null tests on the collection element itself. For example: ```cs IEnumerable M(IEnumerable items) { return items.Where(x => x != null); // no warning. } ``` We think we could introduce a rule where the type of a `Where()` invocation expression is changed depending on the predicate which is passed in. (Specifically, the type argument of the return type would change its top level nullability.) However, we are not sure this solution is generally useful enough. It has been also been [suggested](https://github.com/dotnet/runtime/issues/30381) on multiple occasions to add a `.WhereNotNull()` LINQ operator to the core library. This suggestion has been rejected by the runtime team. We found a number of drawbacks to this solution, including the fact that query expressions would need to be updated to use this operator, and that query providers would need to be updated to understand the operator. ## Field tracking within collection elements We think scenarios like the following are common and important to handle: ```cs IEnumerable M1(IEnumerable enumerable) { return enumerable .Where(x => x.Item != null) .Select(x => new HasNotNullable(x.Item)); // ok } record HasNullable(string? Item); record HasNotNullable(string Item); ``` Note that this *wouldn't* be handled by an approach which only updates the top-level nullable state, i.e. by adding a `.WhereNotNull()` operator to the core library. We want to support a complete and coherent mental model for the analysis of these queries as much as possible. Users should be able to think of the above query as being *morally equivalent* to the following iterator method: ```cs IEnumerable M2(IEnumerable enumerable) { foreach (var x in enumerable) { if (x.Item != null) { yield return new HasNotNullable(x.Item); // ok } } } ``` We also think that the filtering performed by operators like `.Where()` should be able to be consumed by custom downstream operators. ```cs enumerable .Where(x => x.Item != null) .SelectAsArray(x => new HasNotNullable(x.Item)); // state of 'x.Item' is still propagated, although it seems like the compiler can't make a hard assumption that this lambda is operating on the collection element. ``` We think this can be accomplished by associating a *slot* with a type argument, and when values of that type are materialized, we would copy the state from that *slot* in as the initial state of those values. For example: ```cs // after visiting the 'Where()' invocation, we associate the type 'HasNullable' with a slot representing the when-true branch of 'x.Item != null' in the lambda. enumerable.Where(x => x.Item != null) // Parameter 'x' has type 'HasNullable', and 'HasNullable' has an associated slot. We copy this slot as the initial state of the parameter. .Select(x => new HasNotNullable(x.Item)); ``` We'd like to avoid thinking of this as a bona-fide type system addition--for example, where unspeakable types are inferred and flow through to represent the combination of field nullable states. As much as possible, we want to leverage the analogy of assigning variables whose field states were already modified: ```cs var x = GetHasNullable(); x.Item = "not null"; var y = x; y.Item.ToString(); // no warning in currently shipped compiler ``` Currently, we are still at the stage of *heavily* handwaving questions about the lifetimes of such slots and how to propagate this state only to variables which are actually associated with the tests from filtering lambdas. ```cs enumerable.Where(x => x.Item != null) .Select(x => new HasNotNullable(GetHasNullable().Item); // should still warn, since we didn't test the return value of 'GetHasNullable()'. ``` More consideration is needed here. We'll try to organize our thoughts and explore this approach in more detail when we meet again. ## Identifying filtering operators We considered how to identify when a method should get the "`Where()` method treatment". For example, operators like `First()`, `Last()`, `SkipWhile()` and `TakeWhile()` would also want to propagate collection element nullable state based on the lambda argument. A few basic options we considered for identifying these methods include: 1. Look specifically for `System.Linq.Enumerable.Where()` `System.Linq.Enumerable.First()`, etc., and don't include user-defined operators or operator "suites" on different collections. 2. Look for any method which is callable on an enumerable receiver and which matches the naming pattern, i.e. `MyExtensions.Where(this MyCollection, Func)` would be automatically opted in to the new analysis behavior. This *slightly* resembles the translation of [query expressions](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1118-query-expressions). 3. Introduce an attribute which indicates a method output is being *filtered* by a predicate. We sketched out a few possible names and shapes for "filter" attributes here: ```cs public static IEnumerable Where<[NotNullWhenLambdaFlowStateNotNull]T>(this IEnumerable enumerable, Predicate predicate); public static IEnumerable Where(this IEnumerable enumerable, [Filter] Predicate predicate); // or [NullFilter]? [return: Filter(nameof(predicate))] public static IEnumerable Where(this IEnumerable enumerable, Predicate predicate); ``` At this point, we think that filtering methods should onboard deliberately to the new analysis behavior, and we want user-defined operators, perhaps ones with names or shapes we didn't anticipate to be able to participate where possible. Therefore, we are leaning towards introducing a filtering attribute of some kind. When we meet again we will present and consider some more concrete designs for such an attribute. ================================================ FILE: meetings/working-groups/nullability-improvements/NI-2022-11-22.md ================================================ # Nullability improvements working group meeting for November 22nd, 2022 We continued our discussion on [nullable LINQ analysis](https://github.com/dotnet/roslyn/issues/37468), focusing particularly on introducing a "filtering" attribute which allows nullable state within predicate lambdas to flow out of the resulting collection. We used the following attribute declaration as a starting point: ```cs namespace System.Diagnostics.CodeAnalysis; /// /// Can be applied to a parameter of a delegate type such as which has a single parameter and returns . /// /// When this attribute is used, the language assumes that the predicate returns true for all values of its input type which are returned by the containing method. /// [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] public sealed class NullableFilterAttribute : Attribute { } ``` Where usage would look like the following: ```cs using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace System.Linq; public static class Enumerable { public static IEnumerable Where( this IEnumerable source, [NullableFilter] Func predicate) { // ... } } ``` When a `[NullableFilter]` parameter is used, it is associated with the parameter type of the delegate. In this case, that parameter is `predicate`, the delegate parameter's type is `TSource`, and the containing method is `Where`. All values of type `TSource` which *flow out* of `Where` are assumed to have been passed in to a call to `predicate`, and a `true` value was returned from each call. One of the first bits of feedback was on the name. We think this attribute is not itself about nullability. It's just that if lambdas passed as arguments for the predicate parameter do null tests, then null state information about the collection elements can propagate through. Other forms of static analysis could easily take advantage of the same assumption, that the predicate returned true for all elements returned from the containing method. Therefore, we think perhaps the attribute name `[Filter]` or `[Predicate]` would be more suitable. We want to ensure we consider the suitability of this attribute for other APIs in [`System.Linq.Enumerable`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=net-7.0) that have overloads that apply a predicate to a sequence, such as: - `First` - `Last` - `TakeWhile` - ... Relating to the above, we'd like to consider what the impacts are with various signatures, especially with various return types. For example, for `Collection Method([NullableFilter] Func predicate)`, do both of the `T`s in the return type get the additional flow state from the predicate? We're not sure whether we should have both "predicate returned `true`" and "predicate returned `false`" flavors of the attribute. If the implementation wanted to guarantee that a predicate returned `false`, they would need something like `[Filter(predicateReturnValue: false)]`. If we're not able to find compelling examples where people would want to adopt in this manner, we'll probably want to stick with just the "predicate returns `true`" flavor. We also briefly considered whether this attribute can affect other outputs besides returns. For example, what if you had a method `void Where(IEnumerable, [NullableFilter] Func, out IEnumerable)`? At this point, we think there isn't a scenario involving `out` parameters, but we'll want to keep it in mind as we proceed with the design. It's worth noting that attributes like `[NotNullIfNotNull]` can be applied to both return values and `out` parameters (where the attributed thing is the *output* affected by some named input.) We noted that some indirect usages which rely on a more "set theory" approach are unlikely to result in useful changes to flow analysis. ```csharp // Bad usage that would be a problem if the attribute is on the method, not on the lambda expression: var allNullValues = someCollection.Where(i => i is null); // IEnumerable allNullValues[0].ToString(); // warn because the predicate didn't contain a null check. var dataToWrite = someCollection.Except(allNullValues); // lost track ``` We are tentatively thinking of the state-propagation which occurs for a filtering attribute as *modifying/reinferring type arguments present in output positions*. ```cs record Widget(string? Prop) { } var en = widgets.Where(widget => widget.Prop != null); // IEnumerable foreach (var item in en) { item.Prop.ToString(); // ok } ``` ```cs // lambda for 'lastChance' can't assume T passed the filter (it didn't in this case) public static IEnumerable Where(IEnumerable source, [Filter] Func primary, Func lastChance) { foreach (T item in source) { if (primary(item)) yield return item; else if (lastChance(item)) yield return item; } } ``` We considered would happen when a filtered enumerable `en` is enumerated multiple times, potentially modifying its contents. Our conclusion is basically that we don't want to attempt to track changes to the collection element across statements. We think it is best to avoid any scheme where the initial state of elements in `en` depends on where `en` is used. ```cs var en = widgets.Where(widget => widget.Prop != null); foreach (var item in en) { item.Prop = null; } foreach (var item in en) { item.Prop.ToString(); // no warning and NRE at runtime } ``` We think the NRE present above is similar to aliasing scenarios which already don't work: ```cs var item = new Widget("a"); var item1 = item; item1.Prop = null; // doesn't apply to `item` item.Prop.ToString(); // NRE but no warning ``` We also note that the language assumes that member method calls don't change the state of fields, unless specifically annotated in the signature: ```cs var en = widgets.Where(widget => widget.Prop != null); // When does the compiler decide a mutation has taken place? // mutation and repeated enumeration foreach (var item in en) { item.M(); } foreach (var item in en) { item.Prop.ToString(); // ? } // similar to the following method scenario: local(item); item.Prop.ToString(); // kaboom, no warning void local(Widget i) => i.Prop = null; ``` There is still some ambiguity about when the collection element flow state disappears, particularly when the collection element state is contained in some mutable collection which we go on to mutate. ```cs var en = widgets.Where(widget => widget.Prop != null); var arr = en.ToArray(); // T[] ToArray(this IEnumerable) arr[0].Prop.ToString(); // ok? Do we remember that we passed the `widget.Prop != null` test? foreach (var item in arr) { item.Prop.ToString(); // ok } // ToArray() followed by mutation // what happens here? do we just accept it? // Is this itself a warning--this is not a *Widget-with-non-null-Prop*, it's a *Widget-with-null-Prop*. arr[0] = new Widget(null); foreach (var item in arr) { item.Prop.ToString(); // do we still expect non-null-Props here? } // Concat() var en1 = en.Concat(new Widget(Prop: null)); // "unify" into a standard Widget type foreach (var item in en1) { item.Prop.ToString(); // warning } // Null tests through methods with nullable postcondition attributes var en1 = widgets.Where(widget => !string.IsNullOrWhitespace(widget.Prop)); foreach (var item in en1) { item.Prop.ToString(); // ok (we expect the compiler to understand that widget.Prop is not-null when the predicate returns true) } ``` We think that this scheme should be naturally composable, and for example, allow a sequence of `Where()` calls to just do the right thing. ```cs record Widget(string? Prop) { } var x = en.Where(w => w is not null) // IEnumerable .Where(w => w.Prop is not null); // IEnumerable ``` There won't be checking of the implementation for correctness, so a Where method like the following will result in a misleading analysis at the call-site: ```cs IEnumerable Where(this IEnumerable input, Func predicate) { foreach (var item in input) { if (predicate(item)) { } // ignore result of the predicate call yield return item; } } ``` As mentioned earlier, we want to take care to avoid inventing *flow-dependent typing* here. So there is some question on a technical level about whether to associate the collection element slot information with variables and expressions or with types. Also, we don't think this collection element information should appear in the public API for types. Review of https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/nullable-reference-types-specification.md could be helpful when we go deeper in attempting to formalize this. ## Implementation brainstorming A subset of the working group brainstormed briefly about how the implementation would work. This part is more speculative than the rest, and depends on the design leading to it being fully formed and approved by the language design team. The `NullableWalker` in Roslyn has a structure called `VisitResult` which is produced whenever an expression is visited. It currently contains an rvalue and lvalue flow state, and we think with this feature we might augment it to also include a map of "filtered type" to "element flow state". For example, `en.Where(w => w.Prop is not null)` would have an entry for the type of `w` which points to the final state-when-true after `w.Prop is not null`. Then, when we access a member off of `en.Where(...)` whose type is a *filtered type*, we can get the slot for that element flow state and use it as the initial state of variables of the filtered type. ## Next In our next meeting we plan to dive deeper into the mechanics of the filtering attribute, and hopefully reach a point where we understand the solution we want well enough to write a proposal for it. ================================================ FILE: meetings/working-groups/params-improvements/PI-2022-10-25.md ================================================ # Params Improvements Working Group Meeting for October 25, 2022 This was the first meeting of the _`params` improvements_ working group. We discussed extending `params` support to collection types other than arrays. ## Agenda 1. `params IEnumerable`, `IList`, etc. 1. `params List`, `ImmutableArray`, etc. 1. `params Span`, `ReadOnlySpan` 1. Overload resolution and backward compatibility ## Discussion ### Interfaces and collection types: `params IEnumerable`, `List`, etc. Perhaps the most common request is support for `params IEnumerable`. This is potentially useful for scenarios where the `params` method simply iterates over the collection, and where callers might have an `IEnumerable` instance rather than a `T[]` and are calling the method with _normal form_. However, when callers use _expanded form_, a `params IEnumerable` may not provide any advantage over `params T[]` to the caller or callee. If we extend `params` to support `IEnumerable`, we should consider extending `params` support to other interface types and concrete collection types as well. If so, what are the requirements of supported types? The [_collection literals working group_](https://github.com/dotnet/csharplang/tree/main/meetings/working-groups/collection-literals) is already defining similar requirements for _constructible collection types_. Those types can be `interface` types or concrete `class`, `struct`, and `ref struct` types. The _collection literals_ proposal specifies how the corresponding collection instances for _constructible collection types_ are constructed and populated. Any additional `params` types should match the _collection literals_ approach. That is, for any particular `params` type, the code generated for `WriteLine(fmt, a, b, c)` should be equivalent to the code generated for `WriteLine(fmt, [a, b, c])`. That said, the difference between an _expanded form_ call (with implicit collection) and a call using a _collection literal_ is just two characters, `[]`, so the advantage of `params` over _collection literals_ seems small. Given that, we concluded we _should not extend `params`_ to arbitrary interfaces or collection types. ### Spans: `params ReadOnlySpan`, `Span` `ReadOnlySpan` has a couple of advantages over other potential `params` types. 1. With runtime support, the compiler could create spans at call sites without allocating an underlying array on the heap. 1. `T[]` and `Span` are implicitly convertible to `ReadOnlySpan`. That means `params ReadOnlySpan` is a reasonable alternative to `params T[]` in new APIs. And for existing APIs, adding a `params ReadOnlySpan` overload could reduce allocations for current callers of a `params T[]` overload without changing the calling code. The potential perf benefit, and the opportunity to replace `params T[]` with `params ReadOnlySpan` seem sufficiently important that we should support `params ReadOnlySpan` even if _collection literals_ provide similar optimizations for span literals - that is, even if the difference between using `params` and using a _collection literal_ is simply `[]`. `Span` has the same benefits as `ReadOnlySpan`, but `Span` is only preferable to `ReadOnlySpan` if the `params` method modifies the span. But modifying a `params` span is not a pattern we should encourage - in part because modifications are not visible to callers where the compiler generated the `Span` instance. In short, we concluded we should support `params ReadOnlySpan` but not `params Span`. `params ReadOnlySpan` should be implicitly `scoped` so the compiler can reuse the implicitly generated `ReadOnlySpan` instance across multiple calls in the calling method. Should we also support `params scoped T[]`, to allowing sharing the array across callers? ### Overload resolution and backward compatibility Overload resolution should prefer an overload with `params ReadOnlySpan` over an overload with `params T[]`, to ensure call sites get benefit from reduced allocations. That means adding a `params ReadOnlySpan` overload to an API with a `params T[]` overload is a source breaking change. ### Conclusions Extend `params` support to `ReadOnlySpan` only. `params ReadOnlySpan` parameters should be implicitly `scoped`. Overload resolution should prefer `params ReadOnlySpan` over `params T[]`. Consider support for `params scoped T[]` as an open question. ================================================ FILE: meetings/working-groups/params-improvements/PI-2022-11-03.md ================================================ # Params Improvements Working Group Meeting for November 3, 2022 The second meeting of the _`params` improvements_ working group was focused on the code generated for arguments to `params ReadOnlySpan`. ## Span allocation The compiler _may allocate a span on the stack_ when: - the span is implicitly allocated (as a `params` argument or as a [_collection literal_](https://github.com/dotnet/csharplang/issues/5354)), - the span length is known at compile time, and - the compiler can determine _through escape analysis_ that no references to the span escape the containing method. For arguments to a `params ReadOnlySpan` parameter (which is implicitly `scoped`), the conditions above are satisfied. We have a [request](https://github.com/dotnet/runtime/issues/61135) with the runtime team to support _fixed size arrays_ of managed types, at least for fields. If we have _fixed size array_ fields, we can define `struct` types with inline arrays and use locals for stack allocated arrays. For example, consider a `FixedSizeArray2` type defined below which includes an inline 2 element array: ```csharp struct FixedSizeArray2 { public T[2] Array; // pseudo-code for inline array } ``` With that type, a call to `WriteLine("{0}, {1}", x, y)` could be emitted as: ```csharp var _tmp1 = new FixedSizeArray2(); _tmp1.Array[0] = x; _tmp1.Array[1] = y; var _tmp2 = new ReadOnlySpan(_tmp.Array); // WriteLine(string format, params ReadOnlySpan args); WriteLine("{0}, {1}", _tmp2); ``` Ideally the BCL will provide types such as `FixedSizeArray1`, `FixedSizeArray2`, etc. for a limited number of array lengths. And if the compilation requires spans for other argument lengths, the compiler will generate and emit the additional types. The number of arguments passed to a `params` parameter is _not considered_ when determining whether to implicitly allocate the span on the stack. To avoid implicit stack allocation at a call site, the calling code should allocate the array explicitly with `new[] { ... }`. We believe scenarios where stack allocation regardless of argument length becomes an issue are unlikely, and should be easy to work around, but we can adjust if necessary. ## Span reuse The compiler _may reuse a span_ when: - the span is implicitly allocated (as a `params` argument or as a [_collection literal_](https://github.com/dotnet/csharplang/issues/5354)), - the span length is known at compile time, - the compiler can determine _through escape analysis_ that no references to the span escape the containing method, and - the compiler can determine there are _no reachable aliases_ to the span in user code. For arguments to a `params ReadOnlySpan` parameter (which is implicitly `scoped`), the conditions above are satisfied. The compiler will reuse a single span across all calls to `params ReadOnlySpan` methods where the span element types are considered identical by the runtime. The length of the reused span will be the length of the longest `params` argument list from all uses. (The actual span argument passed to a `params` method will be a slice of the reused span, with the expected length for the call.) Reuse may be across distinct call sites _or_ repeated calls from the same call site. Reuse is per thread of execution and within the same method only. No reuse across _lambda expressions_ and the containing method. Reusing spans across _local functions_ and the containing method is possible if the local function is not used as a delegate, although the implementation cost of such an optimization may outweigh the benefit. When exiting a scope, the compiler will ensure that no implicitly allocated span holds references from the scope. To opt out of reuse at a call site, the calling code should allocate the span explicitly. _Should we only reuse spans that are allocated on the stack? If we also allow reuse of heap allocated buffers, that will require completely different code gen for managing allocations._ ## Collection literals We will support stack allocation and reuse of spans for _collection literals_. For collection literals that include a _spread operator_, the length of the resulting span is not known at compile time. For those cases, we will choose a maximum length for stack allocation and generate code that falls back to heap allocation at runtime if that length is exceeded. As per the _collection literals_ working group, a hidden diagnostic will be reported in cases where stack allocation may not be possible at runtime. For instance, the expression `(ReadOnlySpan)[..e]` could be emitted as: ```csharp var _tmp1 = new FixedSizeArray8(); ReadOnlySpan _tmp2; if (Enumerable.TryGetNonEnumeratedCount(e, out int n) && n <= 8) { int i = 0; foreach (var item in e) { _tmp1.Array[i++] = item; if (i == n) break; } _tmp2 = new ReadOnlySpan(_tmp1.Array, 0, n); } else _tmp2 = new ReadOnlySpan(e.ToArray()); ``` ## Example Consider the following method with multiple calls to a `params` method: ```csharp // static void WriteLine(string format, params ReadOnlySpan args); static void WriteDictionary(Dictionary dictionary) { WriteLine("Dictionary"); foreach (var (k, v) in dictionary) WriteLine("{0}, {1}", k, v); WriteLine("Count = {0}", dictionary.Count); } ``` The method could be lowered to: ```csharp static void WriteDictionary(Dictionary dictionary) { FixedSizeArray2 _tmp1 = new FixedSizeArray2(); WriteLine("Dictionary", new ReadOnlySpan(Array.Empty()); // no reuse foreach (var (k, v) in dictionary) { _tmp1.Array[0] = k; _tmp1.Array[1] = v; WriteLine("{0}, {1}", new ReadOnlySpan(_tmp1.Array)); // reuse Array.Clear(_tmp1.Array); // clear } _tmp1.Array[0] = dictionary.Count; WriteLine("Count = {0}", new ReadOnlySpan(_tmp1.Array, 0, 1)); // reuse } ``` ================================================ FILE: meetings/working-groups/ref-improvements/REF-2022-11-11.md ================================================ ref fields to ref struct === # Terminology RFRS: `ref` field to `ref struct` # TypedReference All the discussion on RFRS is tied to how we eventually fully support `TypedReference` (remove it from restricted types list). The reason is that `TypedReference` can support storing anything. It can even store a `ref TypedReference` value which makes it equivalent to a type that has RFRS. The language does not necessarily need to support this full complexity though. One simplification is that we only limit the arguments passed to `__makeref` to be those we logically support on `ref struct` fields today. That would allow `ref struct` values, or `ref` to normal `struct`, but not `ref` to `ref struct`. This means IL could construct instances that violate C# lifetime rules but that would be labeled as simply unsupported. However until we know how RFRS support will occur it's difficult to move forward with `TypedReference` support for fear we'd end up painting ourselves into a corner. # Lifetime notation When discussing `ref` and the associated lifetime challenges it helps to have an explicit notation syntax to fall back on for discussions. That will be done by using the `'a` notation. The letter following `'` is the lifetime name and it can be applied to any value or `ref` in code. By default named lifetimes have no relationship to each other but one can be created by adding `lifetime` constraints at the method body. For example here is how the use of `scoped` is expressed in lifetime notation ```csharp Span Read(scoped ref int i) { ... } 'b Span Read('a ref 'b int i) { } ``` In this case `scoped` becomes the `'a` lifetime. That enforces the behavior of `scoped` because the lifetime for returns is `'b`. There is no provided relationship between `'a` and `'b` hence no conversion and it cannot be returned. The *return only* vs. *containing method* scopes can be explained using the following example: ```csharp ref struct RS { } Span Create(ref RS i) { ... } 'b Span Create('b ref 'a RS i) { where 'b <= 'a } ``` By establishing a relationship between `'a` and `'b` it allows for both the value and the `ref` to be returned from the method. It also prevents cyclic assignment issues because the relationship does not allow for it from a lifetime (`'b` could be smaller than `'a`). # Complications around ref fields of ref struct The immediate issue this causes is it creates layers of lifetimes in our types. Consider concretely: ```csharp ref struct RS { ref 'a Span Field; } ``` The value of the field has a lifetime that is independent of the containing instance. Typically when we look at a value there are two lifetimes to consider: the safe to escape and ref safe to escape. In the case of `RS` though there is also the lifetime of `Field`. ```csharp void E() { // Only two lifetimes here: the value and the ref Span span = ...; // Three lifetimes: value, ref and value of Field RS rs = ...; } ``` These lifetimes can now be arbitrarily deep. So a given struct can have `N` different lifetimes associated with it that we have to manage. As the types get more fields this gets more complex because the lifetimes aren't necessarily linear either: ```csharp ref struct RS { ref Span Field1; ref Span Field2; } ``` There are two different ways we could think about this: ```csharp // Option 1: simple but limiting ref struct RS { ref 'a Span Field1; ref 'a Span Field2; } // Option 2: flexible but now we have a tree ref struct RS { ref 'a Span Field1; ref 'b Span Field2; } ``` These problems mostly manifest in the method arguments must match rule. This is the rule where we look at all of the `ref` going into the method and ensure that they can never be cross assigned in a way that would cause an unsafe reference to the stack. ```csharp void Swap(ref Span x, ref Span y) { ... } void Use(ref Span span) { Span local = stackalloc int[42]; Swap(ref span, ref local); } ``` The MAMM rules are intricate but can be simplified to the following: > Every `ref` argument to a method be assignable *from* every other `ref` argument in terms of lifetimes. Before there were just two layers of lifetimes, now there are basically infinite and they **all** have to match. The problem with `ref` fields to `ref struct` is now virtually everything is a MAMM nightmare. Even an instance method on a `struct` is dealing with MAMM insanity. The only way for MAMM to work in this scenario is for all the lifetimes involved to be equivalent. That really defeats the purpose of adding them. # Paths Forward ## Implement RFRS The consensus is that to fully support RFRS would likely require us to have full lifetime notations in the language. For example putting the lifetimes on `scoped` modifier. That would allow for example `scoped`, `scoped`, etc ... At the same time, we don't have the supporting scenarios at this time to justify that work. It's possible that features like `params ReadOnlySpan` will produce these. ## Implement RFRS in a limited fashion Much of the complexity of RFRS comes from the fields being as flexible as a `ref` parameter. The ability to ref re-assign, assign new values, etc ... drive the complexity particularly around MAMM. Perhaps if we limited their capabilities we could satisfy key scenarios without jumping off the complexity cliff. For example if we forced them to be `readonly ref`, `ref scoped`, etc ... would that help? The `ref scoped` is likely the most plausible approach here. The key is whether we can define its meaning in such a way that it remains useful while keeping complexity low. The rules would also need to apply to `ref scoped` parameters too which increase the scenarios a bit. ## Punt it The explicit decision to not support RFRS for now should open the door for us exploring `ref struct` as generic arguments and implementing interfaces. There are existing scenarios that will sufficiently help drive this feature. Need more due diligence to be certain that we don't close the door on RFRS in the future. # Next Steps 1. Andy and Aaron are going to provide simplified samples where RFRS would be beneficial. 2. Group will explore if there are ways to expose them in a limited way that does not force us off the complexity cliff. For example if they are required to be `readonly ref`, or `ref scoped` can be defined with sufficient restrictions, etc ... Do any of these both allow the scenarios that matter without forcing off the complexity cliff of full lifetime notations. 3. Explore whether we can go forward with `ref struct` generics + interfaces without cutting off our ability to support RFRS at a later time ================================================ FILE: meetings/working-groups/ref-improvements/ignore-overloads-in-expressions.md ================================================ # Ignore overloads with ref struct parameters within Expression lambdas Ignore overloads with `ref struct` parameters when binding within `Expression` lambdas. ## Motivation C#13 adds support for *`params` span*, and C# preview adds *first-class span types*. Both of these features allow overload resolution to prefer overloads with *span type* parameters in more cases than with earlier language versions. However, within an `Expression`, binding to a member that requires a `ref struct` instance may result in a compile-time or runtime error, and is therefore a breaking change. Example 1: `params` span, see [#110592](https://github.com/dotnet/runtime/issues/110592) ```csharp Expression> expr = (x, y) => string.Join("", x, y); // String.Join(string?, params ReadOnlySpan) ``` Example 2: First-class span types, see [#109757](https://github.com/dotnet/runtime/issues/109757) ```csharp Expression> expr = (x, y) => x.Contains(y); // System.MemoryExtensions.Contains(this ReadOnlySpan, T) var f = expr.Compile(preferInterpretation: true); // Exception ``` ## Design [*12.6.4.2 Applicable function member*](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#12642-applicable-function-member) could be updated as follows: > - A static method is only applicable if the method group results from a *simple_name* or a *member_access* through a type. > - ... > - **Within an `Expression`, if any parameters of the candidate method, other than the implicit instance method receiver, may have a `ref struct` type, the candidate is not applicable.** Note that the disqualifying parameter: - May be a `params` parameter - May be an optional parameter - May have a *generic parameter* type with `allows ref struct` ## Drawbacks Ignoring certain overloads may be a breaking change itself, in limited scenarios where `ref struct` instances are supported within an `Expression` currently. *Examples?* ## Alternatives ### No compiler change; add IDE fixer Make no additional compiler changes. Instead, require callers to rewrite `Expression` instances to work around the overload resolution changes. An IDE fixer could be provided to rewrite calls for these cases. ### Support `ref struct` arguments within `Expression` lambdas The [`Expression` interpreter](https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression-1.compile?view=net-9.0#system-linq-expressions-expression-1-compile(system-boolean)) relies on *reflection*, so supporting these cases would require supporting `ref struct` arguments in reflection or rewriting parts of the interpreter. ================================================ FILE: meetings/working-groups/roles/extension-wg-2024-06-07.md ================================================ # Extensions WG 2024-06-07 ## Type erasure encoding We need to encode the extension type (for extension-aware compiler) and the underlying type (erasure and for non-extension-aware tools). We'll need to encode tuple names, dynamic bits, nullability, native integer for both types. Ideally, this should be done in a way that doesn't break tools that understand the current attribute/constructor encoding. Options for encoding extension type: - generic attribute (pinging David to ask limitations/bugs on older frameworks) - attribute with typeof (may not refer to type parameters) - nested type - nested type with parallel method Decision: we'll spec out the parallel method in nested type design. ### modopt ``` // source void M(E e) // codegen void M(C modopt(E) e) ``` ``` virtual void M2(C c) // other assembly override void M2(E e) ``` Con: binary break to change API from extension to underlying type and back Con: problem for override at language level (might be solvable) Con: need to new solution encode the second set of tuples names/dynamic/nullability ### Generic attribute ``` // source void M(E e) // codegen void M([Extension] Underlying e) // System.Extension`1 (new) ``` Con: Attribute doesn't exist downlevel (we should synthesize) Con: Generic attribute not supported before .NET 7 (prove there's enough support, or block off) Con: need to new solution encode the second set of tuples names/dynamic/nullability ### Attribute with typeof Limitation with typeof in attribute referencing type parameter (typeof encoded as fully-qualified name, context-free, but no such syntax exists for type parameters) ``` class C { void M(E> e) // encoding: void M([Extension(typeof(E>))] object e) // not possible } explicit extension E for object { } ``` ### Nested type Doesn't work with method type parameter. We could use a generic private implementation nested type with same number of type parameters. ``` explicit extension E where T2 : IDisposable { } class C { // source: void M(E e) where U : IDisposable void M([Extension("Secret1", "Field1")] Underlying e) // problem with method type parameter private class Secret1 where U : IDisposable { E Field1; } } ``` ### Nested type with parallel methods Pro: Nested type reduces pollution of members for other tools Pro: Runtime may not need to load the members of nested type (to be confirmed) Pro: natural encoding of tuple names/dynamic/etc Con: copies all the parameters Pro: it naturally offers a place to encode the second set of tuples names/dynamic/nullability information Secret class is abstract and parallel methods are abstract Note: Hard to point backwards from parallel method, so attribute is on visible method ``` class C { [SecretMethod("Method1")] // explicit matching, rather than complex auto-matching U M(U e, int x) where U : IDisposable private abstract class Secret { abstract void/E Method1(E "", Secret "") where U1 : IDisposable; } } ``` #### Optimization? one secret method per type to encode ``` private class Secret { // More parameter cause more methods, but we can share E Method1 where U1 : IDisposable { } E Method1 where U1 : IDisposable { } } ``` #### Optimization? remove parameters that don't involve extensions/erased We could use parameter names encoding ordinals from original method: ``` private class Secret { void Method1(E p0 /*, Secret x */) where U1 : IDisposable } ``` #### Properties ``` // E this[E e] No parallel property, only have parallel accessors ``` #### Types? ``` // class C : I> { } class C : [SecretField("Field1")] I { private abstract class Secret { I> Field1; } } ``` ## Translation from instance methods to static metadata methods CLS rules for events requires a specific signature (single parameter), but we need a second parameter. See ECMA 335 II.22.28. How bad is it? May break other compilers. Would those compilers allow to consume those methods or treat as invalid metadata? Disallow events? ================================================ FILE: meetings/working-groups/roles/extension-wg-2024-06-14.md ================================================ # Extensions WG 2024-06-14 Agenda - type erasure TL;DR: we'll pursue the attribute with typeof-like string. ## Type erasure We did a quick recap of various options we'd considered so far, and explored using an attribute with a string serialization to represent the erased types. 1. modopts 2. generic attribute 3. attribute+typeof 4. parallel method 5. typeref/blobs ### Attribute with typeof-like string The main problem is how to represent type parameters, since they are currently disallowed in typeof inside attributes. We could extend the typeof format with existing convention "!N" (type parameters), "!!N" (method type parameters), stopping at first container (method or type) when resolving. We would use a `string` instead of a `Type`: `ExtensionAttribute(string encodedInformation)`. ```csharp // source void M(C>) // metadata void M([ExtensionAttribute("E")] C) ``` There are some concerns with re-hydration reliability, but those are not worse than what typeof in attributes already encounters. In terms of specification, we would just say we use the existing (not well-specified) format from typeof and just add the type parameter case. For reflection users, this string would be less useful than a `Type`. Maybe some helper APIs could be offered (somewhere outside the compiler-synthesized attribute and not in roslyn). This format feels verbose (compared to typerefs), but it seems the most feasible and comparatively simpler overall compared to other options. Encoding one attribute per typed entity (return type, parameter type, constraint type) fits with current codegen for tuple names, nullability, dynamic, etc. We will let the runtime team know in case this is a major concern. We need separate encoding for tuple names, nullability on non-erased type. This can be stored in the attribute itself. ```csharp class ExtensionAttribute { ExtensionAttribute(string) { } Nullability { get; } TupleNames { get; } Dynamic { get; } } ``` As for other such compiler-generated attributes, we'll disallow direct usage of this attribute in source. ================================================ FILE: meetings/working-groups/roles/extension-wg-2024-06-21.md ================================================ # Extensions WG 2024-06-21 - update on instance invocation - open issue: what kind of type is an extension type? - spec: conversions, operators, adapting to existing type kind rules, any updates from design review (Mads) # Instance invocation See description in https://github.com/dotnet/roslyn/pull/74012 We'll have follow-up issue for nullability of the extra parameter. We'll have follow-up issue for capturing the receiver when it is a type parameter. Note: When implementing interfaces, a modopt(ExtensionAttribute) could be brought into the picture and cause a conflict/ambiguity. ## Open issue: what kind of type is an extension type? Many sections of the spec need to consider the kind of type. Consider a few examples: - the spec for a conditional element access `P?[A]B` considers whether `P` is a nullable value type. So it will need to handle the case where `P` is an instance of an extension type on a nullable value type, - the spec for an object creation considers whether the type is a value_type, a type_parameter, a class_type or a struct_type, - the spec for satisfying constraints also consider what kind of type were dealing with. It may be possible to address all those cases without changing each such section of the spec, but rather by adding general rules ("an extension on a class type is considered a class type" or some such). 1. extension type on class type is a class type, on a struct type is a struct type, on a type parameter is considered a type parameter, ... (not sure how to word this, maybe "for purpose of") 2. but nullable value type needs to be handled case by case. System.Nullable<...> 3. are there other cases we need to consider (keep an eye out) explicit extension E for T { void M(); } new C().M(); new S().M(); ================================================ FILE: meetings/working-groups/roles/extensions-2023-02-21.md ================================================ # Extensions 2023-02-21 Since the latest syntax proposal is `implicit`/`explicit` `extension`, we're going to refer to the feature as "extensions" or "extension types", rather than "roles". ## Allow omitting `implicit` or `explicit`? Without `implicit` or `explicit`, `extension` would likely mean `implicit`. But that view is not obvious. There's a historical view where extensions come first and roles follow, but there's also a view where roles are fundational and extensions are roles plus implicit lookup rules... For now, we're leaning to require `implicit`/`explicit`. We can decide to relax this and pick what the default means later. ## When to keep using extension methods? Overall, extension types are more powerful than extension methods. But we could think of two scenarios where one could prefer to use extension methods: 1. Extending multiple times whilst sharing code: ``` void M(this OneType one) void M(this OtherType other) ``` 2. Targeting a framework that doesn't support ref fields ## Types may not be called "extension" Although there's no grammatical ambiguity (especially since we decided to require `implicit`/`explicit` before `extension`), it feels simpler from a language and implementation point of view to disallow types named "extension". Here are some productions to consider: `implicit extension E` `explicit extension E` `implicit operator` `explicit operator` `explicit I.operator(...)` `explicit extension.operator(...)` // reserving keyword helps a bit Options: 1. add an error 2. existing warning wave is sufficient Decision: let's go with (1). ## Allow optional underlying type? There are two scenarios where omitting the underlying type could be useful: 1. partial scenarios, where the underlying type is derived from other parts `partial implicit extension R { }` 2. deriving from other extension types, where the underlying type could be derived from the base extensions (somehow) `implicit extension R : R1, R2` Decision: we definitely see value in (1), but are not yet sure about (2). We can add that support later. Let's make room in the syntax and support (1) for now. ## Explicit identity conversion for sideways conversions? In the following example, should we give some kind of type safety so that instances of `CustomerExtension` and `EmailExtension` don't mix? ``` explicit extension CustomerExtension for ReadOnlySpan : PersonExtension explicit extension EmailExtension for ReadOnlySpan ``` Options: 1. implicit identity conversion all the way 2. implicit up-down, but explicit sideways (Customer<->Email requires explicit conversion). 2A. TODO: do we have an explicit identity conversion or an implicit one that warns? 3. distinguish between up and down, ... 4. explicit all the way There's some benefits of such type safety, so we're interested to explore this more. But the notion of "explicit identity conversion" is problematic. Maybe we could spec this as having an implicit conversion that warns? There may be value in distinguishing identity conversions up and down as well. There could be validation scenarios on the way "down". This would need further exploration. How does a type opt into such validation? We couldn't use the presence of an explicit conversion operator, since a user-defined conversion operator is problematic for an identity conversion (it would break the identity invariant). A "validator" would have to be bool-returning. If the validation succeeds, then we handle the conversion. This could come into play into patterns (almost like active patterns). Decision: let's start with (2), consider "validator" for later on. ================================================ FILE: meetings/working-groups/roles/extensions-wg-2023-04-27.md ================================================ # Extensions 2023-02-21 ## Problem with lookup rules on implicit extensions The issue is that the proposed rules would allow a member from a base implicit extension to be found by extension member lookups in two way: via import and via inheritance. This would result into having duplicates (same member appears twice in lookup) or conflicts (hidden member conflicts with hiding member). Below are a few examples. ### Various examples ``` // No definition in Derived implicit extension Base for object { void M() { } } implicit extension Derived for object : Base { } object o = ...; o.M(); ``` ``` // Hiding in Derived implicit extension Base for object { void M() { } } implicit extension Derived for object : Base { new void M() { } } object o = ...; o.M(); // ambiguous ``` ``` // Better in Base implicit extension Base for object { void M(int i) { } } implicit extension Derived for object : Base { void M(double d) { } } object o = ...; o.M(1); // Which does it pick? ``` Beyond those invocation scenarios, there are also non-invocation scenarios: ``` // Non invocation implicit extension Base for object { int Prop => 0; } implicit extension Derived for object : Base { } object o = ...; o.Prop; // find it twice, but it's only Base.Prop, so okay ``` ``` // Non invocation, shadowing implicit extension Base for object { int Prop => 0; } implicit extension Derived for object : Base { new long Prop => 0; // warning so you put "new" } object o = ...; o.Prop; // ambiguity (this is an argument for pruning) derived.Prop; // would find Derived.Prop ``` ### Options We considered a few different ways of handling this: First, we thought of relying on overload resolution to prefer more derived members. But this doesn't work for non-invocation scenarios. Then we considered removing/pruning base extensions from the set of candidates during extension member lookup. This means a member from a base extension is only visible via inheritance, but not via import when it is hidden. But then we realized that existing lookup rules have to solve this already, as they are able to deal with interface members in multi-inheritance scenarios, or in scenario with a type parameter with two interface constraints. Here's an example to illustrate the current behavior: ``` I2 i2 = default!; var r2 = i2.Prop; // Finds I2.Prop, infers long I4 i4 = default!; var r4 = i4.Prop; // Finds I2.Prop, infers long public interface I1 { public int Prop { get; } } public interface I2 : I1 { new public long Prop { get; } } public interface I3 : I1 { } public interface I4 : I2, I3 { } ``` We tracked down the relevant rule from member lookup: "Next, members that are hidden by other members are removed from the set." Conclusion: We'll incorporate a similar rule in extension member lookup. ## Unification of base extensions? For interfaces, we check that directly implemented interfaces cannot unify. For example: ``` interface I { } class C : I, I // error { void I.M() { } void I.M() { } } C.M() // allowing such unifying scenario would cause a problem here (because we would have two V-tables to merge) ``` Do we similarly need to check for potential unification of base extensions? The scenario would be: ``` explicit extension E for object { } explicit extension E2 for object : E, E { void M() { } } ``` We couldn't think of a problem with allowing this at the moment. Lookup on `E2` would work. But if we ended up having some special support for extension types in the runtime, we could have a problem. Conclusion: Let's move ahead to unblock basic scenario and revisit ## Mixing implicit/explicit in a hierarchy Do we need some check on implicit/explicit consistency in an extension hierarchy? For example: ``` implicit extension Base for object { } explicit extension Derived for object : Base { } ``` ``` explicit extension Base for object { } implicit extension Derived for object : Base { } // why? this may be scoped in a namespace ``` Conclusion: Some of the mixing scenarios seem questionable, but adding a restriction would not give much benefit/protection. So we'll allow any mix of implicit/explicit-ness. ================================================ FILE: meetings/working-groups/roles/extensions-wg-2023-06-07.md ================================================ # Extensions 2023-06-07 ## Reviewing the "invocation expression", "member access" and "extension invocation" sections We reviewed the proposals in PR https://github.com/dotnet/csharplang/pull/7179 and discussed some open issues. ### Invoking members from extended type The first issue is that we want to access members from the extended type on a type or value of an extension type. ```csharp extension E for C { void M() { this.MethodFromUnderlying(); // problem MethodFromUnderlying(); // problem } } ``` The current proposal had a bullet in "member access" which would make member access scenarios work for non-invocations, but that left invocations and simple name scenarios unsupported. Decision: we want those scenarios to work and we can achieve that by modifying "member lookup" to also find members from the underlying type. The "member lookup" section already handles lookups ### Pattern-based method binding The second issue is that the proposed rules raise a question about how pattern-based invocations, such as the one involved in deconstruction. For instance, should the following work? ```csharp implicit extension E for C { public dynamic Deconstruct { get { ... } } } class C { void M() { (int i, int j) = this; } } ``` Decision: If the scenario allowed instance properties today, then extension properties should work, if not, then not. So, given that an instance `Deconstruct` property would not participate in deconstruction today, we don't want an extension `Deconstruct` property to participate either. Any scenario that allowed extension methods should allow methods from extension types to participate. This means we'll need to review all pattern-based sections of the spec to come up with some language to only bind to methods in the extension type case. Maybe something like "Do a member access and if that resolves to a method, then ..." or "if the expression ... resolves at compile-time to a unique instance or extension method or extension type method, that expression is evaluated". Also, we'll need to make decisions for non-invocation members, such as the `Current` property involved in a `foreach`, as those were not previously covered by extension methods. ## Forward compatible emit strategy for interface support We briefly reviewed an alternative to the `ref struct` approach that is currently proposed. We will need to experiment and flesh out a proposal. ================================================ FILE: meetings/working-groups/roles/extensions-wg-2024-03-05.md ================================================ # Extensions WG 2024-03-05 ## finding extension members on underlying type Just like extension methods on a base type are eligible when given an instance of the derived type, extension members on the underlying type should be eligible when given an extension type or value. The extension member lookup rule was updated to look at the underlying type when given an extension type. ``` var x = E1.Member; System.Console.Write(x); class C { } extension E1 for C { } implicit extension E2 for C { public static string Member = "ran"; } ``` Decision: that seems fine. Note: like other extension lookups, that would not apply on Simple Names. For example: ``` class C { } extension E1 for C { void M() { var x = Member; // error string s = Member; // error } } implicit extension E2 for C { public static string Member = ""; } ``` ## merging extension methods and extension type members within same scope LDM decided that we should merge extension methods and extension members within the same scope. But how should we deal with ambiguities, either of the same kind or of different kinds? Decision: let's be strict on merging different kinds of members (produce ambiguity). Proposal: Consider doing a breaking change or warning wave warning (would be error for extension case)? ## preference for more specific extension members We discussed two ways of doing this: 1. stop once we find something on a more specific extension? ("shadowing" type of rule) 2. ambiguity when different kinds are found, disambiguate within one kind when overload resolution involved? (no shadowing across different kinds) Decision: we'll pursue proposal 2. ## Follow-up investigations 1. Look at how lookup within a type works today (not inside an interface) - for example, derived defines a field and base defines a method - for example, derived defines a method and a base defines a field 2. find existing merging logic for different kinds of members 3. find existing logic for overload resolution picking between extension methods based on receiver 4. need to understand how the field shadowing works in the following example: ``` System.Console.WriteLine(I4.F); interface I1 { static int F = 1; } interface I2 : I1 { static int F = 2; } interface I3 : I1 {} interface I4 : I3, I2 {} ``` ## brainstorm on inapplicable members hiding applicable ones We did not cover this yet. ``` var x = C.Member; // error: member lookup finds C.Member (method group) and lacks type arguments to apply to that match class C { public static void Member() { } } implicit extension E for C { public static int Member = 42; } ``` ================================================ FILE: meetings/working-groups/roles/extensions-wg-2024-08-09.md ================================================ # Extensions WG 2024-08-09 ## Conversions We reviewed proposed approach for conversions: - extend existing conversions rather than introduce a new "extension conversion - extend categories of "reference types", "value types", "nullable value types", "enum types" - follow-ups: non-transitive identity conversion (`EInt1 => EInt2`), user-defined conversions ## Reference nullability Allowing top-level nullability on underlying types is tempting for implicit scenarios, but it's going to cause problems on explicit scenarios. If we define `explicit extension E for object? { }`, then do you get to say `E?`? Also would `E` be considered annotated or not? This is already tracked as an open issue to investigate further. ## Differences between extensions and structs you could write today? ``` implicit extension JsonDoc for string { public void ExtensionMember() { } } ``` vs. ``` struct JsonDoc { public static implicit operator string(JsonDoc) public static implicit operator JsonDoc(string) } ``` With extensions you get the following benefits: - identity/standard conversion - List<[JsonDoc] string> vs. List - extension conversions - `"".ExtensionMember()` (by virtue of `implicit`) ## Relationship between implicit and explicit? Every extensions can be used explicitly, but only implicit ones can come into play implicitly. ## Brainstorming on naming extensions The name "JsonDoc" brings a mental model of hierarchy or "is-a" relationship. This feels more natural for explicit usages. But the name "StringExtension" does not bring such a mental model. This feels more natural for implicit usages. `JsonDoc s = "";` `StringExtension s = "";` // weird ## Disambiguation for properties Allowing implicit extensions to be named explicitly helps for disambiguation. `StringExtension.ExtensionMember(receiver, args)` // fallback for extension methods ``` implicit extension StringExtension1 for string { public int Prop { get; set; } } implicit extension StringExtension2 for string { public int Prop { get; set; } } "".Prop // how to disambiguate? ((StringExtension1)"").Prop ``` ## Concern over type explosions ``` static class MemoryExtension { public static void M1(this Type1) public static void M2(this Type2) } ``` You would need to split this into two extension types. One for Type1 and the other for Type2. ================================================ FILE: meetings/working-groups/roles/roles-2022-11-10.md ================================================ # Roles & extensions working group (2022-11-10) ## Framing of phase 1 (extension everything) In the last few months, much of our investigation into roles was focused on scenarios that involve adding an interface implementation to an existing type. Our thinking was that we needed to figure out those harder scenarios first, so that we wouldn't risk painting ourselves into a design corner. But we believe using ref structs as part of the emit strategy for roles and extensions would protect this scenario. So we're going to explore this further in the next few meetings with the intention of carving a phase 1 for roles and extensions. Phase 1 would allow extending existing types with new members, but interface scenarios would come in phase 2. ## Syntax and usage scenarios The syntax used below has not been thoroughly discussed yet. But it is good enough for discussion. We will need to revisit in a couple of weeks (definitely before any implementation work starts). ## Emit strategy Here's an outline of the proposed emit strategy in a few scenarios: ### Role type itself ```csharp role MyRole : UnderlyingType where T : Constraint { // Member declarations void M() { ... usage of `this` with type `MyRole` ... _ = this.ExistingMethod(); _ = this.existingField; this.M(); } } ``` would be emitted as: ```csharp ref struct MyRole where T : Constraint // possibly with a special base type like `System.Role` or `System.Role<...>` or some other marker { ref UnderlyingType @this; MyRole(ref UnderlyingType underlying) { @this = ref underlying; } // Member declarations void M() { ... usages of `this` are replaced with `@this` ... _ = @this.ExistingMethod(); _ = @this.existingField; this.M(); } } ``` Interfaces would be disallowed until phase 2. ### Role on a role ```csharp role MyRole2 : MyRole where T : Constraint { ... } ``` would be emitted as: ```csharp // Need to encode relationship to MyRole. Maybe the @this field is sufficient. ref struct MyRole2 where T : Constraint { [Role(typeof(MyRole))] ref UnderlyingType @this; // ... members ... } ``` Open question: confirm whether the attribute is needed ### Invocations on role member Usages of `MyRole.M()` on a `MyRole` local: ``` UnderlyingType underlying = ...; ref MyRole role = ref underlying; // conversion creates a ref struct role.M(); ``` would be emitted as: ``` c# UnderlyingType /*MyRole*/ role = underlying; new MyRole(ref role).M(); ``` ### What kind of conversion is this? For `List` and `List` to be convertible, we need identity conversion between `MyRole` and `UnderlyingType`. Then the role is only instantiated as part of the invocation and is short-lived. Open question: need to confirm this design and weight pros/cons. Open question: How about structs? ``` UnderlyingStructType underlying = ...; ((MyRole)underlying).M(); or declare a ref local ``` ### Inferred type arguments in invocation This erasure approach may run into some friction with a future phase 2, as phase 2 requires type arguments to use role types to satisfy certain interface constraints. Whether the role type is erased into the underlying type is observable with type tests like `is IEnumerable`. In phase 2, the role could add this interface on an underlying that doesn't have it. ```cs MyStruct m = new(); ref MyRole r = ref myStruct; var x = M(ref r); // T is MyStruct /* MyRole */ var y = M2(ref r); // In phase 2, T would be MyRole, would we change what is emitted for calling M from phase 1? ref T M([Unscoped] ref T t) => ref t; ref T M2([Unscoped] ref T t) where T : IEnumerable => ref t; ref T M3([Unscoped] ref T t) { // problem with erasure if (t is IEnumerable i) ... } ``` ### Usage as extension To turn a role into an extension, its declaration needs to be changed (let's say from `role` to `extension`): ``` class UnderlyingType { void M1() { ... } void M2() { ... } } namespace MyRoleNamespace { extension MyRole : UnderlyingType { void M2() { } void M3() { } } } ``` Then to use that extension, it needs to be brought into scope with `using MyRoleNamespace;`. #### Usages of `MyRole.M()` as an extension on an `UnderlyingType` local: ``` using MyRoleNamespace; UnderlyingType underlying = ...; underlying.M1(); underlying.M2(); underlying.M3(); ``` would be emitted as: ``` UnderlyingType underlying = ...; underlying.M1(); underlying.M2(); new MyRole(ref underlying).M3(); ``` In terms of lookup rules, we would keep the same order as extensions today, namely that instance members win. In the example, lookup for `M1`, `M2` or `M3` would be: 1. instance members on UnderlyingType 2. otherwise, fall back to extensions ### Role type in signatures ``` MyRole M(MyRole role) ``` would be emitted as `UnderlyingType` with an attribute: ``` [return: Role(typeof(MyRole))] UnderlyingType M([Role(typeof(MyRole))] UnderlyingType role) ``` This encoding would allow callers of `M` to get the right return type (`MyRole`): ``` MyRole role1 = ...; var role2 = M(role1); // var == MyRole ``` ### What kinds of types can roles be defined on? ```cs role Role : T {} // TODO: Confirm whether typeof in attribute can refer to T? Role M(Role role) => [return: Role(typeof(T))] UnderlyingType M([Role(typeof(T))] UnderlyingType role) ``` ```cs role MyFunctionPointerRole : delegate*<...> { } // can't put function pointer in typeof in attribute delegate* // nowhere to store attribute ``` ### Encoding of roles in metadata Some possible encodings: 1. attributes 2. Custom modifiers (`modopt`) 3. don't erase roles (need runtime support) Previously, we were thinking of emitting erased roles like tuples or `dynamic`, ie. using an attribute. But that encoding scheme doesn't work so well for roles, because we need to encode types. This is not only more verbose, but it runs into some limitations. We're going to explore the next alternative, ie. using `modopt`. Use a modopt on return type: ```cs Role M(Role role) ``` would emit as: ```cs modopt(Role) UnderlyingType M(modopt(Role) UnderlyingType role) ``` One benefit of this approach is that modopt is allowed anywhere a type is allowed: `List) UnderlyingType>` A downside of this approach is that call sites need to spell out the entire signature, including modopts. Also, this implies that changing API from underlying type to role is a breaking change. Finally, this implies that you could overload on role types: ``` void M(UnderlyingType underlying) { } void M(MyRole role) { } ``` Open question: confirm we're okay with such compat behavior ### Hiding ### Next time - Compat behavior of modopt - Conversion - Repetition at call-site ================================================ FILE: meetings/working-groups/roles/roles-2023-01-23.md ================================================ # Roles WG 2023-01-23 Earlier this week, we reviewed the proposed lookup rules. We're continuing to review https://github.com/jcouv/csharplang/blob/roles-spec/proposals/roles.md ## Static-ness Roles must support a non-static flavor and since any role can be turned into an extension, extensions must also support a non-static flavor. But it doesn't make sense to allow a non-static extension on a static underlying type. The proposal we're landing on is that roles/extensions would not require specifying static-ness. We would use the static-ness from the underlying type and disallow explicit `static` modifiers. This approach seems convenient for users. But allowing a `static` modifier could be useful to avoid emitting a ref field when possible. Some auto-detection might also be possible, but not very attractive. ## Is it necessary to place the nesting restriction? Let's remove the restriction. We can update the extension member lookup rules to first look in enclosing types then enclosing namespaces. Treating types as scopes like namespaces invites allowing usings/imports in types, but let's not. ## Static imports Is it a general principle that an extension is in effect when its type can be accessed with a simple name? If so, can a static using of an enclosing type bring a nested extension in effect? Should `using static C;` bring the nested extension into scope? ``` public class C { public extension E { } } using static C; // E an extension here? ``` Yes, this would make sense. You could name the type `E` from that location, so the extension members should be in scope. ## Modifiers Since we're allowing nested roles/extensions, then we'll allow the `private` modifier. We should allow the `file` modifier. ## Underlying type ### Pointer Should we disallow pointer types as underlying types? There is precedent with extension methods and some issues with metadata (`System.Role`). On the other hand, it would be nice to support. We'll start by disallowing pointer types, but let's find an emit strategy that would allow them eventually. ### Nullability Just like for base types, we'll disallow top-level nullability in underlying types. Nested nullability should be allowed, but it will not affect lookup. We may want to warn on mismatch on nullability when used. ### Ref structs We would like to allow ref structs as underlying type for roles/extension, but our emit strategy relies on ref fields, which don't yet support ref structs. Maybe the lifetime rules could allow this, given that the usage pattern is controlled/limited. ### Roles We would like to allow role types as underlying types. Maybe we should even allow **multiple role types** in the definition of a role. ``` role DiamondRole : NarrowerUnderlyingType, BaseRole1, Interface1, BaseRole2, Interface2 { } ``` The relationship between a role and it's "base role" would not be one of inheritance and virtual calls: ```cs class C { void M() { ... } } role R1 : C { // New version adds below //void M() { ... } } // Different library role R2 : R1 { void M2() { M(); } } ``` We still need to refine terminology. Since members of a role shadow its underlying type, maybe we should emit a warning when shadowing a name? (need to add `new`) ### Extensions Extensions are just roles, and roles are allowed in underlying role list. So extensions should be allowed too. ### Extension syntax The current syntax proposal puts the producer of a role in charge of making it an extension or not. Should we use a syntax that puts the consumer in charge instead? ``` role R { } using R; ``` `extension role R`? `extension E : R;`? ### What kind of type are roles/extensions? The fact that roles are emitted a ref structs should be kept an implementation detail. As far as the language goes, roles would be a new kind of type. A role type would satisfy the same generic constraints as its underlying type. ``` role Role : SomeClassType; void M() where T : class { } M(); // okay, `modopt(Role) Underlying`, Roslyn metadata reader can recover `Role`. Can reflection see custom modifiers? ``` ================================================ FILE: meetings/working-groups/roles/roles-2023-01-25.md ================================================ # Roles WG 2023-01-25 ## Language for relationships We'll use "augments" for relationship to underlying type (comparable to "inherits" for relationship to base type). We'll use "inherits" for relationship to inherited roles (comparable to "implements" for relationship to implemented interfaces). ```csharp struct U { } role X : U { } role Y : U, X, X1 { } ``` "Y has underlying type U" "Y augments U" "Y inherits X and X1" "Derived role Y inherits members from inherited roles X and X1" ## Syntax for role underlying type list We should assume multiple inheritance for now. Will update syntax to allow multiple inherited roles. In that syntax, the first type will be the underlying type. `role R : I { } // First type is underlying type (not an implemented interface)` ## Type parameters in extensions The purpose of requiring all extension type parameters to appear in the underlying type is to help find a unique substitution when looking for compatible extensions. For example, if you have `extension X : U` and then need to decide whether it is compatible with the `U` type, you need to find a unique compatible substituted extension type `X`. This rule does not apply to roles (assuming that we don't let consumers turn a role into an extension after the fact). ## Constraints Will need to spell out rules for all kinds of constraints, such as `struct`, `class`, etc. `where T : Extension`, `where T : Role` We'll disallow roles/extensions in type constraints for now. But this is assuming that casting makes the experience acceptable and doesn't cause an issue with struct. ## Definition of "role type" Interfaces don't have a base type, but have base interfaces. Similarly, roles don't have a base type, but have base roles. `role R : T where T : I1, I2 { }` `role R : T where T : INumber { }` A role may be a value or reference type, and this may not be known at compile-time. ## Emit strategy This will need more discussion, and we prefer to separate it from language-level questions. `System.Role` is harder to use for T being pointer, ref struct. Also implies runtime support. We don't want a breaking change when we reach phase C and you add an interface to an existing role. We'd rather limit amount of runtime work in phase B, if possible. A typeref is preferrable to some string. We brainstormed using a modopt for encoding the underlying type in a way that would support pointers and ref structs. Maybe something like `System.Role` or `ref struct S : modopt(pointer) System.Role`. But there is a deep restriction in compiler codebase. Internal/public API expose custom modifiers as named types. This restriction stems from the published version of 335, so if we take a dependency on being able to put Types in a modopt and not just TypeRefs, the runtime will want to doc the spec change in their 335 augments doc. Another option for this position (underlying type) would be to use an attribute. This would not be sprawling. ## Extension type members We should allow `new` and warn that you should use `new` when shadowing. Shadowing includes underlying type and inherited roles. ``` class U { public void M() { } } role R : U { /*new*/ public void M() { } } // wins when dealing with an R ``` ``` class U { public void M() { } } extension X : U { /*new*/ public void M() { } } // ignored in some cases, but extension is a role so rule should apply anyways U u; u.M(); // U.M (ignored X.M) X x; x.M(); // X.M ``` ``` class U { } role R : U { public void M() { } } role R2 : U, R { /*new*/ public void M() { } } // wins when dealing with an R2 ``` ## Accessing various members, qualifying names Will need to spec or disallow `base.` syntax. Casting seems an adequate solution to access hidden members: `((R)r2).M()`. We may not need a syntax like `base(R).` which was brainstormed for some other features. We want to ensure that both of these are possible: From an extension, need to access a hidden thing. From an underlying type, still need to access the extension member when extension loses. ## Conversions Should allow conversion operators. Extension conversion is useful. Example: from `int` to `string` (done by `StringExtension`). But we should disallow user-defined conversions from/to underlying type or inherited roles, because a conversion already exists. Conversion to interface still disallowed. ================================================ FILE: meetings/working-groups/roles/roles-2023-02-15.md ================================================ # Roles WG 2023-02-15 ## Syntax for base type list In the proposal so far, we have a single base type list, which starts with an underlying type and then continues with a mix of roles and interfaces. `role R : UnderlyingType, BaseRole1, Interface1, BaseRole2, Interface2` This feels complicated and error-prone. For example, you might not notice that you forgot to specify an underlying type since interfaces are allowed as underlying types. `role R : IUnderlyingInterface, Interface1, Interface2` Options: 1. Keep as-is single list (underlying type followed by base roles followed by interfaces) We might make this more readable by requiring some ordering of groups (don't mix base roles and interfaces). But this seems stylistic and requires some decoder ring knowledge. The readability issue may be mitigated by naming conventions (interfaces have `I`). 2. No ordering requirement for base roles and interfaces Users can organize the list themselves 3. allow optional underlying type when a base role is present, ordering of groups `role R : BaseRole(s), Interface(s)` An issue is that order now affects meaning: `role R : BaseRole1, Interface1` vs. `role R : Interface1, BaseRole1` 4. Keyword for underlying type `view VJsonView on type : views, interfaces` `view VCustomer extends type : view, interfaces` `view VJsonView of type : views, interfaces` This provides an advantage for partials (keyword allows making underlying type optional with less ambiguity): `partial R : UnderlyingType, Interface1` `partial R : Interface2` (currently error since missing underlying type) We could also deal with an optional underlying type when base role(s) is present: `view VCustomer : views, interfaces` // infer `extension of type : views, interfaces` // optional extension name `role of type ...` // disallowed Decision: let's go with (4) ## What keyword for underlying type? Here are some keywords we considered: 1. `extension E of U` 2. `extension E extends U` (too verbose) 3. `extension E on U` 4. `extension E for U` 5. `extension E over U` 6. `this E of U` 7. `extension E is U` 8. `extension E override U` (misleading since no virtual/inheritance) 9. `extension E using U` (two meanings for `using` already, not intent) 10. `extension E from U` A few keywords stand out as potential candidates: `of` suggests a property of the underlying thing `on` suggests something added "on top of" `for` is more like natural/English language (we have extensions for this type) Here's what those could look like in context: 1. `role R of U` 2. `view V of U` 3. `role R on U` 4. `view V on U` 5. `view V for U` 6. `extension V for U` 7. `extension Extension for T` Decision: let's go with `for`. `of` would be second choice, `on` third. ## Keyword for declaration of roles We've considered a few candiates so far, but need a firmer decision since we're working on implementation. 1. `role` 2. `view` (this unfortunately already has connotations in various fields, from databases, UI, etc, but some of those can be misleading) 3. `shape` 4. `alias` (ambiguity with using aliases; empty roles are better aliases) 5. modifiers `implicit` and `explicit`: - A. `explicit extension Role for U` (it's a bit long-winded but makes it clear that they are variations of same feature) - B. `explicit view Role for U`, `implicit view Extension for U` Decision: let's go with 5A ================================================ FILE: meetings/working-groups/unsafe-evolution/unsafe-alternative-syntax.md ================================================ # Alternative syntax for caller-unsafe This proposal amends and references [Unsafe evolution](https://github.com/dotnet/csharplang/blob/main/proposals/unsafe-evolution.md). Read that one first! ## Summary Key differences from the [Unsafe evolution](https://github.com/dotnet/csharplang/blob/main/proposals/unsafe-evolution.md) proposal: - Mark caller-unsafe members directly with the `[RequiresUnsafe]` attribute instead of repurposing the `unsafe` keyword to generate that. - Keep the existing meaning of `unsafe` as is: designating a code region as an unsafe context. - Use the proposed opt-in mechanism only to control the enforcement against invoking caller-unsafe members outside of unsafe regions. It does not affect what `unsafe` means or whether a member can be marked caller-unsafe. ```csharp public static class Unsafe { [RequiresUnsafe] // Always allowed - even in older language versions [System.CLSCompliant(false)] public static ref T AsRef(void* source) where T : allows ref struct { ... } ... } void M() { int i = 1; int* ptr = &i; // No longer unsafe in either proposal Console.WriteLine(*ptr); // Error outside of unsafe region ref int intRef = Unsafe.AsRef(ptr); // Error if enforcement is opted in unsafe { Console.WriteLine(*ptr); // Allowed in unsafe region ref int intRef = Unsafe.AsRef(ptr); // Allowed in unsafe region } } ``` ## Motivation This proposal is primarily motivated by avoiding breaking changes in the language, but does provide additional benefits. ### Declaration vs consumption The existing `unsafe` feature is all about where a user is allowed to *consume* unsafe operations. Both proposals add the ability to *declare* new unsafe operations - in the form of caller-unsafe members. However, the original proposal repurposes the syntax of the *consumption* feature for *declaration* purposes, muddling the distinction between the two. **With this proposal**, instead, declaration gets a separate syntax. Declaration is likely much more rare than consumption, and having a dedicated keyword seems unwarranted. An attribute seems just fine, especially since that's what the original proposal compiles down to anyway. ### Language breaking changes Both proposals support users introducing *API breaking changes* (mitigated by an opt-in mechanism) by marking existing APIs caller-unsafe. The original proposal additionally introduces two *language breaking changes*: 1. **Unsafe members**: Existing uses of `unsafe` on a member are generally intended only to make the member body an unsafe context. They may be used as a more convenient shorthand for wrapping the whole member body in an `unsafe` statement. However, with the new semantics of the original proposal, an `unsafe` member is now considered caller-unsafe, and existing calls to the member will break. 2. **Unsafe types**: Existing uses of `unsafe` on a type are intended to make the type body an unsafe context. They may be used as a more convenient shorthand for marking each member unsafe. However, with the new semantics of the original proposal, an `unsafe` type is no longer an unsafe context, and unsafe operations within the type will break. When the language change is in effect, it is likely to break nearly every occurrence of `unsafe` on members and types! These breaks impose a hardship on users without accruing any additional safety benefits. Users need to do significant work just to get back to an equivalent state to what they had before. We have never attempted a breaking change on even close to that scale in C#, and would need incredibly compelling arguments to change that stance. Arguments such as "we truly have no other viable option." However: **With this proposal**, the language breaking changes go away. The `unsafe` modifier continues to mean what it has always meant: introducing an unsafe context. ### Time of effect With the original proposal, all of these changes to semantics take effect together, when an opt-in is specified: - The `unsafe` modifier on a member changes its meaning to an indicator that the member is caller-unsafe. - The `unsafe` modifier on a type changes its meaning to a no-op. - Some operations such as pointer types and many pointer expressions cease being considered unsafe. - A call to a caller-unsafe member outside of an unsafe context becomes an error. This all-or-nothing approach to opt-in effectively means adoption must happen in bulk. It raises the burden for someone keen to make their code more safe: They cannot get enforcement of caller-unsafe calls until they have gone through the work of mitigating the language breaking change. Arguably this makes the impact of the language breaking change even worse, because it doesn't happen cleanly on a C# version boundary. In new C# versions going forward, the meaning of `unsafe` in code will not be clear in and of itself, but will depend on a separate opt-in mechanism. **With this proposal**, on the other hand: - There *is* no language breaking change: the `unsafe` modifier keeps its meaning. - The relaxation of previously unsafe operations can happen cleanly on a language version boundary, and doesn't need to be tied to opt-in. - Marking a member caller-unsafe is independent of opt-in (and even of language version). `[RequiresUnsafe]` is just an attribute. - Only the *enforcement* of `[RequiresUnsafe]` is guarded by an opt-in. ### Opt-in granularity The original proposal envisions an opt-in mechanism as a mitigation for the fact that *newly marking an existing member caller-unsafe is an API breaking change*. It would be harsh to mark a number of existing members caller-unsafe in a new version of .NET if there weren't also a mechanism for people to manage the break to their consuming code. What about *new* caller-unsafe members, though? Presumably we and others will keep adding such members in significant numbers. Why should a modern C# user be allowed to avoid enforcement when calling those? After all, they won't have existing code already calling them outside an unsafe context. It seems like the original proposal leaves safety on the table by treating new and existing members equally. While we may initially see a lot of existing members being annotated, over time the majority would shift to newly added members. **With this proposal** the caller-unsafe marker is an attribute. As such, it *could* use attribute arguments to specify whether a caller-unsafe member should be subject to opt-in or not. For instance, existing members could be annotated with something like `[RequiresUnsafe(optional:true)]` which lets their enforcement be controlled by an opt-in flag. New caller-unsafe members would just use `[RequiresUnsafe]` which defaults to `optional:false`. Over time, after an initial transition period, this would become the common case. Libraries could also use this to "tighten the screws" over several releases, limiting their users' ability to evade enforcement. *Note:* Such arguments on `RequiresUnsafe` are not part of this proposal. We're merely pointing out that the proposal, unlike the original one, *allows* for such a design. ### Unsafe context in caller-unsafe members In the original proposal, annotating a member as caller-unsafe *also* makes the whole member an unsafe context. This means that the member's author doesn't get more fine-grained control over which parts of the member body may use unsafe operations. That's unfortunate, as it increases the surface area that needs to be manually audited for safety issues. **With this proposal** the `[RequiresUnsafe]` attribute does not imply that the member is an unsafe context. The author is free to mark the boundaries that make the most sense. ### Meaningless uses of unsafe The original proposal introduces a warning for a few places where the `unsafe` modifier is allowed only for compat purposes, but no longer has an effect. **With this proposal** there is no need for such a warning, since the meaning of `unsafe` doesn't change. ## Detailed design - Keep the `unsafe` keyword with the same meaning in the same places as today: In every location it simply denotes a region of code as an unsafe context. - Remove certain operations from the set of language-defined unsafe operations, exactly as in the original proposal. This takes effect unconditionally for the C# version where it is introduced. - Designate caller-unsafe members directly with the `[RequiresUnsafe]` attribute instead of repurposing the `unsafe` keyword. The attribute does *not* automatically make member bodies unsafe contexts; an explicit `unsafe` region is needed for that. - Introduce a compiler opt-in mechanism that controls whether caller-unsafe methods are prevented from being called outside of unsafe contexts. When opted-in, the assembly gets marked with `[MemorySafetyRules]` just as in the original proposal. - Consider augmenting `[RequiresUnsafe]` with arguments to distinguish new vs existing caller-unsafe members and regulate the impact of the opt-in mechanism on the members' allowed use. - Other aspects remain unchanged from the original proposal, e.g.: - Placement of `[RequiresUnsafe]` needs to obey the same restrictions as the original proposal lays out for `unsafe` on members. - Delegates and lambdas must obey equivalent rules to the original proposal. - Members marked `extern` are implicitly caller-unsafe. There is an open question around "compat mode" below. ## Drawbacks - **No dedicated syntax:** Unlike the original proposal, this version falls back to attributes to express the intended semantics. It wouldn't be the only place in the language where member annotations are by attribute, but maybe there are arguments why this situation is more common or important and deserves a keyword. - **Lack of "uniformity":** Arguably, using `unsafe` both to *produce* (on members) and *consume* (on blocks) unsafe operations is a simpler mental model. It's similar to how `Obsolete` members can call other `Obsolete` members. ## Alternatives - **New keyword:** A dedicated keyword (or keyword combination) different from `unsafe` could be used instead of an attribute. This would still address many of the problems in the original proposal, in particular the language breaking changes. - **Different attribute name:** This proposal uses `[RequiresUnsafe]` because it is descriptive of the meaning and is what the original proposal compiles down to. But it's possible that another name might be better. ## Open questions ### Compat mode The original proposal suggests a "compat mode": When a compilation is "opted-in" to the new semantics but depends on an assembly that is not, a heuristic is applied to members of that assembly to decide whether they should be considered caller-unsafe. The heuristic involves the presence of pointer or function pointer types in the member's signature. This allows some freedom in the order that compilations are opted-in, while preserving a measure of safety when consuming assemblies that are not. The original proposal uses the presence of `[MemorySafetyRules]` in an assembly to determine whether it was annotated, which has some validity because if it didn't have the attribute it *couldn't* have been annotated. Essentially there's an assumption that when an assembly opts in it takes responsibility for appropriately annotating its members. In this alternative proposal, is it still reasonable to use `[MemorySafetyRules]` as the only indication that members have been properly annotated and compat mode should not be used? After all they could have been annotated with `[RequiresUnsafe]` even *without* the assembly having been opted in. Perhaps any assembly with at least one `[RequiresUnsafe]` should also be considered annotated? This seems a topic for further discussion. ================================================ FILE: proposals/README.md ================================================ # C# Language Proposals Language proposals are living documents describing the current thinking about a given language feature. Proposals can be either *active*, *inactive*, *rejected* or *done*. *Active* proposals are stored directly in the proposals folder, *inactive* and *rejected* proposals are stored in the [inactive](inactive) and [rejected](rejected) subfolders, and *done* proposals are archived in a folder corresponding to the language version they are part of. ## Lifetime of a proposal A proposal starts its life when the language design team decides that it might make a good addition to the language some day. Typically it will start out being *active*, but if we want to capture an idea without wanting to work on it right now, a proposal can also start out in the *inactive* subfolder. Proposals may even start out directly in the *rejected* state, if we want to make a record of something we don't intend to do. For instance, if a popular and recurring request is not possible to implement, we can capture that as a rejected proposal. The proposal may start out as an idea in a [discussion issue](https://github.com/dotnet/csharplang/labels/Discussion), or it may come from discussions in the Language Design Meeting, or arrive from many other fronts. The main thing is that the design team feels that it should be done, and that there's someone who is willing to write it up. Typically a member of the Language Design Team will assign themselves as a champion for the feature, tracked by a [Champion issue](https://github.com/dotnet/csharplang/labels/Proposal%20champion). The champion is responsible for moving the proposal through the design process. A proposal is *active* if it is moving forward through design and implementation toward an upcoming release. Once it is completely *done*, i.e. an implementation has been merged into a release and the feature has been specified, it is moved into a subdirectory corresponding to its release. If a feature turns out not to be likely to make it into the language at all, e.g. because it proves unfeasible, does not seem to add enough value or just isn't right for the language, it will be [rejected](rejected). If a feature has reasonable promise but is not currently being prioritized to work on, it may be declared [inactive](inactive) to avoid cluttering the main folder. It is perfectly fine for work to happen on inactive or rejected proposals, and for them to be resurrected later. The categories are there to reflect current design intent. ## Nature of a proposal A proposal should follow the [proposal template](proposal-template.md). A good proposal should: - Fit with the general spirit and aesthetic of the language. - Not introduce subtly alternate syntax for existing features. - Add a lot of value for a clear set of users. - Not add significantly to the complexity of the language, especially for new users. ## Discussion of proposals Feedback and discussion happens in [discussion issues](https://github.com/dotnet/csharplang/labels/Discussion). When a new proposal is added to the proposals folder, it should be announced in a discussion issue by the champion or proposal author. ================================================ FILE: proposals/anonymous-using-declarations.md ================================================ # Anonymous using declarations Champion issue: ## Summary Permit `using` declarations whose variable is anonymous: ```cs void M() { using _ = GetDisposable(); } ``` ## Motivation This statement form adds the final corner to the following square: | | **New variable name** | **No new variable name** | |------------------|-----------------------------|--------------------------| | **New scope** | `using (var name = expr) {` | `using (expr) {` | | **No new scope** | `using var name = expr;` | `using _ = expr;` 🆕 | It has been wildly popular to remove or avoid nesting by preferring the bottom form inside the "New variable" column to the top form. However, there has been no corresponding opportunity in the other column. The absence of this corner has been painfully felt. When a language user doesn't want the extra nesting, but also has no use for the variable, this results in workarounds such as creating variables named `_`, `_1`, `_2`, and so on. ```cs using var _1 = factory.Lease(out var widget); using var _2 = widget.BeginScope(); ``` Another place where this request comes up is when using ConfigureAwait for `await using`. The ConfigureAwait extension method on IAsyncDisposable returns a wrapper which is only good for disposal, and useless to place in a new variable. Yet users must place it in a new variable, or else they must increase nesting just because they're using ConfigureAwait. ```cs var stream = await client.GetStreamAsync(...).ConfigureAwait(false); await using var _3 = stream.ConfigureAwait(false); ``` By using throwaway variable names as seen in community discussions and the Roslyn codebase itself ([one example](https://github.com/dotnet/roslyn/blob/6b2b0e4c7c3e0470c44ad22653996231a013d8e1/src/Workspaces/Remote/Core/AbstractAssetProviderExtensions.cs#L54-L60)), language users are already attempting to use the language as though this feature existed. ## Detailed design *using_statement* syntax is expanded to include a new *using_discard_statement* syntax, `'await'? 'using' '_' '=' expression ';'` (see [Specification](#specification)). The lifetime of the resources acquired in a *using_discard_statement*, and the allowed locations for a *using_discard_statement*, are defined in the same way as for C# 8's [`using_declaration` syntax](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/using.md#using-declaration). This proposal does not create support for multiple discards in a single using statement: ```cs ❌ NOT supported: using _ = expr1, _ = expr2; using _ = expr1, expr2; ``` *using_declaration* syntax is not expanded or unified. *using_declaration* is based on *implicitly_typed_local_variable_declaration* and *explicitly_typed_local_variable_declaration* which do not include *simple discards*. A *simple discard* is a special case of a *declaration_expression* ([§12.17](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#1217-declaration-expressions)). (Specification work for *using_declaration* is [ongoing](https://github.com/dotnet/csharpstandard/pull/672/files#diff-20796c21eeccfd2c773f2969d23440cbc04e7439f4777729aad5d602477c232fR2057) at the time of writing.) ## Specification [§13.14 The using statement](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1314-the-using-statement) is updated as follows. ```diff using_statement : 'await'? 'using' '(' resource_acquisition ')' embedded_statement + | using_discard_statement ; resource_acquisition : non_ref_local_variable_declaration | expression ; non_ref_local_variable_declaration : implicitly_typed_local_variable_declaration | explicitly_typed_local_variable_declaration ; +using_discard_statement + : 'await'? 'using' '_' '=' expression ';' + ; ``` Additions in **bold**: > If the form of *resource_acquisition* is *local_variable_declaration* then the type of the *local_variable_declaration* shall be either `dynamic` or a resource type. If the form of *resource_acquisition* is *expression*, **or the form of *using_statement* is *using_discard_statement* with its constituent *expression*,** then this expression shall have a resource type. If `await` is present, the resource type shall implement `System.IAsyncDisposable`. A `ref struct` type cannot be the resource type for a `using` statement with the `await` modifier. Removals in ~~strikeout~~, additions in **bold**: > ~~A `using` statement of the form:~~ **`using` statements of the other two forms:** > > ```csharp > using («expression») «statement» > ``` > > **and:** > > ```csharp > using _ = «expression»; > ``` > > ~~has~~ **have** the same possible formulations. A new subsection is added: > ### Using discard statements > > A *using discard statement* has the same semantics as, and can be rewritten as, the corresponding parenthesized form of the using statement, as follows: > > ```csharp > using _ = «expression»; > // statements > ``` > > is semantically equivalent to > > ```csharp > using («expression») > { > // statements > } > ``` > > and > > ```csharp > await using _ = «expression»; > // statements > ``` > > is semantically equivalent to > > ```csharp > await using («expression») > { > // statements > } > ``` > > A using discard statement shall not appear directly inside a `case` label, but, instead, may be within a block inside a `case` label. ## Alternatives `using expr;` would be a consistent extrapolation from the existing constructs, where you simply remove the curly braces and parentheses and add a semicolon. However, this statement form would conflict with using directives, particularly in top-level statements. `using _ = expr;` *also* conflicts with using alias directives, but `_` has a strong sense of "discard" and is discouraged as an identifier name. (There is an [open question](#conflict-with-using-aliases-named-_-in-top-level-statements) on this point.) ## Open questions ### Simpler form versus standing out In most cases, the expression in a using statement will not be of the form `A.B`. It is more common to call a constructor or method. In these common scenarios, there would be very little visual confusion: ```cs using someLock.BeginScope(); using new SomeResource(...); ``` The compiler could disambiguate by checking whether the expression binds to a namespace or type versus to some other kind of expression. The human reader would only be confused if this feature was used in a misleading way. Even when the underlying syntax is the same, as in the following deliberately-contrived example, it is obvious from context that the first `using` below involves a namespace or type, and that the second `using` does not: ```cs using System.ComponentModel; var result = TryXyz(); using result.Value; ``` If this syntax was available, users could *also* write `using _ = expr;` as well in scenarios where they felt it improved clarity. It would work the same way as `using (_ = expr) {` does today. This option optimizes for the user's flexibility in expressiveness. Community feedback is already beginning to show a desire for this flexibility. Shall we proceed to design the simpler form, allowing `using new SomeResource();` and getting `using _ = new SomeResource();` as a corollary, or shall we design the form that stands out visually with `_ =` as the only form? ### Disallowed top-level expressions This open question assumes the `using expr;` syntax. If the top-level expression is a parenthesized expression, this syntax would conflict with `using (expr);` which compiles successfully with a warning about an empty statement. Shall we disallow the expression to be a parenthesized expression? If the top-level expression is a simple assignment expression, this syntax would conflict in top-level statements with using alias directives: `using existingVariable = expr;`. Shall we disallow the expression to be a simple assignment expression, or disambiguate based on whether expr is a type or namespace? ### Conflict with using aliases named `_` in top-level statements This open question assumes the `using _ = expr;` syntax. A legal program may exist as follows: ```cs using _ = SomeNamespace; var xyz = new _.SomeType(); ``` Disallowing the use of `_` as an identifier has been previously discussed. Is there appetite for a tiny subset of this breaking change, disallowing `_` as an identifier *only* for using aliases and *only* in files with top-level statements? Other alternatives: - Disallow usings with discards in top-level statements - Only permit usings with discards in top-level statements when nested inside a block statement - Only permit usings with discards in top-level statements when following some other top-level statement - Always allow both forms, and prefer the existing meaning if the expression binds to a namespace or type, and prefer the new meaning if the expression binds to a value. ================================================ FILE: proposals/async-main-update.md ================================================ # Async main codegen update ## Summary We update the codegen for `async Task Main` to allow the compiler to use a separate helper from the runtime when present. ## Motivation In some scenarios, the runtime needs to be able to hook the real `Main` method, not just the `void` or `int` returning method that is generated to wrap the user-written `async Task` method. To facilitate this, we allow codegen to use a different method to call the real user-written `Main` method. ## Detailed Design We update section [§7.1](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#71-application-startup) of the spec as follows: ```diff - Otherwise, the synthesized entry point waits for the returned task to complete, calling `GetAwaiter().GetResult()` on the task, using either the parameterless instance method or the extension method described by [§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271). If the task fails, `GetResult()` will throw an exception, and this exception is propagated by the synthesized method. + Otherwise, the synthesized entry point waits for the returned task to complete, either passing the task to `System.Runtime.CompilerServices.AsyncHelpers.HandleAsyncEntryPoint` (if it exists) or calling `GetAwaiter().GetResult()` on the task, using either the parameterless instance method or the extension method described by [§C.3](standard-library.md#c3-standard-library-types-not-defined-in-isoiec-23271). If the task fails, the calling method form will throw an exception, and this exception is propagated by the synthesized method. ``` The compiler will look for the following APIs from the core libraries. We do not look at implementations defined outside the core library: ```cs namespace System.Runtime.CompilerServices; public class AsyncHelpers { public static void HandleAsyncEntryPoint(System.Threading.Tasks.Task task); public static int HandleAsyncEntryPoint(System.Threading.Tasks.Task task); } ``` When present the resulting transformation looks like this: ```cs // Original code public class C { public static async Task Main() { await System.Threading.Tasks.Task.Yield(); Console.WriteLine("Hello world"); } } // Lowered pseudocode, not including async state machine if present public class C { public static void $
() { System.Runtime.CompilerServices.AsyncHelpers.HandleAsyncEntryPoint(Main()); } public static async Task Main() { await System.Threading.Tasks.Task.Yield(); Console.WriteLine("Hello world"); } } ``` See also the approved API discussion in https://github.com/dotnet/runtime/issues/121046. ================================================ FILE: proposals/block-bodied-switch-expression-arms.md ================================================ # Block-bodied switch expression arms Champion issue: ## Summary [summary]: #summary This proposal is an enhancement to the new switch expressions added in C# 8.0: allowing multiple statements in a switch expression arm. We permit braces after the arrow, and use `break value;` to return a value from the switch expression arm. ## Motivation [motivation]: #motivation This addresses a common complaint we've heard since the release of switch expressions: users would like to execute multiple things in a switch-expression arm before returning a value. We knew that this would be a top request after initial release, and this is a proposal to address that. This is not a fully-featured proposal to replace [`sequence expressions`](https://github.com/dotnet/csharplang/issues/377). Rather, it is constrained to just address the complaints around switch expressions specifically. It could serve as a prototype for adding sequence expressions to the language at a later date in a similar manner, but isn't intended to support or replace them. ## Detailed design [design]: #detailed-design We allow users to put brackets after the arrow in a switch expression, instead of a single statement. These brackets contain a standard statement list, and the user must use a `break` statement to "return" a value from the block. The end of the block must not be reachable, as in a non-void returning method body. In other words, control is not permitted to flow off the end of this block. Any switch arm can choose to either have a block body, or a single expression body as currently. As an example: ```cs void M(List myObjects) { var stringified = myObjects switch { List strings => string.Join(strings, ","), List others => { string result = string.Empty; foreach (var other in others) { if (other.IsFaulted) return; else if (other.IsLastItem) break; // This breaks the foreach, not the switch result += other.ToString(); } break result; }, _ => { var message = $"Unexpected type {myObjects.GetType()}"; Logger.Error(message); throw new InvalidOperationException(message); } }; Console.WriteLine(stringified); } ``` We make the following changes to the grammar: ```antlr switch_expression_arm : pattern case_guard? '=>' expression | pattern case_guard? '=>' block ; break_statement : 'break' expression? ';' ; ``` It is an error for the endpoint of a switch expression arm's block to be reachable. `break` with an expression is only allowed when the nearest enclosing `switch`, `while`, `do`, `for`, or `foreach` statement is a block-bodied switch expression arm. Additionally, when the nearest enclosing `switch`, `while`, `do`, `for`, or `foreach` statement is a block-bodied switch expression arm, an expressionless `break` is a compile-time error. When a pattern and case guard evaluate to true, the block is executed with control entering at the first statement of the block. The type of the switch expression is determined with the same algorithm as it does today, except that, for every block, all expressions used in a `break expression;` statement are used in determining the _best common type_ of the switch. As an example: ```cs bool b = ...; var o = ...; _ = o switch { 1 => (byte)1, 2 => { if (b) break (short)2; else break 3; } _ => 4L; }; ``` The arms contribute `byte`, `short`, `int`, and `long` as possible types, and the best common type algorithm will choose `long` as the resulting type of the switch expression. ## Drawbacks [drawbacks]: #drawbacks As with any proposals, we will be complicating the language further by doing these proposals. With this proposal, we will effectively lock ourselves into a design for sequence expressions (should we ever decide to do them), or be left with an ugly wart on the language where we have two different syntax for similar end results. ## Alternatives [alternatives]: #alternatives An alternative is the more general-purpose sequence expressions proposal, https://github.com/dotnet/csharplang/issues/377. This (as currently proposed) would enable a more restrictive, but also more widely usable, feature that could be applied to solve the problems this proposal is addressing. Even if we don't do general purpose sequence expressions at the same time as this proposal, doing this form of block-bodied switch expressions would essentially serve as a prototype for how we'd do sequence expressions in the future (if we decide to do them at all), so we likely need to design ahead and ensure that we'd either be ok with this syntax in a general-purpose scenario, or that we're ok with rejecting general purpose sequence expressions as a whole. ## Unresolved questions [unresolved]: #unresolved-questions * Should we allow labels/gotos in the body? We need to make sure that any branches out of block bodies clean up the stack appropriately and that labels inside the body are scoped appropriately. * In a similar vein, should we allow return statements in the block body? The example shown above has these, but there might be unresolved questions around stack spilling, and this will be the first time we would introduce the ability to return from _inside_ an expression. ================================================ FILE: proposals/breaking-change-warnings.md ================================================ # Breaking change warnings Champion issue: ## Summary [summary]: #summary Allow very limited breaking changes in C# when this enables significantly simpler feature designs that are easier to learn, understand and use. Retroactively add warnings in previous language versions to help identify and fix user code that would be vulnerable to such breaks upon a language version upgrade. ## Motivation [motivation]: #motivation We currently restrict new C# language features from causing any breaks (errors or behavior changes) to existing code, occasionally leading to unnatural and unintuitive design choices that make the language harder than necessary to learn and reason about. Examples include discards `_` (sometimes an identifier), `var` (sometimes a type name) and the upcoming `field` access in auto-properties. Breaking changes _should_ be rare and have limited impact, but sometimes they are the right thing to do. This proposal creates a mechanism by which the effects of such breaks are mitigated for existing code. ## Detailed design [design]: #detailed-design When a new language feature is added in C# version `n` that may cause existing code to error or work differently, such code is detected in C# versions `n - 1` and lower, and a warning is yielded with a suggestion for how to fortify the code against the future break. It is customary that newer compilers are used to compile older versions of C#. The compiler that supports C# version `n` will implement these warnings when used to compiler older language versions. ### Example: field access in auto-properties As an example, let's say we introduce [field access in auto-properties](https://github.com/dotnet/csharplang/blob/e222e8b490b5c231430260c3fd3f8df8bcab9a3c/proposals/semi-auto-properties.md) in C# 13, with a design that introduces a new `field` parameter in scope within property accessors. This new `field` parameter would shadow access to any field etc. called `field` in existing property accessors, potentially altering its meaning. We would accompany that feature with a warning in C# 12 and lower for any code that uses the identifier `field` within a property accessor: ``` c# public class Entity { string field; public string Field { get { return field; } // Warning in C# 12 and below set { field = value.Trim(); } // Warning in C# 12 and below } ... } ``` In C# 13 the warning would go away, and `field` in the accessors would start referencing the underlying generated field for `Field`, which would now be considered an auto-property. ## Drawbacks [drawbacks]: #drawbacks ### New warnings A small number of C# users will see new warnings on existing code after directly or indirectly upgrading their compiler. These warnings point out important problems with their code, should they want to move to a newer language version. Hopefully users will find these warnings useful, and will prefer this early warning to finding themselves broken at a later stage. Users that do not find them useful - e.g. because they never plan to upgrade - can simply turn them off. Tooling might embrace more sophisticated models here - "turn off temporarily", "fix automatically", etc. - that further mitigate any inconvenience of the experience, but that is outside of the scope of this proposal, and can be developed independently over time. ### Missed warnings Upgrading to a new compiler must happen *before* upgrading to the new language version that the compiler supports, so technically there will always be a window of time to observe the warnings. However, users might upgrade both without a single compile in between, or they may have turned off the warnings and forgot to turn them on again. It seems tooling can be helpful in avoiding situations where breaking change warnings are missed. For instance it could look for explicit `#pragma warning disable` directives for breaking change warnings "from the past", or try to "check one last time" on explicit language version upgrade gestures in the tool. Such features are outside of the scope of this proposal, and can be developed independently over time. ## Alternatives [alternatives]: #alternatives ### Don't break An alternative is to stick to the current policy and continue to live with the feature contortions that result from avoiding breaking changes at all cost. ### Break but don't mitigate At the other end of the scale, we could allow breaking changes to the same limited level as proposed here, but simply not provide any language-level mechanism around it. Wherever code changes meaning, people will have to use docs and testing to find, debug and understand where things went wrong. Breaks may not yield errors, and differences in behavior may be subtle, so this will likely be more disruptive to users. ### Upgrading tool This is a variant of "break don't mitigate" where the tools people use to write C# code would direct them through a dedicated upgrade experience, which would do breaking change checking as one of its steps towards a new language version. This seems like a "boil the ocean" approach, since everyone has to take that route, even though the vast majority of users won't be affected by the breaking change. Also, it suffers from the risk that people bypass the upgrade tool (e.g. by manually changing the version in their project file) and miss out on the checking. ## Unresolved questions [unresolved]: #unresolved-questions For every breaking change, there will be specific design considerations concerning the associated warnings: Which patterns in existing code will trigger them? Which fixes will they recommend? ## Design meetings - [Mar 8, 2023](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-08.md#limited-breaking-changes-in-c): Based on discussion [#7033](https://github.com/dotnet/csharplang/discussions/7033) the LDM decided to continue to pursue the topic. ================================================ FILE: proposals/case-declarations.md ================================================ # Case Declarations ## Summary A case declaration is a shorthand syntax for declaring a nested case of a closed type. It infers much of what it is from context. ```csharp public closed record GateState { case Closed; case Locked; case Open(float Percent); } ``` When used within a record declaration, it is the equivalent of writing: ```csharp public closed record GateState { public sealed record Closed : GateState; public sealed record Locked : GateState; public sealed record Open(float Percent) : GateState; } ``` ## Motivation To provide a concise way to specify simple type cases of a closed type that can be transitioned into more complex declarations without falling off a syntax cliff. ## Specification ### Semantics - A case declaration can only be specified in the body of a closed class, record or union. - The existence of a case declaration is independent of other declarations in the containing class. ### Declaration A case declaration includes the `case` keyword followed by a type declaration, similar to a class or record, without the keywords, modifiers or base type. ```csharp case Open(float Percent); ``` #### Grammar TBD #### Attributes A case declaration may declare custom attributes. ```csharp [Attribute] case Open(float Percent); ``` #### Modifiers The case declaration may not specify any modifiers. The class or record is always `public` and `sealed`. #### Partial Case Classes TBD. Probably not, since you cannot specify partial modifier. #### Type Parameters The case declaration may not specify type parameters, as that would make exhaustiveness impossible, but it may refer to any type parameter declared by the containing class. #### Base Type A case declaration may not declare a base type. For a containing class or record, the case types base type is always the containing class. #### Interfaces A case declaration may declare interfaces. ```csharp case Open(float percent) : ISomeInterface { ... } ``` #### Body A case declaration may declare a body with member declarations. ```csharp case Open(float percent) : { pubic void SomeMethod() {...} } ``` ---- ## Optional Features Optional features are suggestions for additional work that could be done to enhance the core proposal. ### Singleton Case Classes Case declarations that are records without properties include a static property that is a singleton value for the class. For example, ```csharp case Closed; ``` becomes: ```csharp public sealed record Closed : GateState { public static Closed Instance => field ??= new Closed(); } ``` References to singleton case values can be reduced to referencing the static property. ```csharp GateState state = GateState.Closed.Instance; ``` With the addition of a static value operator feature, not specified here, the case declaration type can be converted to the singleton value when referenced in non-type contexts that would shorten this to: ```csharp GateState state = GateState.Closed; ``` With the addition of a target typed member lookup feature, not specified here, this would shorted further to: ```csharp GateState state = Closed; ``` ================================================ FILE: proposals/closed-enums.md ================================================ # Closed Enums Champion issue: https://github.com/dotnet/csharplang/issues/9011 ## Summary Allow an enum type to be declared closed: ``` c# public closed enum Color { Red, Green, Blue } ``` This prevents creation of enum values other than the specified members. A consuming switch expression that covers all its specified members can therefore be concluded to "exhaust" the closed enum - it does not need to provide a default case to avoid warnings: ``` c# Color color = ...; string description = color switch { Red => "red", Green => "green", Blue => "blue" // No warning about missing cases }; ``` ## Motivation Many enum types are not intended to take on values beyond the declared members, but the language provides no way to express that intent, let alone guard against it happening. For consumers of the type this means that no set of enum values short of the full range of the underlying integral type will be considered to "exhaust" the enum type, and a switch expression needs to include a catch-all case to avoid warnings. Closed enums provide a way to indicate that the set of enum mebers is complete, and allow consuming code to rely on that for exhaustiveness in switch expressions. ## Detailed design ### Syntax Allow `closed` as a modifier on enum types. ### Enforcement The following restrictions apply to closed enum types compared to other enum types: - A closed enum must declare a member corresponding to the integral value `0`. - Explicit enumeration conversions are not allowed _to_ a closed enum type, except from a constant whose value corresponds to a declared member. - Operators that return a closed enum type are only allowed over constant operands, and it is an error for them to produce a value that is not a declared member. ``` c# Color c = 0; // Ok, all closed enums hve a member corresponding to the value `0` c = (Color)1; // Ok, the constant `1` corresponds to the member `Green` c = (Color)10; // Error, there is no member corresponding to the constant `10` c = (Color)myInt; // Error, `myInt` is not constant _ = c == Color.Blue; // Ok, `==` does not return a closed enum _ = Color.Red + 1; // Ok, operands are constant and result corresponds to the member `Green` _ = c + 1; // Error, operand `c` is not constant ``` ### Exhaustiveness in switches A `switch` expression that handles all of the members of a closed enum type will be considered to have exhausted that enum type. That means that some non-exhaustiveness warnings will no longer be given: ``` c# Color c = ...; _ = c switch { Red => ..., Green => ..., Blue => ... // No warning about non-exhaustive switch }; ``` On the other hand this also means that it can be an error for the closed enum type to occur as a case after all its members: ``` c# Color c = ...; _ = c switch { Red => ..., Green => ..., Blue => ..., Color => ... // Error, case cannot be reached }; ``` ### Lowering Closed enums are generated with a `Closed` attribute, to allow them to be recognized by a consuming compiler. ## Drawbacks It can be a breaking change to add a `closed` modifier to an existing enum, or to add an additional member to an existing closed enum. Before publishing a closed enum type, the author needs to consider the long term contract it implies with its consumers. ## Alternatives - Instead of a new `closed` modifier, a closed enum could be designated with a `[Closed]` attribute. ## Open questions - Can closed enums be generated into IL in a way that prevents other languages and compilers from allowing unintended operations on them even if they do not implement the feature? - Should closed `[Flags]` enums be supported? If so, they should allow the binary logical (bitwise) operators `&`, `|` and `^`. ================================================ FILE: proposals/closed-hierarchies.md ================================================ # Closed Hierarchies Champion issue: https://github.com/dotnet/csharplang/issues/9499 ## Summary Allow a class to be declared `closed`. This prevents directly derived classes from being declared in a different assembly: ``` c# // Assembly 1 public closed record class GateState; public record class Closed : GateState; public record class Open(float Percent) : GateState; // Assembly 2 public record class Locked : GateState; // ERROR - 'GateState' is a closed class ``` Since all derived classes are declared in the closed class' assembly, a consuming `switch` expression that covers all of them can be concluded to "exhaust" the closed class - it does not need to provide a default case to avoid warnings. ``` c# // Assembly 3 GateState state = ...; string description = state switch { Closed => "closed", Open(var percent) => $"{percent}% open" // No warning about missing cases }; ``` ## Motivation Many class types are not intended to be extended by anyone but their authors, but the language provides no way to express that intent, let alone guard against it happening. For consumers of the class this means that no set of derived classes will be considered to "exhaust" the base class, and a switch expression needs to include a catch-all case to avoid warnings. Closed classes provide a way to indicate that a set of derived classes is complete, and allow consuming code to rely on that for exhaustiveness in switch expressions. ## Detailed design ### Syntax Allow `closed` as a modifier on classes. A `closed` class is implicitly abstract whether or not the `abstract` modifier is specified. Thus, it cannot also have a `sealed` or `static` modifier. A class deriving from a closed class is *not* itself closed unless explicitly declared to be. ### Same-assembly restriction If a class in one assembly is declared `closed` then it is an error to directly derive from it in another assembly: ``` c# // Assembly 1 public closed class CC { ... } public class CO : CC { ... } // Ok, same assembly // Assembly 2 public class C1 : CC { ... } // Error, 'CC' is closed and in a different assembly public class C2 : CO { ... } // Ok, 'CO' is not closed ``` ### Type parameter restriction If a generic class directly derives from a closed class, then all of its type parameters must be used in the base class specification: ```csharp closed class C { ... } class D1 : C { ... } // Ok, 'U' is used in base class class D2 : C { ... } // Ok, 'V' is used in base class class D3 : C { ... } // Error, 'W' is not used in base class ``` This rule is to ensure that there is a single generic instantiation of the derived type that "exhausts" a given generic instantiation of the closed base type. *Note:* This rule may not be sufficient if we allow closed interfaces at some point, because a) classes can implement multiple generic instantiations of the same interface, and b) interface type parameters can be co- or contravariant. At such point we'd need to refine the rule to continue to ensure that there's only ever one generic instantiation of a given derived type per generic instantiation of a closed base type. ### Exhaustiveness in switches A `switch` expression that handles all of the direct descendants of a closed class will be considered to have exhausted that class. That means that some non-exhaustiveness warnings will no longer be given: ``` c# CC cc = ...; _ = cc switch { CO co => ..., // No warning about non-exhaustive switch }; ``` On the other hand this also means that it can be an error for the closed base class to occur as a case after all its direct descendants: ``` c# _ = cc switch { CO co => ..., CC cc => ..., // Error, case cannot be reached }; ``` *Note:* There may not exist valid derived classes for certain generic instantiations of a closed base class. An exhaustive switch only needs to specify cases for derived types that are actually possible. For example: ```csharp closed class C { ... } class D1 : C { ... } class D2 : C { ... } ``` For `C`, for instance, there is no corresponding instantiation of `D2<...>`, and no case for `D2<...>` needs to be given in a switch: ```csharp C cs = ...; _ = cs switch { D1 d1 => ..., // No need for a 'D2<...>' case - no instantiation corresponds to 'C' } ``` ### Lowering Closed classes are generated with a `Closed` attribute, to allow them to be recognized by a consuming compiler. ## Drawbacks - It can be a breaking change to add a `closed` modifier to an existing class, or to add an additional derived class from a closed class. Before publishing a closed class, the author needs to consider the long term contract it implies with its consumers. - Unless we find a way to prevent it, "unauthorized" derived classes may be allowed by unwitting other compilers, leading to the risk that the set of cases is not in fact closed at runtime. ## Alternatives - Instead of a new `closed` modifier, a closed class could be designated with a `[Closed]` attribute. - The scope of where descendants are allowed could be narrowed further to a file (although that would not have a lot of precedent in C#) or to inside the body of the closed class as nested classes. - The closed set of allowed descendants could be given as a list instead of implied by where declarations occur. This would allow inclusion of classes in other assemblies. ## Optional features - Interfaces could also be allowed to be closed. The rules would be very similar. ## Open questions ### ClosedAttribute We propose using the following attribute to denote that a class is closed in metadata: ```cs namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class ClosedAttribute : Attribute { } } ``` ### Blocking subtyping from other languages/compilers Can closed classes be generated into IL in a way that prevents other languages and compilers from deriving from them even if they do not implement the feature? We propose accomplishing this by adding `[CompilerFeatureRequired("ClosedClasses")]` to all constructors of closed classes. Since constructors of abstract types can generally only be used in a constructor initializer, this seems to effectively prevent languages which don't understand the feature, from allowing user to declare a subclass of a closed class. ```cs // Authoring assembly, built with .NET 10 SDK closed class C1 { public C1() { } public C1(int param) { } } // Consuming assembly, built with .NET 8 SDK class C2 : C1 { public C2() { } // error: 'C1.C1()' requires compiler feature "ClosedClasses" public C2() : base(42) { } // error: 'C1.C1(int)' requires compiler feature "ClosedClasses" } ``` Metadata "view" of `C1`: ```cs [Closed] class C1 { [CompilerFeatureRequired("ClosedClasses")] public C1() { } [CompilerFeatureRequired("ClosedClasses")] public C1(int param) { } } ``` #### Use of multiple `[CompilerFeatureRequired]` attributes A [question came up](https://github.com/dotnet/roslyn/pull/82052#discussion_r2759549101) about a case where a closed class has required members: ```cs closed class C1 { public C() { } public required string P { get; set; } } // Metadata: class C1 { // Do we expect all of the following to be emitted? // Or do we want to drop CompilerFeatureRequired("RequiredMembers"), for example, on the assumption that 'any compiler that supports closed classes should also support required members'?("RequiredMembers")? [Obsolete("Types with required members are not supported in this version of your compiler")] [CompilerFeatureRequired("RequiredMembers")] [CompilerFeatureRequired("ClosedClasses")] public C1() { } } ``` Do we want to emit all attributes in the above sample or just a subset of them? ### Same module restriction The proposal text mentions that there must be a "same assembly" restriction. We propose strengthening this restriction so that subtypes may only be declared in the same module. That is, declaring a closed class in a netmodule, and subtyping it in a different module, should not be permitted, because it breaks the ability to determine the exhaustive set of subtypes from the context of the original declaration in a similar way as cross-assembly subtyping. ### Permit explicit use of abstract modifier The `closed` modifier also makes the class abstract. Should we permit both `closed` and `abstract` to be used on a declaration? Permitting it may give an impression that the `abstract` modifier is making a difference. ```cs closed abstract class C { } // equivalent to: closed class C { } // compare with: abstract interface I // error { abstract void M(); // ok } internal class C { } // ok, explicitly specifying the default accessibility ``` ### TODO: Should subtypes be marked in metadata? Note: this is more of an engineering question, and, probably should not be brought to LDM until we have evidence this solves a perf problem, we have considered the tradeoffs, etc. It seems like when a closed type is referenced from metadata, there is a need to search its containing assembly for all possible subtypes of it, to check exhaustiveness of patterns in the consuming compilation. This might be expensive. Would there be a benefit in encoding on a closed type itself, the list of subtypes which have it as a base type? At first glance, something like `[ClosedSubtype(typeof(Subtype1), typeof(Subtype2), typeof(GenericSubtype1<>), ...)]` in metadata seems viable. Note that the compiler would still need to examine the base type of each subtype to look at the way a generic closed type is being constructed, for example, to handle cases such as `class GenericSubtype1 : ClosedType>`. In this case, the amount and identities of the subtypes will depend on the specific type arguments to `ClosedType`. ================================================ FILE: proposals/collection-expression-arguments.md ================================================ # Collection expression arguments Champion issue: ## Motivation The [*dictionary expression*](https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md) feature has identified a need for collection expressions to pass along user-specified data in order to configure the behavior of the final collection. Specifically, dictionaries allow users to customize how their keys compare, using them to define equality between keys, and sorting or hashing (in the case of sorted or hashed collections respectively). This need applies when creating any sort of dictionary type (like `D d = new D(...)`, `D d = D.CreateRange(...)` and even `IDictionary<...> d = `) To support this, a new `with(...arguments...)` element is proposed as the first element of a collection expression like so: ```c# Dictionary nameToAge = [with(comparer), .. d1, .. d2, .. d3]; ``` 1. When translating to a `new CollectionType(...)` call, these `...arguments...` are used to determine the appropriate constructor and are passed along accordingly. 2. When translating to a `CollectionFactory.Create` call, these `...arguments...` are passed before with the `ReadOnlySpan` elements argument, all of which are used to determine the appropriate `Create` overload, and are passed along accordingly. 3. When translating to an interface (like `IDictionary<,>`) only a single argument is allowed. It implements one of the well-known BCL comparer interfaces, and will be used to control the key comparing semantics of the final instance. This syntax was chosen as it: 1. Keeps all information within the `[...]` syntax. Ensuring that the code still clearly indicates a collection being created. 1. Does not imply calling a `new` constructor (when that isn't how all collections are created). 1. Does not imply creating/copying the values of the collection multiple times (like a postfix `with { ... }` might. 1. Does not contort order of operations, especially with C#'s consistent left-to-right expression evaluation ordering semantics. For example, it does not evaluate the arguments used to construct a collection *after* evaluating the expressions used to populate the collection. 1. Does not force a user to read to the end of a (potentially large) collection expression to determine core behavioral semantics. For example, having to see to the end of a hundred-line dictionary, only to find that, yes, it was using the right key comparer. 1. Is both not subtle, while also not being excessively verbose. For example, using `;` instead of `,` to indicate arguments is a very easy piece of syntax to miss. `with()` only adds 6 characters, and will easily stand out, especially with syntax coloring of the `with` keyword. 1. Reads nicely. "This is a collection expression 'with' these arguments, consisting of these elements." 1. Solves the need for comparers for both dictionaries and sets. 1. Ensures any user need for passing arguments, or any needs we ourselves have beyond comparers in the future are already handled. 1. Does not conflict with any existing code (using https://grep.app/ to search). ## Design Philosophy The below section covers prior design philosophy discussions. Including why certain forms were rejected. There are two main directions we can go in to supply this user-defined data. The first is to special case *only* values in the *comparer* space (which we define as types inheriting from the BCL's `IComparer` or `IEqualityComparer` types). The second is to provide a generalized mechanism to supply arbitrary arguments to the final invoked API when creating collection expressions. The primary *dictionary expression* specification shows how we could do the former, while this specification seeks to do the latter. Examinations of the solutions for just passing *comparers* have revealed weaknesses in their approach if we wanted to expand them to *arbitrary arguments*. For example: 1. Reusing *element* syntax, like we do with the form: `[StringComparer.OrdinalIgnoreCase, "mads": 21]`. This works well in a space where `KeyValuePair<,>` and comparers do not inherit from common types. But it breaks down in a world where one might do: `HashSet h = [StringComparer.OrdinalIgnoreCase, "v"]`. Is this passing along a comparer? Or attempting to put two object values into the set? 2. Separating out arguments versus elements with subtle syntax (like using a semicolon instead of a comma to separate them in `[comparer; v1]`). This risks very confusing situations where a user accidentally writes `[1; 2]` (and gets a collection that passes '1' as, say, the 'capacity' argument for a `List<>`, and only contains the single value '2'), when they intended `[1, 2]` (a collection with two elements). Because of this, in order to support arbitrary arguments, we believe a more obvious syntax is needed to more clearly demarcate these values. Several other design concerns have also come up with in this space. In no particular order, these are: 1. That the solution not be ambiguous and cause breaks with code that people are likely using with collection expressions today. For example: ```c# List c = [new(...), w1, w2, w3]; ``` This is legal today, with the `new(...)` expression being a 'implicit object creation' that creates a new widget. We cannot repurpose this to pass along arguments to `List<>`'s constructor as it would *certainly* break existing code. 1. That the syntax not extend to outside of the `[...]` construct. For example: ```c# HashSet s = [...] with ...; ``` These syntaxes can be construed to mean that the collection is created first, and then recreated into a differing form, implying multiple transformations of the data, and potentially unwanted higher costs (even if that's not what is emitted). 1. That `new` as a potential keyword to use *at all* in this space is undesirably confusing. Both because `[...]` *already* indicates that a *new* object is created, and because translations of the collection expression may go through non-constructor APIs (for example, the *Create method* pattern). 1. That the solution not be excessively verbose. A core value proposition of collection expressions is *brevity*. So if the form adds a large amount of syntactic scaffolding, it will feel like a step backwards, and will undercut the value proposition of using collection-expressions, versus calling into the existing APIs to make the collection. Note that a syntax like `new([...], ...)` runs afoul of both '2' and '3' above. It makes it appear as if we are calling into a constructor (when we may not be) *and* it implies that a created collection expression is passed to that constructor, which is definitely is not. Based on all of the above, a small handful of options have come up that are felt to solve the needs of passing arguments, without stepping out of bounds of the goals of collection expressions. ## `[with(...arguments...)]` Design ### Syntax: ```diff collection_element : expression_element | spread_element + | with_element ; +with_element + : 'with' argument_list + ; ``` There is a syntactic ambiguity immediately introduced with this grammar production. Similar to the ambiguity between `spread_element` and `expression_element` (explained [here](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#detailed-design), there is an immediate syntactic ambiguity between `with_element` and `expression_element`. Specifically `with()` is both exactly the production-body for `with_element`, and is also reachable through `expression_element -> expression -> ... -> invocation_expression`. There is a simple overarching rule for collection_elements. Specifically, if the element [lexically](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/lexical-structure.md) starts with the token sequence `with` `(` then it is always treated as a `with_element`. This is beneficial in two ways. First, a compiler implementation needs only look at the immediately following tokens it sees to determine what sort of element to parse. Second, correspondingly, a user can trivially understand what sort of element they have without having to mentally try to parse what follows to see if they should think of it as a `with_element` or an `expression_element`. ### Examples Examples of how this would look are: ```c# // With an existing type: // Initialize to twice the capacity since we'll have to add // more values later. List names = [with(capacity: values.Count * 2), .. values]; ``` These forms seem to "read" reasonably well. In all those cases, the code is "creating a collection expression, 'with' the following arguments to pass along to control the final instance, and then the subsequent elements used to populate it. For example, the first line "creates a list of strings 'with' a capacity of two times the count of the values about to be spread into it" Importantly, this code has little chance of being overlooked like with forms such as: `[arg; element]`, while also adding minimal verbosity, with a large amount of flexibility to pass any desired arguments along. This would *technically* be a breaking change as `with(...)` *could* have been a call to a pre-existing method called `with`. However, unlike `new(...)` which is a *known* and recommended way to create implicitly-typed values, `with(...)` is far less likely as a method name, running afoul of .Net naming for methods. In the unlikely event that a user did have such a method, they would certainly be able to continue calling into the existing method by using `@with(...)`. We would translate this `with(...)` element like so: ```c# List names = [with(/*capacity*/10), ...]; // translates to: // argument_list *becomes* the argument list for the // constructor call. __result = new List(10); // followed by normal initialization // or IList names2 = [with(capacity: 20), ...]; // translates to: __result = new List(20); ``` In other words, the argument_list arguments would be passed to the appropriate constructor if we are calling a constructor, or to the appropriate 'create method' if we are calling such a method. We would also allow a single argument inheriting from the BCL *comparer* types to be provided when instantiating one of the destination dictionary interface types to control its behavior. ## Conversions The [conversions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) section for collection-expressions is updated in the following manner: ```diff > A struct or class type that implements System.Collections.IEnumerable where: - * The type has an applicable constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression. + a. the collection expression has no `with_element` and the type has an applicable constructor + that can be invoked with no arguments, accessible at the location of the collection expression. or + b. the collection expression has a `with_element` and the type has at least one constructor + accessible at the location of the collection expression. ``` Note the actual arguments within the `argument_list` of the `with_element` do not affect if the conversion exists or not. Just the presence or absence of the `with_element` itself. The intuition here is simply that if the collection expression is written without one (like `[x, y, z]`) it would have to be to be able to call the constructor without args. While if it is has `[with(...), x, y, z]` it could then call the appropriate constructor. This also means that types that can *not* invoked with a no-argument constructor *can* be used with a collection expression, but *only* if that collection expression that contains a `with_element`. The actual determination of how a `with_element` will affect construction is given [below](#construction). ## Construction Construction is updated as follows. The elements of a collection expression are evaluated in order, left to right. Within *collection arguments*, the arguments are evaluated in order, left to right. Each element or argument is evaluated exactly once, and any further references refer to the results of this initial evaluation. If *collection_arguments* is included and is not the first element in the collection expression, a compile-time error is reported. If the *argument list* contains any values with *dynamic* type, a compile-time error is reported ([LDM-2025-01-22](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-22.md#conclusion-1)). ### Constructors If the target type is a *struct* or *class type* that implements `System.Collections.IEnumerable`, and the target type does not have a *create method*, and the target type is not a *generic parameter type* then: * [*Overload resolution*](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1264-overload-resolution) is used to determine the best instance constructor from the candidates. * The set of candidate constructors is all accessible instance constructors declared on the target type that are applicable with respect to the *argument list* as defined in [*applicable function member*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12642-applicable-function-member). * If a best instance constructor is found, the constructor is invoked with the *argument list*. * If the constructor has a `params` parameter, the invocation may be in expanded form. * Otherwise, a binding error is reported. ```csharp // List candidates: // List() // List(IEnumerable collection) // List(int capacity) List l; l = [with(capacity: 3), 1, 2]; // new List(capacity: 3) l = [with([1, 2]), 3]; // new List(IEnumerable collection) l = [with(default)]; // error: ambiguous constructor ``` ### CollectionBuilderAttribute methods If the target type is a type with a *create method*, then: * [*Overload resolution*](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1264-overload-resolution) is used to determine the best create method from the candidates. * For each [*create method*](#create-methods) for the target type, we define a *projection method* with an identical signature to the create method but *without the last parameter*. * The set of *candidate projection methods* is the projection methods that are applicable with respect to the *argument list* as defined in [*applicable function member*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12642-applicable-function-member). * If a best projection method is found, the corresponding create method is invoked with the *argument list* appended with a `ReadOnlySpan` containing the elements. * Otherwise, a binding error is reported. ```csharp [CollectionBuilder(typeof(MyBuilder), "Create")] class MyCollection { ... } class MyBuilder { public static MyCollection Create(ReadOnlySpan elements); public static MyCollection Create(IEqualityComparer comparer, ReadOnlySpan elements); } ``` ```c# MyCollection c1 = [with(GetComparer()), "1", "2"]; // IEqualityComparer _tmp1 = GetComparer(); // ReadOnlySpan _tmp2 = ["1", "2"]; // c1 = MyBuilder.Create(_tmp1, _tmp2); MyCollection c2 = [with(), "1", "2"]; // ReadOnlySpan _tmp3 = ["1", "2"]; // c2 = MyBuilder.Create(_tmp3); ``` #### CollectionBuilderAttribute: Create methods For a collection expression where the target type *definition* has a `[CollectionBuilder]` attribute, the *create methods* are the following, **updated** from [*collection expressions: create methods*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#create-methods). > A `[CollectionBuilder(...)]` attribute specifies the *builder type* and *method name* of a method to be invoked to construct an instance of the collection type. > > The *builder type* must be a non-generic `class` or `struct`. > > First, the set of applicable *create methods* `CM` is determined. > It consists of methods that meet the following requirements: > > * The method must have the name specified in the `[CollectionBuilder(...)]` attribute. > * The method must be defined on the *builder type* directly. > * The method must be `static`. > * The method must be accessible where the collection expression is used. > * The *arity* of the method must match the *arity* of the collection type. > * The method must have a **last** parameter of type `System.ReadOnlySpan`, passed by value. > * There is an [*identity conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1022-identity-conversion), [*implicit reference conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1028-implicit-reference-conversions), or [*boxing conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1029-boxing-conversions) from the method return type to the *collection type*. > > Methods declared on base types or interfaces are ignored and not part of the `CM` set. > For a *collection expression* with a target type C<S0, S1, …> where the *type declaration* C<T0, T1, …> has an associated *builder method* B.M<U0, U1, …>(), the *generic type arguments* from the target type are applied in order — and from outermost containing type to innermost — to the *builder method*. The key differences from the earlier algorithm are: * Create methods may have additional parameters *before* the `ReadOnlySpan` parameter. * Multiple create methods are supported. ### Interface target type If the target type is an *interface type*, then: * [*Overload resolution*](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1264-overload-resolution) is used to determine the best candidate method signature. * The set of candidate signatures is the signatures below for the target interface that are applicable with respect to the *argument list* as defined in [*applicable function member*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12642-applicable-function-member). |Interfaces|Candidate signatures| |:---:|:---:| |`IEnumerable`
`IReadOnlyCollection`
`IReadOnlyList`|`()` (no parameters)| |`ICollection`
`IList`|`List()`
`List(int)`| If a best method signature is found, the semantics are as follows: * The candidate signature for `IEnumerable`, `IReadOnlyCollection` and `IReadOnlyList` is simply `()` and has the same meaning as not having the `with()` element at all. * The candidate signatures for `IList` and `ICollection` are the signatures of `List()` and `List(int)` constructors. When constructing the value (see [Mutable Interface Translation](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#mutable-interface-translation)), the respective `List` constructor will be invoked. * Otherwise, a binding error is reported. #### Dictionary-Interface target type This is specified here as part of the feature defined in https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md. The above list is augmented to have the following items: |Interfaces|Candidate signatures| |:---:|:---:| |`IReadOnlyDictionary`|`()` (no parameters)
`(IEqualityComparer? comparer)`| |`IDictionary`|`Dictionary()`
`Dictionary(int)`
`Dictionary(IEqualityComparer)`
`Dictionary(int, IEqualityComparer)`| If a best method signature is found, the semantics are as followed: * The candidate signatures for `IReadOnlyDictionary` are `()` (which has the same meaning as not having the `with()` element at all), and `(IEqualityComparer)`. This comparer will be used to appropriately hash and compare the keys in the destination dictionary the compiler chooses to create (see [Non Mutable Interface Translation](https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md#non-mutable-interface-translation)). * The candidate signatures for `IDictionary` are the signatures of `Dictionary()`, `Dictionary(int)`, `Dictionary(IEqualityComparer)` and `Dictionary(int, IEqualityComparer)`constructors. When constructing the value (see [Mutable Interface Translation](https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md#mutable-interface-translation)), the respective `Dictionary` constructor will be invoked. * Otherwise, a binding error is reported. ```csharp IDictionary d; IReadOnlyDictionary r; d = [with(StringComparer.Ordinal)]; // new Dictionary(StringComparer.Ordinal) r = [with(StringComparer.Ordinal)]; // new $PrivateImpl(StringComparer.Ordinal) d = [with(capacity: 2)]; // new Dictionary(capacity: 2) r = [with(capacity: 2)]; // error: 'capacity' parameter not recognized d = [with()]; // Legal: empty arguments supported for interfaces ``` ### Other target types If the target type is any other type, then a binding error is reported for the *argument list*, even if empty. ```csharp Span a = [with(), 1, 2, 3]; // error: arguments not supported Span b = [with([1, 2]), 3]; // error: arguments not supported int[] a = [with(), 1, 2, 3]; // error: arguments not supported int[] b = [with(length: 1), 3]; // error: arguments not supported ``` ## Ref safety We adjust the [collection-expressions.md#ref-safety](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#ref-safety) rules to account for the `with()` element. See also [§16.4.15 Safe context constraint](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/structs.md#16415-safe-context-constraint). ### Create methods This section applies to collection-expressions whose target type meets the constraints defined in [CollectionBuilderAttribute methods](#collectionbuilderattribute-methods). The *safe-context* is determined by modifying a clause from [collection-expressions.md#ref-safety](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#ref-safety) (changes in **bold**): > * If the target type is a *ref struct type* with a [*create method*](#create-methods), the safe-context of the collection expression is the [*safe-context of an invocation*](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/structs.md#164126-method-and-property-invocation) of the create method where **the arguments are the `with()` element arguments followed by the collection expression as the argument for the last parameter (the `ReadOnlySpan` parameter).** The [*method arguments must match*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#method-arguments-must-match) constraint applies to the collection expression. Similarly to the *safe-context* determination above, the *method arguments must match* constraint is applied by treating the collection expression as an invocation of the create method, where the arguments are the `with()` element arguments followed by the collection expression as the argument for the last parameter. ### Constructor calls This section applies to collection-expressions whose target type meets the constraints defined in [Constructors](#constructors). For a collection-expression of a *ref struct type* of the following form: `[with(a₁, a₂, ..., aₙ), e₁, e₂, ..., eₙ]` The *safe-context* of the collection expression is the narrowest of the *safe-contexts* of the following expressions: - An object creation expression `new C(a₁, a₂, ..., aₙ)`, where `C` is the target type - The element expressions `e₁, e₂, ..., eₙ` (either the expressions themselves, or the spread value in the case of a spread element). The [*method arguments must match*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#method-arguments-must-match) constraint applies to the collection expression. The constraint is applied by treating the collection expression as an object creation of the form `new C(a₁, a₂, ..., aₙ) { e₁, e₂, ..., eₙ }` per [low-level-struct-improvements.md#rules-for-object-initializers](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#rules-for-object-initializers). - Expression elements are treated as if they are collection element initializers. - Spread elements are treated similarly, by temporarily assuming that `C` has an `Add(SpreadType spread)` method, where `SpreadType` is the type of the spread value. ## Answered questions ### `dynamic` arguments Should arguments with `dynamic` type be allowed? That might require using the runtime binder for overload resolution, which would make it difficult to limit the set of candidates, for instance for collection builder cases. **Resolution:** Disallowed. [LDM-2025-01-22](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-22.md#conclusion-1) ### `with()` breaking change The proposed `with()` element is a breaking change. ```csharp object x, y, z = ...; object[] items = [with(x, y), z]; // C#13: ok; C#14: error args not supported for object[] object with(object x, object y) { ... } ``` Confirm the breaking change is acceptable, and whether breaking change should be tied to language version. **Resolution:** Keep previous behavior (no breaking change) when compiling with earlier language version. [LDM-2025-03-17](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion) ### Should arguments affect collection expression conversion? Should collection arguments and the applicable methods affect convertibility of the collection expression? ```csharp Print([with(comparer: null), 1, 2, 3]); // ambiguous or Print(HashSet)? static void Print(List list) { ... } static void Print(HashSet set) { ... } ``` If the arguments affect convertibility based on the applicable methods, arguments should probably affect type inference as well. ```csharp Print([with(comparer: StringComparer.Ordinal)]); // Print(HashSet)? ``` For reference, similar cases with target-typed `new()` result in errors. ```csharp Print(new(comparer: null)); // error: ambiguous Print(new(comparer: StringComparer.Ordinal)); // error: type arguments cannot be inferred ``` **Resolution:** Collection arguments should be ignored in conversions and type inference. [LDM-2025-03-17](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion-1) ### Collection builder method parameter order For *collection builder* methods, should the span parameter be before or after any parameters for collection arguments? Elements first would allow the arguments to be declared as optional. ```csharp class MySetBuilder { public static MySet Create(ReadOnlySpan items, IEqualityComparer comparer = null) { ... } } ``` Arguments first would allow the span to be a `params` parameter, to support calling directly in expanded form. ```csharp var s = MySetBuilder.Create(StringComparer.Ordinal, x, y, z); class MySetBuilder { public static MySet Create(IEqualityComparer comparer, params ReadOnlySpan items) { ... } } ``` **Resolution:** The span parameter for elements should be the last parameter. [LDM-2025-03-12](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-12.md#conclusion-1) ### Arguments with earlier language version Is an error reported for `with()` when compiling with an earlier language version, or does `with` bind to another symbol in scope? **Resolution:** No breaking change for `with` inside a collection expression when compiling with earlier language versions. [LDM-2025-03-17](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion) ### Target types where arguments are *required* Should collection expression conversions be supported to target types where arguments must be supplied because all of the constructors or factory methods require at least one argument? Such types could be used with collection expressions that include explicit `with()` arguments but the types could not be used for `params` parameters. For example, consider the following type constructed from a factory method: ```csharp MyCollection c; c = []; // error: no arguments c = [with(capacity: 1)]; // ok [CollectionBuilder(typeof(MyBuilder), "Create")] class MyCollection : IEnumerable { ... } class MyBuilder { public static MyCollection Create(ReadOnlySpan items, int capacity) { ... } } ``` The same question applies for when the constructor is called directly as in the example below. However, for the target types where the constructor is called directly, the collection expression *conversion* currently **requires a constructor callable with no arguments**, but the collection *arguments* are ignored when determining convertibility. ```csharp c = []; // error: no arguments c = [with(capacity: 1)]; // error: no constructor callable with no arguments? class MyCollection : IEnumerable { public MyCollection(int capacity) { ... } public void Add(T t) { ... } // ... } ``` **Resolution:** Support conversions to target types where all constructors or factory methods require arguments, and require `with()` for the conversion. [LDM-2025-03-05](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-14.md#conclusion-2) ### `__arglist` Should `__arglist` be supported in `with()` elements? ```csharp class MyCollection : IEnumerable { public MyCollection(__arglist) { ... } public void Add(object o) { } } MyCollection c; c = [with(__arglist())]; // ok c = [with(__arglist(x, y)]; // ok ``` **Resolution:** No support for `__arglist` in collection arguments unless free. [LDM-2025-03-05](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-14.md#conclusion-3) ### Arguments for *interface types* Should arguments be supported for interface target types? ```csharp ICollection c = [with(capacity: 4)]; IReadOnlyDictionary d = [with(comparer: StringComparer.Ordinal), ..values]; ```
If so, which method signatures are used when binding the arguments? For **mutable** interface types, the options are: 1. Use the accessible constructors from the well-known type required for instantation: `List` or `Dictionary`. 1. Use signatures independent of specific type, for instance using `new()` and `new(int capacity)` for `ICollection` and `IList` (see [*Construction*](#construction) for potential signatures for each interface). Using the accessible constructors from a well-known type has the following implications: - Parameter names, optional-ness, `params`, are taken from the parameters directly. - All accessible constructors are included, even though that may not be useful for collection expressions, such as `List(IEnumerable)` which would allow `IList list = [with(1, 2, 3)];`. - The set of constructors may depend on the BCL version. Recomendation: Use the accessible constructors from the well-known types. We have guaranteed we would use these types, so this just 'falls out' and is the clearest and simplest path to constructing these values. For **non-mutable** interface types, the options are similar: 1. Do nothing. This 1. Use signatures independent of specific type, although the only scenario may be `new(IEqualityComparer comparer)` for `IReadOnlyDictionary` for C#14.. Using accessible constructors from some well known type (the strategy for mutable-interface-types) is not viable as there is no relation to any particular existing type, and the final type we may use and/or synthesize. As such, there would have to be odd new requirements that the compiler be able to map any existing constructor of said type (even as it evolves) over to the non-mutable instance it actually generates. Recomendation: Use signatures independent of a specific type. And, for C# 14, only support `new(IEqualityComparer comparer)` for `IReadOnlyDictionary` as that is the only non-mutable interface where we feel it is critical for usability/semantics to allow users to provide this. Future C# releases can consider expanding on this set based on solid justifications provided.
**Resolution:** https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md Arguments are supported for interface target types. For both mutable and non-mutable interfaces the set of arguments will be curated. The expected list (which still needs to be LDM ratified) is [Interface target type](#interface-target-type) ### Empty argument lists Should we allow empty argument lists for some or all target types? An empty `with()` would be equivalent to no `with()`. It might provide some consistency with non-empty cases, but it wouldn’t add any new capability.
The meaning of an empty `with()` might be clearer for some target types than others: - For types where **constructors** are used, call the applicable constructor with no arguments. - For types with **`CollectionBuilderAttribute`**, call the applicable factory method with elements only. - For **interface types**, construct the well-known or implementation-defined type with no arguments. - However, for **arrays** and **spans**, where collection arguments are not otherwise supported, `with()` may be confusing. ```csharp List l = [with()]; // ok? new List() ImmutableArray m = [with()]; // ok? ImmutableArray.Create() IList i = [with()]; // ok? new List() or equivalent IEnumerable e = [with()]; // ok? int[] a = [with()]; // ok? Span s = [with()]; // ok? ```
**Resolution:** https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-12.md#empty-argument-lists > We will allow with() for constructor types and builder types that can be called without arguments at all, and we will add empty constructor signatures for the interface (mutable and readonly) types. Arrays and spans will not allow with(), as there are no signatures that would fit them. ## Open questions ### Finalizing an open concern from https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion `with(...)` is a breaking change in the language with `[with(...)]`. Before this feature, it means a collection expression with one element, which is the result of calling the `with`-invocation-expression. After this feature, it is a collection, which has arguments passed to it. Do we want this break to occur only when a user picks a specific language version (like `C#-14/15`?). In other words, if they are on an older langversion, they get the prior parsing logic, but on the newer version they get the newer parsing logic. In Or do we *always* want it to have the newer parsing logic, even on an older langversion? We have prior art for both strategies. `required`, for example, is always parsed with teh new logic, regardless of langversion. Whereas, `record/field` and others chang their parsing logic depending on language version. Finally, this has overlap and impact with `Dictionary Expressions`, which introduces the `key:value` syntax for KVP-elements. We want to establish the behavior we want for any lang version, and for `[with(...)]` on its own, and things like `[with(...) : expr]` or `[expr : with(...)]`. ================================================ FILE: proposals/compound-assignment-in-initializer-and-with.md ================================================ # Compound assignment in object initializer and `with` expression Champion issue: ## Summary Allow compound assignments in an object initializer: ```cs var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1d), Tick += (_, _) => { /*actual work*/ }, }; ``` Or a `with` expression: ```cs var newCounter = counter with { Value -= 1 }; ``` ## Motivation It's not uncommon, especially in UI frameworks, to create objects that both have values assigned and need events hooked up as part of initialization. While object initializers addressed the first part with a nice shorthand syntax, the latter still requires additional statements to be made. This makes it impossible to simply create these sorts of objects as a simple declaration expression, preventing their use in expression-bodied members or in nested constructs like collection initializers or switch expressions. Spilling the object creation expression out to a variable declaration statement makes things more verbose for such a simple concept. The declarative UI story can be made much more complete with a small change to the language. Windows Forms in particular can immediately gain a more appetizing story for dynamic or manual creation of UI controls, both in vanilla form and when using third-party vendor frameworks that build on Windows Forms. The applies to more than just events though as objects created (esp. based off another object with `with`) may want their initialized values to be relative to a prior or default state. ## Detailed design ### Object initializer design The existing will be updated to state: ```diff member_initializer - : initializer_target '=' initializer_value + : initializer_target assignment_operator initializer_value ; ``` The spec language will be changed to: > If an initializer_target is followed by an equals (`'='`) sign, it can be followed by either an expression, an object initializer or a collection initializer. If it is followed by any other assignment operator it can only be followed by an expression. > > If an initializer_target is followed by an equals (`'='`) sign it not possible for expressions within the object initializer to refer to the newly created object it is initializing. If it is followed by any other assignment operator, the new value will be created by reading the value from the new created object and then writing back into it. > > A member initializer that specifies an expression after the assignment_operator is processed in the same way as an [assignment](https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#assignment-operators) to the target. ### `with` expression design The existing [with expression](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/records.md#with-expression) spec will be updated to state: ```diff member_initializer - : identifier '=' expression + : identifier assignment_operator expression ; ``` The spec language will be changed to: > First, receiver's "clone" method (specified above) is invoked and its result is converted to the receiver's type. Then, each member_initializer is processed the same way as a corresponding assignment operation [assignment](https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#assignment-operators) to a field or property access of the result of the conversion. Assignments are processed in lexical order. ### Design notes There is no concern that `new X() { a += b }` has meaning today (for example, as a collection initializer). That's because the spec mandates that a collection initializer's element_initializer is: ```antlr element_initializer : non_assignment_expression | '{' expression_list '}' ; ``` By requiring that all collection elements are `non_assignment_expression`, `a += b` is already disallowed as that is an assignment_expression. ## Open questions Is this feature needed? For example, users could support some of these scenarios doing something like the following: ```cs var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1d), }.Init(t => t.Tick += (_, _) => { /*actual work*/ }), ``` That said, this would only work for non-init members, which seems unfortunate, and it is still clunky compared to the first-class experience of initializing all other members. ================================================ FILE: proposals/conditional-operator-access-syntax-refinement.md ================================================ # Conditional operator/access syntax refinement ## Summary Introduce whitespace-sensitive parsing rules to eliminate ambiguity between the conditional operator (`?:`) and null-conditional access operators (`?.` and `?[`). This change requires that tokens in null-conditional operators must be adjacent with no intervening characters, while conditional operators with certain operand patterns must have whitespace or comments between the `?` and the subsequent token. ## Motivation The C# language currently has potential ambiguities when parsing the `?` token followed by `[` or `.`, which can be difficult to interpret both visually and syntactically. ### Existing ambiguities Consider the sequence `a?[b`. This could be interpreted as: - A null-conditional indexing operation: `a?[b]` (index into `a` with `b` if `a` is not null) - The start of a conditional expression: `a ? [b] : expr` (if `a` is true, evaluate to collection expression `[b]`, otherwise `expr`) While the parser can disambiguate by looking ahead for a `:` token, this creates visual ambiguity for readers. A true syntactic ambiguity exists with: `A ? [ B ] ? [ C ] : D` This could be parsed as: - `(A?[B]) ? [C] : D` - conditional expression producing a collection, then null-conditional indexing - `A ? ([B]?[C]) : D` - conditional expression with nested conditional in the true branch The language currently interprets this as the former, which is reasonable since the latter would involve creating a collection expression solely to perform null-conditional indexing on it—a pattern with no practical use. ### Additional motivation from target-typed static member access This issue also arises in the context of [target-typed static member access](https://github.com/dotnet/csharplang/blob/main/proposals/target-typed-static-member-access.md). The sequence `a?.b` could be: - A null-conditional member access: `a?.b` - The start of a conditional expression with static member access: `a ? .b : expr` By establishing whitespace-sensitivity rules, we eliminate these ambiguities entirely. ### Precedent The C# language already employs whitespace-sensitive parsing for the `>>` token sequence. As stated in the specification: > `right_shift` is made up of the two tokens `>` and `>`. Similarly, `right_shift_assignment` is made up of the two tokens `>` and `>=`. Unlike other productions in the syntactic grammar, no characters of any kind (not even whitespace) are allowed between the two tokens in each of these productions. These productions are treated specially in order to enable the correct handling of type_parameter_lists. This proposal extends the same concept to null-conditional operators. ## Detailed design ### Parsing rules The following multi-character operators are modified to require that their constituent tokens be adjacent with no intervening characters: - **Null-conditional member access (`?.`)**: The `?` and `.` tokens must be adjacent - **Null-conditional indexing (`?[`)**: The `?` and `[` tokens must be adjacent Similar to the `>>` production, no characters of any kind (not even whitespace, comments, or preprocessor directives) are allowed between these tokens. Conversely, when the `?` token is followed by `[` or `.` in a conditional expression context, there **must** be at least one intervening character (whitespace, comment, or preprocessor directive) between the `?` and the subsequent token. ### Grammar changes The existing grammar productions for null-conditional operators are unchanged: ```g4 null_conditional_member_access : primary_expression '?' '.' identifier type_argument_list? (null_forgiving_operator? dependent_access)* ; null_conditional_element_access : primary_expression '?' '[' argument_list ']' (null_forgiving_operator? dependent_access)* ; ``` But explicit rules are added stating: > `?` `.` and `?` `[` are made up of the two tokens `?` followed by either `.` or `[`. Unlike other productions in the syntactic grammar, no characters of any kind (not even whitespace) are allowed between the two tokens in each of these productions. The [Conditional Operator](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1115-conditional-operator) section is updated to include the following rule: > A conditional expression of the form `b ? x : y` is not allowed to have `?` and `x` touch if `x` starts with a `.` or `[` token. If `x` starts with either of those tokens, the expression will instead be interpreted as a `null_conditional_member_access` or `null_conditional_element_access` respectively. ### Examples **Valid null-conditional access (no space):** ```csharp var x = obj?[index]; // Null-conditional indexing var y = obj?.Member; // Null-conditional member access ``` **Valid conditional expressions (with space/comment):** ```csharp var x = condition ? [1, 2, 3] : [4, 5, 6]; // Conditional with collection expressions var y = flag ? .StaticMember : expr; // Conditional with static member access (possible future feature) var z = test ?/*comment*/[x] : [y]; // Comment between tokens ``` **Invalid (violates adjacency requirement):** ```csharp var x = obj? [index]; // Error: space between ? and [ var y = obj? .Member; // Error: space between ? and . ``` ## Drawbacks ### Breaking change This is a breaking change. Code that currently uses whitespace between `?` and `[` for null-conditional indexing will no longer compile. Similarly, code like `a?[b]:[c]` would break as well. Real-world examples from existing codebases include: - `var aux_data = ctx? ["auxData"] as JObject;` - `var baseScheme = SchemeManager.GetHardCodedSchemes ()? ["Base"];` These patterns would need to be updated to remove the whitespace: `ctx?["auxData"]` and `SchemeManager.GetHardCodedSchemes()?["Base"]`. Similarly, if `a?[b]:` was encountered, it would need to be updated to have explicit spaces to preserve parsing behavior. Note: Users using standard .Net tooling (like Visual Studio and `dotnet format`) would not be likely to run into this issue as the standard formatting engine already formats code such that it follows the syntax pattern codified here. ### Migration burden Developers will need to update their code when upgrading to the language version that includes this feature. The compiler will be augmented to parse in the following fashion. Note: the augmentations only affect what errors are reported in code that cannot be legally parsed. Specifically, to help users potentially affected by this breaking change to update their code accordingly. Given: ``` conditional_expression : null_coalescing_expression | null_coalescing_expression '?' expression ':' expression ; ``` - If a `?` is seen after an `null_coalescing_expression`. Then - If the following token is not a `[` then normal `conditional_expression` parsing occurs. - If the following token is a `[` then the first `expression` part of the `conditional_expression` is parsed out. - If what follows is not a `:` and `[` was next to the `?` (e.g. `a?[b]`), then a `null_conditional_element_access` is parsed instead. - If what follows is not a `:` and `[` was not next to the `?` (e.g. `a ? [b]`), then this is an error. The parser should attempt to reparse this as a `null_conditional_element_access` expression. If that succeeds, it should report that the `?` and `[` must be directly next to each other. Otherwise, it should report a normal error that `:` was missing. - Otherwise, what followed was a `:` - If `?` was not touching the `[`, this is a `conditional_expression` (e.g. `a ? [b] :`), which should then consume the `:` and finish parsing the second `expression` portion of the `conditional_expression`. - Otherwise, this is `a?[b]:` The parser should reparse after the `?` as a `null_conditional_element_access`. And return that expression upwards. The `:` can then be consumed by a higher parse operation (like a higher `conditional_expression` parse). - If no such higher parse operation exists to consume the `:`, the parser should see if it had a `null_conditional_element_access` followed by the colon (e.g. `a?[b] : c`). It should then report that a space should be placed after the `?`. Similar rules would be present for `?` followed by `.`, with `[` replaced with `.` in the above and `null_conditional_element_access` replaced with `null_conditional_member_access`. This approach provides clear guidance for migration, also ensuring that the breaking change is manageable. ## Design meetings Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. ## Unresolved questions ## Language version This feature will be enabled only for the C# language version in which it ships, allowing existing code to continue compiling under previous language versions. ================================================ FILE: proposals/csharp-10.0/GlobalUsingDirective.md ================================================ # Global Using Directive [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: Syntax for a using directive is extended with an optional `global` keyword that can precede the `using` keyword: ```antlr compilation_unit : extern_alias_directive* global_using_directive* using_directive* global_attributes? namespace_member_declaration* ; global_using_directive : global_using_alias_directive | global_using_namespace_directive | global_using_static_directive ; global_using_alias_directive : 'global' 'using' identifier '=' namespace_or_type_name ';' ; global_using_namespace_directive : 'global' 'using' namespace_name ';' ; global_using_static_directive : 'global' 'using' 'static' type_name ';' ; ``` - The *global_using_directive*s are allowed only on the Compilation Unit level (cannot be used inside a *namespace_declaration*). - The *global_using_directive*s, if any, must precede any *using_directive*s. - The scope of a *global_using_directive*s extends over the *namespace_member_declaration*s of all compilation units within the program. The scope of a *global_using_directive* specifically does not include other *global_using_directive*s. Thus, peer *global_using_directive*s or those from a different compilation unit do not affect each other, and the order in which they are written is insignificant. The scope of a *global_using_directive* specifically does not include *using_directive*s immediately contained in any compilation unit of the program. The effect of adding a *global_using_directive* to a program can be thought of as the effect of adding a similar *using_directive* that resolves to the same target namespace or type to every compilation unit of the program. However, the target of a *global_using_directive* is resolved in context of the compilation unit that contains it. ## [§7.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#77-scopes) Scopes These are the relevant bullet points with proposed additions (which are **in bold**): * The scope of name defined by an *extern_alias_directive* extends over the ***global_using_directive*s,** *using_directive*s, *global_attributes* and *namespace_member_declaration*s of its immediately containing compilation unit or namespace body. An *extern_alias_directive* does not contribute any new members to the underlying declaration space. In other words, an *extern_alias_directive* is not transitive, but, rather, affects only the compilation unit or namespace body in which it occurs. * **The scope of a name defined or imported by a *global_using_directive* extends over the *global_attributes* and *namespace_member_declaration*s of all the *compilation_unit*s in the program.** ## [§7.8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#78-namespace-and-type-names) Namespace and type names Changes are made to the algorithm determining the meaning of a *namespace_or_type_name* as follows. This is the relevant bullet point with proposed additions (which are **in bold**): * If the *namespace_or_type_name* is of the form `I` or of the form `I`: * If `K` is zero and the *namespace_or_type_name* appears within a generic method declaration ([§15.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#156-methods)) and if that declaration includes a type parameter ([§15.2.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1523-type-parameters)) with name `I`, then the *namespace_or_type_name* refers to that type parameter. * Otherwise, if the *namespace_or_type_name* appears within a type declaration, then for each instance type `T` ([§15.3.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1532-the-instance-type)), starting with the instance type of that type declaration and continuing with the instance type of each enclosing class or struct declaration (if any): * If `K` is zero and the declaration of `T` includes a type parameter with name `I`, then the *namespace_or_type_name* refers to that type parameter. * Otherwise, if the *namespace_or_type_name* appears within the body of the type declaration, and `T` or any of its base types contain a nested accessible type having name `I` and `K` type parameters, then the *namespace_or_type_name* refers to that type constructed with the given type arguments. If there is more than one such type, the type declared within the more derived type is selected. Note that non-type members (constants, fields, methods, properties, indexers, operators, instance constructors, destructors, and static constructors) and type members with a different number of type parameters are ignored when determining the meaning of the *namespace_or_type_name*. * If the previous steps were unsuccessful then, for each namespace `N`, starting with the namespace in which the *namespace_or_type_name* occurs, continuing with each enclosing namespace (if any), and ending with the global namespace, the following steps are evaluated until an entity is located: * If `K` is zero and `I` is the name of a namespace in `N`, then: * If the location where the *namespace_or_type_name* occurs is enclosed by a namespace declaration for `N` and the namespace declaration contains an *extern_alias_directive* or *using_alias_directive* that associates the name `I` with a namespace or type, **or any namespace declaration for `N` in the program contains a *global_using_alias_directive* that associates the name `I` with a namespace or type,** then the *namespace_or_type_name* is ambiguous and a compile-time error occurs. * Otherwise, the *namespace_or_type_name* refers to the namespace named `I` in `N`. * Otherwise, if `N` contains an accessible type having name `I` and `K` type parameters, then: * If `K` is zero and the location where the *namespace_or_type_name* occurs is enclosed by a namespace declaration for `N` and the namespace declaration contains an *extern_alias_directive* or *using_alias_directive* that associates the name `I` with a namespace or type, **or any namespace declaration for `N` in the program contains a *global_using_alias_directive* that associates the name `I` with a namespace or type,** then the *namespace_or_type_name* is ambiguous and a compile-time error occurs. * Otherwise, the *namespace_or_type_name* refers to the type constructed with the given type arguments. * Otherwise, if the location where the *namespace_or_type_name* occurs is enclosed by a namespace declaration for `N`: * If `K` is zero and the namespace declaration contains an *extern_alias_directive* or *using_alias_directive* that associates the name `I` with an imported namespace or type, **or any namespace declaration for `N` in the program contains a *global_using_alias_directive* that associates the name `I` with an imported namespace or type,** then the *namespace_or_type_name* refers to that namespace or type. * Otherwise, if the namespaces and type declarations imported by the *using_namespace_directive*s and *using_alias_directive*s of the namespace declaration **and the namespaces and type declarations imported by the *global_using_namespace_directive*s and *global_using_static_directive*s of any namespace declaration for `N` in the program** contain exactly one accessible type having name `I` and `K` type parameters, then the *namespace_or_type_name* refers to that type constructed with the given type arguments. * Otherwise, if the namespaces and type declarations imported by the *using_namespace_directive*s and *using_alias_directive*s of the namespace declaration **and the namespaces and type declarations imported by the *global_using_namespace_directive*s and *global_using_static_directive*s of any namespace declaration for `N` in the program** contain more than one accessible type having name `I` and `K` type parameters, then the *namespace_or_type_name* is ambiguous and an error occurs. * Otherwise, the *namespace_or_type_name* is undefined and a compile-time error occurs. ## Simple names [§12.8.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1284-simple-names) Changes are made to the *simple_name* evaluation rules as follows. This is the relevant bullet point with proposed additions (which are **in bold**): * Otherwise, for each namespace `N`, starting with the namespace in which the *simple_name* occurs, continuing with each enclosing namespace (if any), and ending with the global namespace, the following steps are evaluated until an entity is located: * If `K` is zero and `I` is the name of a namespace in `N`, then: * If the location where the *simple_name* occurs is enclosed by a namespace declaration for `N` and the namespace declaration contains an *extern_alias_directive* or *using_alias_directive* that associates the name `I` with a namespace or type, **or any namespace declaration for `N` in the program contains a *global_using_alias_directive* that associates the name `I` with a namespace or type,** then the *simple_name* is ambiguous and a compile-time error occurs. * Otherwise, the *simple_name* refers to the namespace named `I` in `N`. * Otherwise, if `N` contains an accessible type having name `I` and `K` type parameters, then: * If `K` is zero and the location where the *simple_name* occurs is enclosed by a namespace declaration for `N` and the namespace declaration contains an *extern_alias_directive* or *using_alias_directive* that associates the name `I` with a namespace or type, **or any namespace declaration for `N` in the program contains a *global_using_alias_directive* that associates the name `I` with a namespace or type,** then the *simple_name* is ambiguous and a compile-time error occurs. * Otherwise, the *namespace_or_type_name* refers to the type constructed with the given type arguments. * Otherwise, if the location where the *simple_name* occurs is enclosed by a namespace declaration for `N`: * If `K` is zero and the namespace declaration contains an *extern_alias_directive* or *using_alias_directive* that associates the name `I` with an imported namespace or type, **or any namespace declaration for `N` in the program contains a *global_using_alias_directive* that associates the name `I` with an imported namespace or type,** then the *simple_name* refers to that namespace or type. * Otherwise, if the namespaces and type declarations imported by the *using_namespace_directive*s and *using_static_directive*s of the namespace declaration **and the namespaces and type declarations imported by the *global_using_namespace_directive*s and *global_using_static_directive*s of any namespace declaration for `N` in the program** contain exactly one accessible type or non-extension static member having name `I` and `K` type parameters, then the *simple_name* refers to that type or member constructed with the given type arguments. * Otherwise, if the namespaces and types imported by the *using_namespace_directive*s of the namespace declaration **and the namespaces and type declarations imported by the *global_using_namespace_directive*s and *global_using_static_directive*s of any namespace declaration for `N` in the program** contain more than one accessible type or non-extension-method static member having name `I` and `K` type parameters, then the *simple_name* is ambiguous and an error occurs. ## Extension method invocations [§12.8.10.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128103-extension-method-invocations) Changes are made to the algorithm to find the best *type_name* `C` as follows. This is the relevant bullet point with proposed additions (which are **in bold**): * Starting with the closest enclosing namespace declaration, continuing with each enclosing namespace declaration, and ending with the containing compilation unit, successive attempts are made to find a candidate set of extension methods: * If the given namespace or compilation unit directly contains non-generic type declarations `Ci` with eligible extension methods `Mj`, then the set of those extension methods is the candidate set. * If types `Ci` imported by *using_static_declarations* and directly declared in namespaces imported by *using_namespace_directive*s in the given namespace or compilation unit **and, if containing compilation unit is reached, imported by *global_using_static_declarations* and directly declared in namespaces imported by *global_using_namespace_directive*s in the program** directly contain eligible extension methods `Mj`, then the set of those extension methods is the candidate set. ## Compilation units [§14.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/namespaces.md#142-compilation-units) A *compilation_unit* defines the overall structure of a source file. A compilation unit consists of **zero or more *global_using_directive*s followed by** zero or more *using_directive*s followed by zero or more *global_attributes* followed by zero or more *namespace_member_declaration*s. ```antlr compilation_unit : extern_alias_directive* global_using_directive* using_directive* global_attributes? namespace_member_declaration* ; ``` A C# program consists of one or more compilation units, each contained in a separate source file. When a C# program is compiled, all of the compilation units are processed together. Thus, compilation units can depend on each other, possibly in a circular fashion. The *global_using_directive*s of a compilation unit affect the *global_attributes* and *namespace_member_declaration*s of all compilation units in the program. ## Extern aliases [§14.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/namespaces.md#144-extern-alias-directives) The scope of an *extern_alias_directive* extends over the ***global_using_directive*s,** *using_directive*s, *global_attributes* and *namespace_member_declaration*s of its immediately containing compilation unit or namespace body. ## Using alias directives [§14.5.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/namespaces.md#1452-using-alias-directives) The order in which *using_alias_directive*s are written has no significance, and resolution of the *namespace_or_type_name* referenced by a *using_alias_directive* is not affected by the *using_alias_directive* itself or by other *using_directive*s in the immediately containing compilation unit or namespace body, **and, if the *using_alias_directive* is immediately contained in a compilation unit, is not affected by the *global_using_directive*s in the program**. In other words, the *namespace_or_type_name* of a *using_alias_directive* is resolved as if the immediately containing compilation unit or namespace body had no *using_directive*s **and, if the *using_alias_directive* is immediately contained in a compilation unit, the program had no *global_using_directive*s**. A *using_alias_directive* may however be affected by *extern_alias_directive*s in the immediately containing compilation unit or namespace body. ## Global Using alias directives A *global_using_alias_directive* introduces an identifier that serves as an alias for a namespace or type within the program. ```antlr global_using_alias_directive : 'global' 'using' identifier '=' namespace_or_type_name ';' ; ``` Within member declarations in any compilation unit of a program that contains a *global_using_alias_directive*, the identifier introduced by the *global_using_alias_directive* can be used to reference the given namespace or type. The *identifier* of a *global_using_alias_directive* must be unique within the declaration space of any compilation unit of a program that contains the *global_using_alias_directive*. Just like regular members, names introduced by *global_using_alias_directive*s are hidden by similarly named members in nested scopes. The order in which *global_using_alias_directive*s are written has no significance, and resolution of the *namespace_or_type_name* referenced by a *global_using_alias_directive* is not affected by the *global_using_alias_directive* itself or by other *global_using_directive*s or *using_directive*s in the program. In other words, the *namespace_or_type_name* of a *global_using_alias_directive* is resolved as if the immediately containing compilation unit had no *using_directive*s and the entire containing program had no *global_using_directive*s. A *global_using_alias_directive* may however be affected by *extern_alias_directive*s in the immediately containing compilation unit. A *global_using_alias_directive* can create an alias for any namespace or type. Accessing a namespace or type through an alias yields exactly the same result as accessing that namespace or type through its declared name. Using aliases can name a closed constructed type, but cannot name an unbound generic type declaration without supplying type arguments. ## Global Using namespace directives A *global_using_namespace_directive* imports the types contained in a namespace into the program, enabling the identifier of each type to be used without qualification. ```antlr global_using_namespace_directive : 'global' 'using' namespace_name ';' ; ``` Within member declarations in a program that contains a *global_using_namespace_directive*, the types contained in the given namespace can be referenced directly. A *global_using_namespace_directive* imports the types contained in the given namespace, but specifically does not import nested namespaces. Unlike a *global_using_alias_directive*, a *global_using_namespace_directive* may import types whose identifiers are already defined within a compilation unit of the program. In effect, in a given compilation unit, names imported by any *global_using_namespace_directive* in the program are hidden by similarly named members in the compilation unit. When more than one namespace or type imported by *global_using_namespace_directive*s or *global_using_static_directive*s in the same program contain types by the same name, references to that name as a *type_name* are considered ambiguous. Furthermore, when more than one namespace or type imported by *global_using_namespace_directive*s or *global_using_static_directive*s in the same program contain types or members by the same name, references to that name as a *simple_name* are considered ambiguous. The *namespace_name* referenced by a *global_using_namespace_directive* is resolved in the same way as the *namespace_or_type_name* referenced by a *global_using_alias_directive*. Thus, *global_using_namespace_directive*s in the same program do not affect each other and can be written in any order. ## Global Using static directives A *global_using_static_directive* imports the nested types and static members contained directly in a type declaration into the containing program, enabling the identifier of each member and type to be used without qualification. ```antlr global_using_static_directive : 'global' 'using' 'static' type_name ';' ; ``` Within member declarations in a program that contains a *global_using_static_directive*, the accessible nested types and static members (except extension methods) contained directly in the declaration of the given type can be referenced directly. A *global_using_static_directive* specifically does not import extension methods directly as static methods, but makes them available for extension method invocation. A *global_using_static_directive* only imports members and types declared directly in the given type, not members and types declared in base classes. Ambiguities between multiple *global_using_namespace_directive*s and *global_using_static_directives* are discussed in the section for *global_using_namespace_directive*s (above). ## Qualified alias member [§14.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/namespaces.md#138-qualified-alias-member) Changes are made to the algorithm determining the meaning of a *qualified_alias_member* as follows. This is the relevant bullet point with proposed additions (which are **in bold**): * Otherwise, starting with the namespace declaration ([§14.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/namespaces.md#143-namespace-declarations)) immediately containing the *qualified_alias_member* (if any), continuing with each enclosing namespace declaration (if any), and ending with the compilation unit containing the *qualified_alias_member*, the following steps are evaluated until an entity is located: * If the namespace declaration or compilation unit contains a *using_alias_directive* that associates `N` with a type, **or, when a compilation unit is reached, the program contains a *global_using_alias_directive* that associates `N` with a type,** then the *qualified_alias_member* is undefined and a compile-time error occurs. * Otherwise, if the namespace declaration or compilation unit contains an *extern_alias_directive* or *using_alias_directive* that associates `N` with a namespace, ***or, when a compilation unit is reached, the program contains a *global_using_alias_directive* that associates `N` with a namespace,** then: * If the namespace associated with `N` contains a namespace named `I` and `K` is zero, then the *qualified_alias_member* refers to that namespace. * Otherwise, if the namespace associated with `N` contains a non-generic type named `I` and `K` is zero, then the *qualified_alias_member* refers to that type. * Otherwise, if the namespace associated with `N` contains a type named `I` that has `K` type parameters, then the *qualified_alias_member* refers to that type constructed with the given type arguments. * Otherwise, the *qualified_alias_member* is undefined and a compile-time error occurs. ================================================ FILE: proposals/csharp-10.0/async-method-builders.md ================================================ # AsyncMethodBuilder override [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Allow per-method override of the async method builder to use. For some async methods we want to customize the invocation of `Builder.Create()` to use a different _builder type_. ```C# [AsyncMethodBuilderAttribute(typeof(PoolingAsyncValueTaskMethodBuilder<>))] // new usage of AsyncMethodBuilderAttribute type static async ValueTask ExampleAsync() { ... } ``` ## Motivation [motivation]: #motivation Today, async method builders are tied to a given type used as a return type of an async method. For example, any method that's declared as `async Task` uses `AsyncTaskMethodBuilder`, and any method that's declared as `async ValueTask` uses `AsyncValueTaskMethodBuilder`. This is due to the `[AsyncMethodBuilder(Type)]` attribute on the type used as a return type, e.g. `ValueTask` is attributed as `[AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder<>))]`. This addresses the majority common case, but it leaves a few notable holes for advanced scenarios. In .NET 5, an experimental feature was shipped that provides two modes in which `AsyncValueTaskMethodBuilder` and `AsyncValueTaskMethodBuilder` operate. The on-by-default mode is the same as has been there since the functionality was introduced: when the state machine needs to be lifted to the heap, an object is allocated to store the state, and the async method returns a `ValueTask{}` backed by a `Task{}`. However, if an environment variable is set, all builders in the process switch to a mode where, instead, the `ValueTask{}` instances are backed by reusable `IValueTaskSource{}` implementations that are pooled. Each async method has its own pool with a fixed maximum number of instances allowed to be pooled, and as long as no more than that number are ever returned to the pool to be pooled at the same time, `async ValueTask<{T}>` methods effectively become free of any GC allocation overhead. There are several problems with this experimental mode, however, which is both why a) it's off by default and b) we're likely to remove it in a future release unless very compelling new information emerges (https://github.com/dotnet/runtime/issues/13633). - It introduces a behavioral difference for consumers of the returned `ValueTask{}` if that `ValueTask` isn't being consumed according to spec. When it's backed by a `Task`, you can do with the `ValueTask` things you can do with a `Task`, like await it multiple times, await it concurrently, block waiting for it to complete, etc. But when it's backed by an arbitrary `IValueTaskSource`, such operations are prohibited, and automatically switching from the former to the latter can lead to bugs. With the switch at the process level and affecting all `async ValueTask` methods in the process, whether you control them or not, it's too big a hammer. - It's not necessarily a performance win, and could represent a regression in some situations. The implementation is trading the cost of pooling (accessing a pool isn't free) with the cost of GC, and in various situations the GC can win. Again, applying the pooling to all `async ValueTask` methods in the process rather than being selective about the ones it would most benefit is too big a hammer. - It adds to the IL size of a trimmed application, even if the flag isn't set, and then to the resulting asm size. It's possible that can be worked around with improvements to the implementation to teach it that for a given deployment the environment variable will always be false, but as it stands today, every `async ValueTask` method saw for example an ~2K binary footprint increase in aot images due to this option, and, again, that applies to all `async ValueTask` methods in the whole application closure. - Different methods may benefit from differing levels of control, e.g. the size of the pool employed because of knowledge of the method and how it's used, but the same setting is applied to all uses of the builder. One could imagine working around that by having the builder code use reflection at runtime to look for some attribute, but that adds significant run-time expense, and likely on the startup path. On top of all of these issues with the existing pooling, it's also the case that developers are prevented from writing their own customized builders for types they don't own. If, for example, a developer wants to implement their own pooling support, they also have to introduce a brand new task-like type, rather than just being able to use `{Value}Task{}`, because the attribute specifying the builder is only specifiable on the type declaration of the return type. We need a way to have an individual async method opt-in to a specific builder. ## Detailed design [design]: #detailed-design ### Using AsyncMethodBuilderAttribute on methods In `dotnet/runtime`, add `AttributeTargets.Method` to the targets for `System.Runtime.CompilerServices.AsyncMethodBuilderAttribute`: ```csharp namespace System.Runtime.CompilerServices { /// /// Indicates the type of the async method builder that should be used by a language compiler: /// - to build the return type of an async method that is attributed, /// - to build the attributed type when used as the return type of an async method. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Delegate | AttributeTargets.Enum, Inherited = false, AllowMultiple = false)] public sealed class AsyncMethodBuilderAttribute : Attribute { /// Initializes the . /// The of the associated builder. public AsyncMethodBuilderAttribute(Type builderType) => BuilderType = builderType; /// Gets the of the associated builder. public Type BuilderType { get; } } } ``` This allows the attribute to be applied on methods or local functions or lambdas. Example of usage on a method: ```C# [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] // new usage, referring to some custom builder type static async ValueTask ExampleAsync() { ... } ``` It is an error to apply the attribute multiple times on a given method. It is an error to apply the attribute to a lambda with an implicit return type. A developer who wants to use a specific custom builder for all of their methods can do so by putting the relevant attribute on each method. ### Determining the builder type for an async method When compiling an async method, the builder type is determined by: 1. using the builder type from the `AsyncMethodBuilder` attribute if one is present, 2. otherwise, falling back to the builder type determined by previous approach. (see [spec for task-like types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/task-types.md)). If an `AsyncMethodBuilder` attribute is present, we take the builder type specified by the attribute and construct it if necessary. If the override type is an open generic type, take the single type argument of the async method's return type and substitute it into the override type. If the override type is a bound generic type, then we produce an error. If the async method's return type does not have a single type argument, then we produce an error. We verify that the builder type is compatible with the return type of the async method: 1. look for the public `Create` method with no type parameters and no parameters on the constructed builder type. It is an error if the method is not found. It is an error if the method returns a type other than the constructed builder type. 2. look for the public `Task` property. It is an error if the property is not found. 3. consider the type of that `Task` property (a task-like type): It is an error if the task-like type does not matches the return type of the async method. Note that it is not necessary for the return type of the method to be a task-like type. ### Execution The builder type determined above is used as part of the existing async method design. For example, today if a method is defined as: ```C# public async ValueTask ExampleAsync() { ... } ``` the compiler will generate code akin to: ```C# [AsyncStateMachine(typeof(d__29))] [CompilerGenerated] static ValueTask ExampleAsync() { d__29 stateMachine; stateMachine.<>t__builder = AsyncValueTaskMethodBuilder.Create(); stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; } ``` With this change, if the developer wrote: ```C# [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] // new usage, referring to some custom builder type static async ValueTask ExampleAsync() { ... } ``` it would instead be compiled to: ```C# [AsyncStateMachine(typeof(d__29))] [CompilerGenerated] [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] // retained but not necessary anymore static ValueTask ExampleAsync() { d__29 stateMachine; stateMachine.<>t__builder = PoolingAsyncValueTaskMethodBuilder.Create(); // <>t__builder now a different type stateMachine.<>1__state = -1; stateMachine.<>t__builder.Start(ref stateMachine); return stateMachine.<>t__builder.Task; } ``` Just those small additions enable: - Anyone to write their own builder that can be applied to async methods that return `Task` and `ValueTask` - As "anyone", the runtime to ship the experimental builder support as new public builder types that can be opted into on a method-by-method basis; the existing support would be removed from the existing builders. Methods (including some we care about in the core libraries) can then be attributed on a case-by-case basis to use the pooling support, without impacting any other unattributed methods. and with minimal surface area changes or feature work in the compiler. Note that we need the emitted code to allow a different type being returned from `Create` method: ``` AsyncPooledBuilder _builder = AsyncPooledBuilderWithSize4.Create(); ``` Note that this mechanism to change the the builder type cannot be used when the synthesized entry-point for top-level statements is async. An explicit entry-point should be used instead. ## Drawbacks [drawbacks]: #drawbacks * The syntax for applying such an attribute to a method is verbose. The impact of this is lessened if a developer can apply it to multiple methods en mass, e.g. at the type or module level. ## Alternatives [alternatives]: #alternatives - Implement a different task-like type and expose that difference to consumers. `ValueTask` was made extensible via the `IValueTaskSource` interface to avoid that need, however. - Address just the ValueTask pooling part of the issue by enabling the experiment as the on-by-default-and-only implementation. That doesn't address other aspects, such as configuring the pooling, or enabling someone else to provide their own builder. - Earlier versions of this document allowed for scoped override of builder types. ## Unresolved questions [unresolved]: #unresolved-questions 1. **Replace or also create.** All of the examples in this proposal are about replacing a buildable task-like's builder. Should the feature be scoped to just that? Or should you be able to use this attribute on a method with a return type that doesn't already have a builder (e.g. some common interface)? That could impact overload resolution. 2. **Private Builders**. Should the compiler support non-public async method builders? This is not spec'd today, but experimentally we only support public ones. That makes some sense when the attribute is applied to a type to control what builder is used with that type, since anyone writing an async method with that type as the return type would need access to the builder. However, with this new feature, when that attribute is applied to a method, it only impacts the implementation of that method, and thus could reasonably reference a non-public builder. Likely we will want to support library authors who have non-public ones they want to use. ================================================ FILE: proposals/csharp-10.0/caller-argument-expression.md ================================================ # CallerArgumentExpression [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Allow developers to capture the expressions passed to a method, to enable better error messages in diagnostic/testing APIs and reduce keystrokes. ## Motivation [motivation]: #motivation When an assertion or argument validation fails, the developer wants to know as much as possible about where and why it failed. However, today's diagnostic APIs do not fully facilitate this. Consider the following method: ```csharp T Single(this T[] array) { Debug.Assert(array != null); Debug.Assert(array.Length == 1); return array[0]; } ``` When one of the asserts fail, only the filename, line number, and method name will be provided in the stack trace. The developer will not be able to tell which assert failed from this information-- they will have to open the file and navigate to the provided line number to see what went wrong. This is also the reason testing frameworks have to provide a variety of assert methods. With xUnit, `Assert.True` and `Assert.False` are not frequently used because they do not provide enough context about what failed. While the situation is a bit better for argument validation because the names of invalid arguments are shown to the developer, the developer must pass these names to exceptions manually. If the above example were rewritten to use traditional argument validation instead of `Debug.Assert`, it would look like ```csharp T Single(this T[] array) { if (array == null) { throw new ArgumentNullException(nameof(array)); } if (array.Length != 1) { throw new ArgumentException("Array must contain a single element.", nameof(array)); } return array[0]; } ``` Notice that `nameof(array)` must be passed to each exception, although it's already clear from context which argument is invalid. ## Detailed design [design]: #detailed-design In the above examples, including the string `"array != null"` or `"array.Length == 1"` in the assert message would help the developer determine what failed. Enter `CallerArgumentExpression`: it's an attribute the framework can use to obtain the string associated with a particular method argument. We would add it to `Debug.Assert` like so ```csharp public static class Debug { public static void Assert(bool condition, [CallerArgumentExpression("condition")] string message = null); } ``` The source code in the above example would stay the same. However, the code the compiler actually emits would correspond to ```csharp T Single(this T[] array) { Debug.Assert(array != null, "array != null"); Debug.Assert(array.Length == 1, "array.Length == 1"); return array[0]; } ``` The compiler specially recognizes the attribute on `Debug.Assert`. It passes the string associated with the argument referred to in the attribute's constructor (in this case, `condition`) at the call site. When either assert fails, the developer will be shown the condition that was false and will know which one failed. For argument validation, the attribute cannot be used directly, but can be made use of through a helper class: ```csharp public static class Verify { public static void Argument(bool condition, string message, [CallerArgumentExpression("condition")] string conditionExpression = null) { if (!condition) throw new ArgumentException(message: message, paramName: conditionExpression); } public static void InRange(int argument, int low, int high, [CallerArgumentExpression("argument")] string argumentExpression = null, [CallerArgumentExpression("low")] string lowExpression = null, [CallerArgumentExpression("high")] string highExpression = null) { if (argument < low) { throw new ArgumentOutOfRangeException(paramName: argumentExpression, message: $"{argumentExpression} ({argument}) cannot be less than {lowExpression} ({low})."); } if (argument > high) { throw new ArgumentOutOfRangeException(paramName: argumentExpression, message: $"{argumentExpression} ({argument}) cannot be greater than {highExpression} ({high})."); } } public static void NotNull(T argument, [CallerArgumentExpression("argument")] string argumentExpression = null) where T : class { if (argument == null) throw new ArgumentNullException(paramName: argumentExpression); } } static T Single(this T[] array) { Verify.NotNull(array); // paramName: "array" Verify.Argument(array.Length == 1, "Array must contain a single element."); // paramName: "array.Length == 1" return array[0]; } static T ElementAt(this T[] array, int index) { Verify.NotNull(array); // paramName: "array" // paramName: "index" // message: "index (-1) cannot be less than 0 (0).", or // "index (6) cannot be greater than array.Length - 1 (5)." Verify.InRange(index, 0, array.Length - 1); return array[index]; } ``` A proposal to add such a helper class to the framework is underway at https://github.com/dotnet/corefx/issues/17068. If this language feature was implemented, the proposal could be updated to take advantage of this feature. ### Extension methods The `this` parameter in an extension method may be referenced by `CallerArgumentExpression`. For example: ```csharp public static void ShouldBe(this T @this, T expected, [CallerArgumentExpression("this")] string thisExpression = null) {} contestant.Points.ShouldBe(1337); // thisExpression: "contestant.Points" ``` `thisExpression` will receive the expression corresponding to the object before the dot. If it's called with static method syntax, e.g. `Ext.ShouldBe(contestant.Points, 1337)`, it will behave as if first parameter wasn't marked `this`. There should always be an expression corresponding to the `this` parameter. Even if an instance of a class calls an extension method on itself, e.g. `this.Single()` from inside a collection type, the `this` is mandated by the compiler so `"this"` will get passed. If this rule is changed in the future, we can consider passing `null` or the empty string. ### Extra details - Like the other `Caller*` attributes, such as `CallerMemberName`, this attribute may only be used on parameters with default values. - Multiple parameters marked with `CallerArgumentExpression` are permitted, as shown above. - The attribute's namespace will be `System.Runtime.CompilerServices`. - If `null` or a string that is not a parameter name (e.g. `"notAParameterName"`) is provided, the compiler will pass in an empty string. - The type of the parameter `CallerArgumentExpressionAttribute` is applied to must have a standard conversion from `string`. This means no user-defined conversions from `string` are allowed, and in practice means the type of such a parameter must be `string`, `object`, or an interface implemented by `string`. ## Drawbacks [drawbacks]: #drawbacks - People who know how to use decompilers will be able to see some of the source code at call sites for methods marked with this attribute. This may be undesirable/unexpected for closed-source software. - Although this is not a flaw in the feature itself, a source of concern may be that there exists a `Debug.Assert` API today that only takes a `bool`. Even if the overload taking a message had its second parameter marked with this attribute and made optional, the compiler would still pick the no-message one in overload resolution. Therefore, the no-message overload would have to be removed to take advantage of this feature, which would be a binary (although not source) breaking change. ## Alternatives [alternatives]: #alternatives - If being able to see source code at call sites for methods that use this attribute proves to be a problem, we can make the attribute's effects opt-in. Developers will enable it through an assembly-wide `[assembly: EnableCallerArgumentExpression]` attribute they put in `AssemblyInfo.cs`. - In the case the attribute's effects are not enabled, calling methods marked with the attribute would not be an error, to allow existing methods to use the attribute and maintain source compatibility. However, the attribute would be ignored and the method would be called with whatever default value was provided. ```csharp // Assembly1 void Foo(string bar); // V1 void Foo(string bar, string barExpression = "not provided"); // V2 void Foo(string bar, [CallerArgumentExpression("bar")] string barExpression = "not provided"); // V3 // Assembly2 Foo(a); // V1: Compiles to Foo(a), V2, V3: Compiles to Foo(a, "not provided") Foo(a, "provided"); // V2, V3: Compiles to Foo(a, "provided") // Assembly3 [assembly: EnableCallerArgumentExpression] Foo(a); // V1: Compiles to Foo(a), V2: Compiles to Foo(a, "not provided"), V3: Compiles to Foo(a, "a") Foo(a, "provided"); // V2, V3: Compiles to Foo(a, "provided") ``` - To prevent the [binary compatibility problem][drawbacks] from occurring every time we want to add new caller info to `Debug.Assert`, an alternative solution would be to add a `CallerInfo` struct to the framework that contains all the necessary information about the caller. ```csharp struct CallerInfo { public string MemberName { get; set; } public string TypeName { get; set; } public string Namespace { get; set; } public string FullTypeName { get; set; } public string FilePath { get; set; } public int LineNumber { get; set; } public int ColumnNumber { get; set; } public Type Type { get; set; } public MethodBase Method { get; set; } public string[] ArgumentExpressions { get; set; } } [Flags] enum CallerInfoOptions { MemberName = 1, TypeName = 2, ... } public static class Debug { public static void Assert(bool condition, // If a flag is not set here, the corresponding CallerInfo member is not populated by the caller, so it's // pay-for-play friendly. [CallerInfo(CallerInfoOptions.FilePath | CallerInfoOptions.Method | CallerInfoOptions.ArgumentExpressions)] CallerInfo callerInfo = default(CallerInfo)) { string filePath = callerInfo.FilePath; MethodBase method = callerInfo.Method; string conditionExpression = callerInfo.ArgumentExpressions[0]; //... } } class Bar { void Foo() { Debug.Assert(false); // Translates to: var callerInfo = new CallerInfo(); callerInfo.FilePath = @"C:\Bar.cs"; callerInfo.Method = MethodBase.GetCurrentMethod(); callerInfo.ArgumentExpressions = new string[] { "false" }; Debug.Assert(false, callerInfo); } } ``` This was originally proposed at https://github.com/dotnet/csharplang/issues/87. There are a few disadvantages of this approach: - Despite being pay-for-play friendly by allowing you to specify which properties you need, it could still hurt perf significantly by allocating an array for the expressions/calling `MethodBase.GetCurrentMethod` even when the assert passes. - Additionally, while passing a new flag to the `CallerInfo` attribute won't be a breaking change, `Debug.Assert` won't be guaranteed to actually receive that new parameter from call sites that compiled against an old version of the method. ## Unresolved questions [unresolved]: #unresolved-questions TBD ## Design meetings N/A ================================================ FILE: proposals/csharp-10.0/constant_interpolated_strings.md ================================================ # Constant Interpolated Strings [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Enables constants to be generated from interpolated strings of type string constant. ## Motivation [motivation]: #motivation The following code is already legal: ``` public class C { const string S1 = "Hello world"; const string S2 = "Hello" + " " + "World"; const string S3 = S1 + " Kevin, welcome to the team!"; } ``` However, there have been many community requests to make the following also legal: ``` public class C { const string S1 = $"Hello world"; const string S2 = $"Hello{" "}World"; const string S3 = $"{S1} Kevin, welcome to the team!"; } ``` This proposal represents the next logical step for constant string generation, where existing string syntax that works in other situations is made to work for constants. ## Detailed design [design]: #detailed-design The following represent the updated specifications for constant expressions under this new proposal. Current specifications from which this was directly based on can be found in [§12.23](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1223-constant-expressions). ### Constant Expressions A *constant_expression* is an expression that can be fully evaluated at compile-time. ```antlr constant_expression : expression ; ``` A constant expression must be the `null` literal or a value with one of the following types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool`, `object`, `string`, or any enumeration type. Only the following constructs are permitted in constant expressions: * Literals (including the `null` literal). * References to `const` members of class and struct types. * References to members of enumeration types. * References to `const` parameters or local variables * Parenthesized sub-expressions, which are themselves constant expressions. * Cast expressions, provided the target type is one of the types listed above. * `checked` and `unchecked` expressions * Default value expressions * Nameof expressions * The predefined `+`, `-`, `!`, and `~` unary operators. * The predefined `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `&`, `|`, `^`, `&&`, `||`, `==`, `!=`, `<`, `>`, `<=`, and `>=` binary operators, provided each operand is of a type listed above. * The `?:` conditional operator. * *Interpolated strings `${}`, provided that all components are constant expressions of type `string` and all interpolated components lack alignment and format specifiers.* The following conversions are permitted in constant expressions: * Identity conversions * Numeric conversions * Enumeration conversions * Constant expression conversions * Implicit and explicit reference conversions, provided that the source of the conversions is a constant expression that evaluates to the null value. Other conversions including boxing, unboxing and implicit reference conversions of non-null values are not permitted in constant expressions. For example: ```csharp class C { const object i = 5; // error: boxing conversion not permitted const object str = "hello"; // error: implicit reference conversion } ``` the initialization of i is an error because a boxing conversion is required. The initialization of str is an error because an implicit reference conversion from a non-null value is required. Whenever an expression fulfills the requirements listed above, the expression is evaluated at compile-time. This is true even if the expression is a sub-expression of a larger expression that contains non-constant constructs. The compile-time evaluation of constant expressions uses the same rules as run-time evaluation of non-constant expressions, except that where run-time evaluation would have thrown an exception, compile-time evaluation causes a compile-time error to occur. Unless a constant expression is explicitly placed in an `unchecked` context, overflows that occur in integral-type arithmetic operations and conversions during the compile-time evaluation of the expression always cause compile-time errors ([§12.23](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1223-constant-expressions)). Constant expressions occur in the contexts listed below. In these contexts, a compile-time error occurs if an expression cannot be fully evaluated at compile-time. * Constant declarations ([§15.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#154-constants)). * Enumeration member declarations ([§19.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/enums.md#194-enum-members)). * Default arguments of formal parameter lists ([§15.6.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1562-method-parameters)) * `case` labels of a `switch` statement ([§13.8.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1383-the-switch-statement)). * `goto case` statements ([§13.10.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#13104-the-goto-statement)). * Dimension lengths in an array creation expression ([§12.8.17.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128175-array-creation-expressions)) that includes an initializer. * Attributes ([§22](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/attributes.md#22-attributes)). An implicit constant expression conversion ([§10.2.11](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#10211-implicit-constant-expression-conversions)) permits a constant expression of type `int` to be converted to `sbyte`, `byte`, `short`, `ushort`, `uint`, or `ulong`, provided the value of the constant expression is within the range of the destination type. ## Drawbacks [drawbacks]: #drawbacks This proposal adds additional complexity to the compiler in exchange for broader applicability of interpolated strings. As these strings are fully evaluated at compile time, the valuable automatic formatting features of interpolated strings are less neccesary. Most use cases can be largely replicated through the alternatives below. ## Alternatives [alternatives]: #alternatives The current `+` operator for string concatenation can combine strings in a similar manner to the current proposal. ## 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. ================================================ FILE: proposals/csharp-10.0/enhanced-line-directives.md ================================================ # Enhanced #line directives [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary The compiler applies the mapping defined by `#line` directives to diagnostic locations and sequence points emitted to the PDB. Currently only the line number and file path can be mapped while the starting character is inferred from the source code. The proposal is to allow specifying full span mapping. ## Motivation [motivation]: #motivation DSLs that generate C# source code (such as ASP.NET Razor) can't currently produce precise source mapping using `#line` directives. This results in degraded debugging experience in some cases as the sequence points emitted to the PDB can't map to the precise location in the original source code. For example, the following Razor code ``` @page "/" Time: @DateTime.Now ``` generates code like so (simplified): ```C# #line hidden void Render() { _builder.Add("Time:"); #line 2 "page.razor" _builder.Add(DateTime.Now); #line hidden } ``` The above directive would map the sequence point emitted by the compiler for the `_builder.Add(DateTime.Now);` statement to the line 2, but the column would be off (16 instead of 7). The Razor source generator actually incorrectly generates the following code: ```C# #line hidden void Render() { _builder.Add("Time:"); _builder.Add( #line 2 "page.razor" DateTime.Now #line hidden ); } ``` The intent was to preserve the starting character and it works for diagnostic location mapping. However, this does not work for sequence points since `#line` directive only applies to the sequence points that follow it. There is no sequence point in the middle of the `_builder.Add(DateTime.Now);` statement (sequence points can only be emitted at IL instructions with empty evaluation stack). The `#line 2` directive in above code thus has no effect on the generated PDB and the debugger won't place a breakpoint or stop on the `@DateTime.Now` snippet in the Razor page. Issues addressed by this proposal: https://github.com/dotnet/roslyn/issues/43432 https://github.com/dotnet/roslyn/issues/46526 ## Detailed design [design]: #detailed-design We amend the syntax of `line_indicator` used in `pp_line` directive like so: Current: ``` line_indicator : decimal_digit+ whitespace file_name | decimal_digit+ | 'default' | 'hidden' ; ``` Proposed: ``` line_indicator : '(' decimal_digit+ ',' decimal_digit+ ')' '-' '(' decimal_digit+ ',' decimal_digit+ ')' whitespace decimal_digit+ whitespace file_name | '(' decimal_digit+ ',' decimal_digit+ ')' '-' '(' decimal_digit+ ',' decimal_digit+ ')' whitespace file_name | decimal_digit+ whitespace file_name | decimal_digit+ | 'default' | 'hidden' ; ``` That is, the `#line` directive would accept either 5 decimal numbers (_start line_, _start character_, _end line_, _end character_, _character offset_), 4 decimal numbers (_start line_, _start character_, _end line_, _end character_), or a single one (_line_). If _character offset_ is not specified its default value is 0, otherwise it specifies the number of UTF-16 characters. The number must be non-negative and less then length of the line following the #line directive in the unmapped file. (_start line_, _start character_)-(_end line_, _end character_) specifies a span in the mapped file. _start line_ and _end line_ are positive integers that specify line numbers. _start character_, _end character_ are positive integers that specify UTF-16 character numbers. _start line_, _start character_, _end line_, _end character_ are 1-based, meaning that the first line of the file and the first UTF-16 character on each line is assigned number 1. > The implementation would constraint these numbers so that they specify a valid [sequence point source span](https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#sequence-points-blob): > - _start line_ - 1 is within range [0, 0x20000000) and not equal to 0xfeefee. > - _end line_ - 1 is within range [0, 0x20000000) and not equal to 0xfeefee. > - _start character_ - 1 is within range [0, 0x10000) > - _end character_ - 1 is within range [0, 0x10000) > - _end line_ is greater or equal to _start line_. > - _start line_ is equal to _end line_ then _end character_ is greater than _start character_. > Note that the numbers specified in the directive syntax are 1-based numbers but the actual spans in the PDB are zero-based. Hence the -1 adjustments above. The mapped spans of sequence points and the locations of diagnostics that `#line` directive applies to are calculated as follows. Let _d_ be the zero-based number of the unmapped line containing the `#line` directive. Let span L = (start: (_start line_ - 1, _start character_ - 1), end: (_end line_ - 1, _end character_ - 1)) be zero-based span specified by the directive. Function M that maps a position (line, character) within the scope of the `#line` directive in the source file containing the #line directive to a mapped position (mapped line, mapped character) is defined as follows: _M_(_l_, _c_) = _l_ == _d_ + 1 => (_L.start.line_ + _l_ – _d_ – 1, _L.start.character_ + max(_c_ – _character offset_, 0)) _l_ > _d_ + 1 => (_L.start.line_ + _l_ – _d_ – 1, _c_) The syntax constructs that sequence points are associated with are determined by the compiler implementation and not covered by this specification. The compiler also decides for each sequence point its unmapped span. This span may partially or fully cover the associated syntax construct. Once the unmapped spans are determined by the compiler the function _M_ defined above is applied to their starting and ending positions, with the exception of the ending position of all sequence points within the scope of the #line directive whose unmapped location is at line _d_ + 1 and character less than character offset. The end position of all these sequence points is _L.end_. > Example [5.i] demonstrates why it is necessary to provide the ability to specify the end position of the first sequence point span. > The above definition allows the generator of the unmapped source code to avoid intimate knowledge of which exact source constructs of the C# language produce sequence points. The mapped spans of the sequence points in the scope of the `#line` directive are derived from the relative position of the corresponding unmapped spans to the first unmapped span. > Specifying the _character offset_ allows the generator to insert any single-line prefix on the first line. This prefix is generated code that is not present in the mapped file. Such inserted prefix affects the value of the first unmapped sequence point span. Therefore the starting character of subsequent sequence point spans need to be offset by the length of the prefix (_character offset_). See example [2]. ![image](https://user-images.githubusercontent.com/41759/120511514-563c7e00-c37f-11eb-9436-20c2def68932.png) ### Examples For clarity the examples use `spanof('...')` and `lineof('...')` pseudo-syntax to express the mapped span start position and line number, respectively, of the specified code snippet. #### 1. First and subsequent spans Consider the following code with unmapped zero-based line numbers listed on the right: ``` #line (1,10)-(1,15) "a" // 3 A();B( // 4 );C(); // 5 D(); // 6 ``` d = 3 L = (0, 9)..(0, 14) There are 4 sequence point spans the directive applies to with following unmapped and mapped spans: (4, 2)..(4, 5) => (0, 9)..(0, 14) (4, 6)..(5, 1) => (0, 15)..(1, 1) (5, 2)..(5, 5) => (1, 2)..(1, 5) (6, 4)..(6, 7) => (2, 4)..(2, 7) #### 2. Character offset Razor generates ` _builder.Add(` prefix of length 15 (including two leading spaces). Razor: ``` @page "/" @F(() => 1+1, () => 2+2 ) ``` Generated C#: ```C# #line hidden void Render() { #line spanof('F(...)') 15 "page.razor" // 4 _builder.Add(F(() => 1+1, // 5 () => 2+2 // 6 )); // 7 #line hidden } ); } ``` d = 4 L = (1, 1)..(3,0) _character offset_ = 15 Spans: - `_builder.Add(F(…));` => `F(…)`: (5, 2)..(7, 2) => (1, 1)..(3, 0) - `1+1` => `1+1`: (5, 23)..(5, 25) => (1, 9)..(1, 11) - `2+2` => `2+2`: (6, 7)..(6, 9) => (2, 7)..(2, 9) #### 3. Razor: Single-line span Razor: ``` @page "/" Time: @DateTime.Now ``` Generated C#: ```C# #line hidden void Render() { _builder.Add("Time:"); #line spanof('DateTime.Now') 15 "page.razor" _builder.Add(DateTime.Now); #line hidden ); } ``` #### 4. Razor: Multi-line span Razor: ``` @page "/" @JsonToHtml(@" { ""key1"": "value1", ""key2"": "value2" }") ``` Generated C#: ```C# #line hidden void Render() { _builder.Add("Time:"); #line spanof('JsonToHtml(@"...")') 15 "page.razor" _builder.Add(JsonToHtml(@" { ""key1"": "value1", ""key2"": "value2" }")); #line hidden } ); } ``` #### 5. Razor: block constructs ##### i. block containing expressions In this example, the mapped span of the first sequence point that is associated with the IL instruction that is emitted for the `_builder.Add(Html.Helper(() => ` statement needs to cover the whole expression of `Html.Helper(...)` in the generated file `a.razor`. This is achieved by application of rule [1] to the end position of the sequence point. ``` @Html.Helper(() => {

Hello World

@DateTime.Now }) ``` ```C# #line spanof('Html.Helper(() => { ... })') 13 "a.razor" _builder.Add(Html.Helper(() => #line lineof('{') "a.razor" { #line spanof('DateTime.Now') 13 "a.razor" _builder.Add(DateTime.Now); #line lineof('}') "a.razor" } #line hidden ) ``` ##### ii. block containing statements Uses existing `#line line file` form since a) Razor does not add any prefix, b) `{` is not present in the generated file and there can't be a sequence point placed on it, therefore the span of the first unmapped sequence point is unknown to Razor. The starting character of `Console` in the generated file must be aligned with the Razor file. ``` @{Console.WriteLine(1);Console.WriteLine(2);} ``` ```C# #line lineof('@{') "a.razor" Console.WriteLine(1);Console.WriteLine(2); #line hidden ``` ##### iii. block containing top-level code (@code, @functions) Uses existing `#line line file` form since a) Razor does not add any prefix, b) `{` is not present in the generated file and there can't be a sequence point placed on it, therefore the span of the first unmapped sequence point is unknown to Razor. The starting character of `[Parameter]` in the generated file must be aligned with the Razor file. ``` @code { [Parameter] public int IncrementAmount { get; set; } } ``` ```C# #line lineof('[') "a.razor" [Parameter] public int IncrementAmount { get; set; } #line hidden ``` #### 6. Razor: `@for`, `@foreach`, `@while`, `@do`, `@if`, `@switch`, `@using`, `@try`, `@lock` Uses existing `#line line file` form since a) Razor does not add any prefix. b) the span of the first unmapped sequence point may not be known to Razor (or shouldn't need to know). The starting character of the keyword in the generated file must be aligned with the Razor file. ``` @for (var i = 0; i < 10; i++) { } @if (condition) { } else { } ``` ```C# #line lineof('for') "a.razor" for (var i = 0; i < 10; i++) { } #line lineof('if') "a.razor" if (condition) { } else { } #line hidden ``` ================================================ FILE: proposals/csharp-10.0/extended-property-patterns.md ================================================ # Extended property patterns [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Allow property subpatterns to reference nested members, for instance: ```cs if (e is MethodCallExpression { Method.Name: "MethodName" }) ``` Instead of: ```cs if (e is MethodCallExpression { Method: { Name: "MethodName" } }) ``` ## Motivation [motivation]: #motivation When you want to match a child property, nesting another recursive pattern adds too much noise which will hurt readability with no real advantage. ## Detailed design [design]: #detailed-design The [*property_pattern*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/patterns.md#property-pattern) syntax is modified as follow: ```diff property_pattern : type? property_pattern_clause simple_designation? ; property_pattern_clause : '{' (subpattern (',' subpattern)* ','?)? '}' ; subpattern - : identifier ':' pattern + : subpattern_name ':' pattern ; +subpattern_name + : identifier + | subpattern_name '.' identifier + ; ``` The receiver for each name lookup is the type of the previous member *T0*, starting from the *input type* of the *property_pattern*. if *T* is a nullable type, *T0* is its underlying type, otherwise *T0* is equal to *T*. For example, a pattern of the form `{ Prop1.Prop2: pattern }` is exactly equivalent to `{ Prop1: { Prop2: pattern } }`. Note that this will include the null check when *T* is a nullable value type or a reference type. This null check means that the nested properties available will be the properties of *T0*, not of *T*. Repeated member paths are allowed. The compilation of pattern matching can take advantage of common parts of patterns. ================================================ FILE: proposals/csharp-10.0/file-scoped-namespaces.md ================================================ # File Scoped Namespaces [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary File scoped namespaces use a less verbose format for the typical case of files containing only one namespace. The file scoped namespace format is `namespace X.Y.Z;` (note the semicolon and lack of braces). This allows for files like the following: ```c# namespace X.Y.Z; using System; class X { } ``` The semantics are that using the `namespace X.Y.Z;` form is equivalent to writing `namespace X.Y.Z { ... }` where the remainder of the file following the file-scoped namespace is in the `...` section of a standard namespace declaration. ## Motivation Analysis of the C# ecosystem shows that approximately 99.7% files are all of either one of these forms: ```c# namespace X.Y.Z { // usings // types } ``` or ```c# // usings namespace X.Y.Z { // types } ``` However, both these forms force the user to indent the majority of their code and add a fair amount of ceremony for what is effectively a very basic concept. This affects clarity, uses horizontal and vertical space, and is often unsatisfying for users both used to C# and coming from other languages (which commonly have less ceremony here). The primary goal of the feature therefore is to meet the needs of the majority of the ecosystem with less unnecessary boilerplate. ## Detailed design This proposal takes the form of a diff to the existing compilation units ([§14.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/namespaces.md#142-compilation-units)) section of the specification. ### Diff ~~A *compilation_unit* defines the overall structure of a source file. A compilation unit consists of zero or more *using_directive*s followed by zero or more *global_attributes* followed by zero or more *namespace_member_declaration*s.~~ A *compilation_unit* defines the overall structure of a source file. A compilation unit consists of zero or more *using_directive*s followed by zero or more *global_attributes* followed by a *compilation_unit_body*. A *compilation_unit_body* can either be a *file_scoped_namespace_declaration* or zero or more *statement*s and *namespace_member_declaration*s. ```antlr compilation_unit ~~ : extern_alias_directive* using_directive* global_attributes? namespace_member_declaration*~~ : extern_alias_directive* using_directive* global_attributes? compilation_unit_body ; compilation_unit_body : statement* namespace_member_declaration* | file_scoped_namespace_declaration ; ``` ... unchanged ... A *file_scoped_namespace_declaration* will contribute members corresponding to the *namespace_declaration* it is semantically equivalent to. See ([Namespace Declarations](#namespace-declarations)) for more details. ## Namespace declarations A *namespace_declaration* consists of the keyword `namespace`, followed by a namespace name and body, optionally followed by a semicolon. A *file_scoped_namespace_declaration* consists of the keyword `namespace`, followed by a namespace name, a semicolon and an optional list of *extern_alias_directive*s, *using_directive*s and *type_declaration*s. ```antlr namespace_declaration : 'namespace' qualified_identifier namespace_body ';'? ; file_scoped_namespace_declaration : 'namespace' qualified_identifier ';' extern_alias_directive* using_directive* type_declaration* ; ... unchanged ... ``` ... unchanged ... the two namespace declarations above contribute to the same declaration space, in this case declaring two classes with the fully qualified names `N1.N2.A` and `N1.N2.B`. Because the two declarations contribute to the same declaration space, it would have been an error if each contained a declaration of a member with the same name. A *file_scoped_namespace_declaration* permits a namespace declaration to be written without the `{ ... }` block. For example: ```csharp extern alias A; namespace Name; using B; class C { } ``` is semantically equivalent to ```csharp extern alias A; namespace Name { using B; class C { } } ``` Specifically, a *file_scoped_namespace_declaration* is treated the same as a *namespace_declaration* at the same location in the *compilation_unit* with the same *qualified_identifier*. The *extern_alias_directive*s, *using_directive*s and *type_declaration*s of that *file_scoped_namespace_declaration* act as if they were declared in the same order inside the *namespace_body* of that *namespace_declaration*. A source file cannot contain both a *file_scoped_namespace_declaration* and a *namespace_declaration*. A source file cannot contain multiple *file_scoped_namespace_declaration*s. A *compilation_unit* cannot contain both a *file_scoped_namespace_declaration* and any top level *statement*s. *type_declaration*s cannot precede a *file_scoped_namespace_declaration*. ## Extern aliases ... unchanged ... ================================================ FILE: proposals/csharp-10.0/improved-definite-assignment.md ================================================ # Improved Definite Assignment Analysis [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Definite assignment [§9.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#94-definite-assignment) as specified has a few gaps which have caused users inconvenience. In particular, scenarios involving comparison to boolean constants, conditional-access, and null coalescing. ## Related discussions and issues csharplang discussion of this proposal: https://github.com/dotnet/csharplang/discussions/4240 Probably a dozen or so user reports can be found via this or similar queries (i.e. search for "definite assignment" instead of "CS0165", or search in csharplang). https://github.com/dotnet/roslyn/issues?q=is%3Aclosed+is%3Aissue+label%3A%22Resolution-By+Design%22+cs0165 I have included related issues in the scenarios below to give a sense of the relative impact of each scenario. ## Scenarios As a point of reference, let's start with a well-known "happy case" that does work in definite assignment and in nullable. ```cs #nullable enable C c = new C(); if (c != null && c.M(out object obj0)) { obj0.ToString(); // ok } public class C { public bool M(out object obj) { obj = new object(); return true; } } ``` ### Comparison to bool constant - https://github.com/dotnet/csharplang/discussions/801 - https://github.com/dotnet/roslyn/issues/45582 - Links to 4 other issues where people were affected by this. ```cs if ((c != null && c.M(out object obj1)) == true) { obj1.ToString(); // undesired error } if ((c != null && c.M(out object obj2)) is true) { obj2.ToString(); // undesired error } ``` ### Comparison between a conditional access and a constant value - https://github.com/dotnet/roslyn/issues/33559 - https://github.com/dotnet/csharplang/discussions/4214 - https://github.com/dotnet/csharplang/issues/3659 - https://github.com/dotnet/csharplang/issues/3485 - https://github.com/dotnet/csharplang/issues/3659 This scenario is probably the biggest one. We do support this in nullable but not in definite assignment. ```cs if (c?.M(out object obj3) == true) { obj3.ToString(); // undesired error } ``` ### Conditional access coalesced to a bool constant - https://github.com/dotnet/csharplang/discussions/916 - https://github.com/dotnet/csharplang/issues/3365 This scenario is very similar to the previous one. This is also supported in nullable but not in definite assignment. ```cs if (c?.M(out object obj4) ?? false) { obj4.ToString(); // undesired error } ``` ### Conditional expressions where one arm is a bool constant - https://github.com/dotnet/roslyn/issues/4272 It's worth pointing out that we already have special behavior for when the condition expression is constant (i.e. `true ? a : b`). We just unconditionally visit the arm indicated by the constant condition and ignore the other arm. Also note that we haven't handled this scenario in nullable. ```cs if (c != null ? c.M(out object obj4) : false) { obj4.ToString(); // undesired error } ``` ## Specification ### ?. (null-conditional operator) expressions We introduce a new section **?. (null-conditional operator) expressions**. See the null-conditional operator specification ([§12.8.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1288-null-conditional-member-access))and Precise rules for determining definite assignment [§9.4.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#944-precise-rules-for-determining-definite-assignment) for context. As in the definite assignment rules linked above, we refer to a given initially unassigned variable as *v*. We introduce the concept of "directly contains". An expression *E* is said to "directly contain" a subexpression *E1* if it is not subject to a user-defined conversion [§10.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#105-user-defined-conversions) whose parameter is not of a non-nullable value type, and one of the following conditions holds: - *E* is *E1*. For example, `a?.b()` directly contains the expression `a?.b()`. - If *E* is a parenthesized expression `(E2)`, and *E2* directly contains *E1*. - If *E* is a null-forgiving operator expression `E2!`, and *E2* directly contains *E1*. - If *E* is a cast expression `(T)E2`, and the cast does not subject *E2* to a non-lifted user-defined conversion whose parameter is not of a non-nullable value type, and *E2* directly contains *E1*. For an expression *E* of the form `primary_expression null_conditional_operations`, let *E0* be the expression obtained by textually removing the leading ? from each of the *null_conditional_operations* of *E* that have one, as in the linked specification above. In subsequent sections we will refer to *E0* as the *non-conditional counterpart* to the null-conditional expression. Note that some expressions in subsequent sections are subject to additional rules that only apply when one of the operands directly contains a null-conditional expression. - The definite assignment state of *v* at any point within *E* is the same as the definite assignment state at the corresponding point within *E0*. - The definite assignment state of *v* after *E* is the same as the definite assignment state of *v* after *primary_expression*. #### Remarks We use the concept of "directly contains" to allow us to skip over relatively simple "wrapper" expressions when analyzing conditional accesses that are compared to other values. For example, `((a?.b(out x))!) == true` is expected to result in the same flow state as `a?.b == true` in general. We also want to allow analysis to function in the presence of a number of possible conversions on a conditional access. Propagating out "state when not null" is not possible when the conversion is user-defined, though, since we can't count on user-defined conversions to honor the constraint that the output is non-null only if the input is non-null. The only exception to this is when the user-defined conversion's input is a non-nullable value type. For example: ```cs public struct S1 { } public struct S2 { public static implicit operator S2?(S1 s1) => null; } ``` This also includes lifted conversions like the following: ```cs string x; S1? s1 = null; _ = s1?.M1(x = "a") ?? s1.Value.M2(x = "a"); x.ToString(); // ok public struct S1 { public S1 M1(object obj) => this; public S2 M2(object obj) => new S2(); } public struct S2 { public static implicit operator S2(S1 s1) => default; } ``` When we consider whether a variable is assigned at a given point within a null-conditional expression, we simply assume that any preceding null-conditional operations within the same null-conditional expression succeeded. For example, given a conditional expression `a?.b(out x)?.c(x)`, the non-conditional counterpart is `a.b(out x).c(x)`. If we want to know the definite assignment state of `x` before `?.c(x)`, for example, then we perform a "hypothetical" analysis of `a.b(out x)` and use the resulting state as an input to `?.c(x)`. ### Boolean constant expressions We introduce a new section "Boolean constant expressions": For an expression *expr* where *expr* is a constant expression with a bool value: - The definite assignment state of *v* after *expr* is determined by: - If *expr* is a constant expression with value *true*, and the state of *v* before *expr* is "not definitely assigned", then the state of *v* after *expr* is "definitely assigned when false". - If *expr* is a constant expression with value *false*, and the state of *v* before *expr* is "not definitely assigned", then the state of *v* after *expr* is "definitely assigned when true". #### Remarks We assume that if an expression has a constant value bool `false`, for example, it's impossible to reach any branch that requires the expression to return `true`. Therefore variables are assumed to be definitely assigned in such branches. This ends up combining nicely with the spec changes for expressions like `??` and `?:` and enabling a lot of useful scenarios. It's also worth noting that we never expect to be in a conditional state *before* visiting a constant expression. That's why we do not account for scenarios such as "*expr* is a constant expression with value *true*, and the state of *v* before *expr* is "definitely assigned when true". ### ?? (null-coalescing expressions) augment We augment section [§9.4.4.29](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#94429--expressions) as follows: For an expression *expr* of the form `expr_first ?? expr_second`: - ... - The definite assignment state of *v* after *expr* is determined by: - ... - If *expr_first* directly contains a null-conditional expression *E*, and *v* is definitely assigned after the non-conditional counterpart *E0*, then the definite assignment state of *v* after *expr* is the same as the definite assignment state of *v* after *expr_second*. #### Remarks The above rule formalizes that for an expression like `a?.M(out x) ?? (x = false)`, either the `a?.M(out x)` was fully evaluated and produced a non-null value, in which case `x` was assigned, or the `x = false` was evaluated, in which case `x` was also assigned. Therefore `x` is always assigned after this expression. This also handles the `dict?.TryGetValue(key, out var value) ?? false` scenario, by observing that *v* is definitely assigned after `dict.TryGetValue(key, out var value)`, and *v* is "definitely assigned when true" after `false`, and concluding that *v* must be "definitely assigned when true". The more general formulation also allows us to handle some more unusual scenarios, such as: - `if (x?.M(out y) ?? (b && z.M(out y))) y.ToString();` - `if (x?.M(out y) ?? z?.M(out y) ?? false) y.ToString();` ### ?: (conditional) expressions We augment section [§9.4.4.30](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#94430--expressions) as follows: For an expression *expr* of the form `expr_cond ? expr_true : expr_false`: - ... - The definite assignment state of *v* after *expr* is determined by: - ... - If the state of *v* after *expr_true* is "definitely assigned when true", and the state of *v* after *expr_false* is "definitely assigned when true", then the state of *v* after *expr* is "definitely assigned when true". - If the state of *v* after *expr_true* is "definitely assigned when false", and the state of *v* after *expr_false* is "definitely assigned when false", then the state of *v* after *expr* is "definitely assigned when false". #### Remarks This makes it so when both arms of a conditional expression result in a conditional state, we join the corresponding conditional states and propagate it out instead of unsplitting the state and allowing the final state to be non-conditional. This enables scenarios like the following: ```cs bool b = true; object x = null; int y; if (b ? x != null && Set(out y) : x != null && Set(out y)) { y.ToString(); } bool Set(out int x) { x = 0; return true; } ``` This is an admittedly niche scenario, that compiles without error in the native compiler, but was broken in Roslyn in order to match the specification at the time. ### ==/!= (relational equality operator) expressions We introduce a new section **==/!= (relational equality operator) expressions**. The general rules for expressions with embedded expressions [§9.4.4.23](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#94423-general-rules-for-expressions-with-embedded-expressions) apply, except for the scenarios described below. For an expression *expr* of the form `expr_first == expr_second`, where `==` is a predefined comparison operator ([§12.12](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1212-relational-and-type-testing-operators)) or a lifted operator ([§12.4.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1248-lifted-operators)), the definite assignment state of *v* after *expr* is determined by: - If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is a constant expression with value *null*, and the state of *v* after the non-conditional counterpart *E0* is "definitely assigned", then the state of *v* after *expr* is "definitely assigned when false". - If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is an expression of a non-nullable value type, or a constant expression with a non-null value, and the state of *v* after the non-conditional counterpart *E0* is "definitely assigned", then the state of *v* after *expr* is "definitely assigned when true". - If *expr_first* is of type *boolean*, and *expr_second* is a constant expression with value *true*, then the definite assignment state after *expr* is the same as the definite assignment state after *expr_first*. - If *expr_first* is of type *boolean*, and *expr_second* is a constant expression with value *false*, then the definite assignment state after *expr* is the same as the definite assignment state of *v* after the logical negation expression `!expr_first`. For an expression *expr* of the form `expr_first != expr_second`, where `!=` is a predefined comparison operator ([§12.12](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1212-relational-and-type-testing-operators)) or a lifted operator (([§12.4.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1248-lifted-operators))), the definite assignment state of *v* after *expr* is determined by: - If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is a constant expression with value *null*, and the state of *v* after the non-conditional counterpart *E0* is "definitely assigned", then the state of *v* after *expr* is "definitely assigned when true". - If *expr_first* directly contains a null-conditional expression *E* and *expr_second* is an expression of a non-nullable value type, or a constant expression with a non-null value, and the state of *v* after the non-conditional counterpart *E0* is "definitely assigned", then the state of *v* after *expr* is "definitely assigned when false". - If *expr_first* is of type *boolean*, and *expr_second* is a constant expression with value *true*, then the definite assignment state after *expr* is the same as the definite assignment state of *v* after the logical negation expression `!expr_first`. - If *expr_first* is of type *boolean*, and *expr_second* is a constant expression with value *false*, then the definite assignment state after *expr* is the same as the definite assignment state after *expr_first*. All of the above rules in this section are commutative, meaning that if a rule applies when evaluated in the form `expr_second op expr_first`, it also applies in the form `expr_first op expr_second`. #### Remarks The general idea expressed by these rules is: - if a conditional access is compared to `null`, then we know the operations definitely occurred if the result of the comparison is `false` - if a conditional access is compared to a non-nullable value type or a non-null constant, then we know the operations definitely occurred if the result of the comparison is `true`. - since we can't trust user-defined operators to provide reliable answers where initialization safety is concerned, the new rules only apply when a predefined `==`/`!=` operator is in use. We may eventually want to refine these rules to thread through conditional state which is present at the end of a member access or call. Such scenarios don't really happen in definite assignment, but they do happen in nullable in the presence of `[NotNullWhen(true)]` and similar attributes. This would require special handling for `bool` constants in addition to just handling for `null`/non-null constants. Some consequences of these rules: - `if (a?.b(out var x) == true)) x() else x();` will error in the 'else' branch - `if (a?.b(out var x) == 42)) x() else x();` will error in the 'else' branch - `if (a?.b(out var x) == false)) x() else x();` will error in the 'else' branch - `if (a?.b(out var x) == null)) x() else x();` will error in the 'then' branch - `if (a?.b(out var x) != true)) x() else x();` will error in the 'then' branch - `if (a?.b(out var x) != 42)) x() else x();` will error in the 'then' branch - `if (a?.b(out var x) != false)) x() else x();` will error in the 'then' branch - `if (a?.b(out var x) != null)) x() else x();` will error in the 'else' branch ### `is` operator and `is` pattern expressions We introduce a new section **`is` operator and `is` pattern expressions**. For an expression *expr* of the form `E is T`, where *T* is any type or pattern - The definite assignment state of *v* before *E* is the same as the definite assignment state of *v* before *expr*. - The definite assignment state of *v* after *expr* is determined by: - If *E* directly contains a null-conditional expression, and the state of *v* after the non-conditional counterpart *E0* is "definitely assigned", and `T` is any type or a pattern that does not match a `null` input, then the state of *v* after *expr* is "definitely assigned when true". - If *E* directly contains a null-conditional expression, and the state of *v* after the non-conditional counterpart *E0* is "definitely assigned", and `T` is a pattern that matches a `null` input, then the state of *v* after *expr* is "definitely assigned when false". - If *E* is of type boolean and `T` is a pattern which only matches a `true` input, then the definite assignment state of *v* after *expr* is the same as the definite assignment state of *v* after E. - If *E* is of type boolean and `T` is a pattern which only matches a `false` input, then the definite assignment state of *v* after *expr* is the same as the definite assignment state of *v* after the logical negation expression `!expr`. - Otherwise, if the definite assignment state of *v* after E is "definitely assigned", then the definite assignment state of *v* after *expr* is "definitely assigned". #### Remarks This section is meant to address similar scenarios as in the `==`/`!=` section above. This specification does not address recursive patterns, e.g. `(a?.b(out x), c?.d(out y)) is (object, object)`. Such support may come later if time permits. ## Additional scenarios This specification doesn't currently address scenarios involving pattern switch expressions and switch statements. For example: ```cs _ = c?.M(out object obj4) switch { not null => obj4.ToString() // undesired error }; ``` It seems like support for this could come later if time permits. There have been several categories of bugs filed for nullable which require we essentially increase the sophistication of pattern analysis. It is likely that any ruling we make which improves definite assignment would also be carried over to nullable. https://github.com/dotnet/roslyn/issues/49353 https://github.com/dotnet/roslyn/issues/46819 https://github.com/dotnet/roslyn/issues/44127 ## Drawbacks [drawbacks]: #drawbacks It feels odd to have the analysis "reach down" and have special recognition of conditional accesses, when typically flow analysis state is supposed to propagate upward. We are concerned about how a solution like this could intersect painfully with possible future language features that do null checks. ## Alternatives [alternatives]: #alternatives Two alternatives to this proposal: 1. Introduce "state when null" and "state when not null" to the language and compiler. This has been judged to be too much effort for the scenarios we are trying to solve, but that we could potentially implement the above proposal and then move to a "state when null/not null" model later on without breaking people. 2. Do nothing. ## Unresolved questions [unresolved]: #unresolved-questions There are impacts on switch expressions that should be specified: https://github.com/dotnet/csharplang/discussions/4240#discussioncomment-343395 ## Design meetings https://github.com/dotnet/csharplang/discussions/4243 ================================================ FILE: proposals/csharp-10.0/improved-interpolated-strings.md ================================================ # Improved Interpolated Strings [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary We introduce a new pattern for creating and using interpolated string expressions to allow for efficient formatting and use in both general `string` scenarios and more specialized scenarios such as logging frameworks, without incurring unnecessary allocations from formatting the string in the framework. ## Motivation Today, string interpolation mainly lowers down to a call to `string.Format`. This, while general purpose, can be inefficient for a number of reasons: 1. It boxes any struct arguments, unless the runtime has happened to introduce an overload of `string.Format` that takes exactly the correct types of arguments in exactly the correct order. * This ordering is why the runtime is hesitant to introduce generic versions of the method, as it would lead to combinatoric explosion of generic instantiations of a very common method. 2. It has to allocate an array for the arguments in most cases. 3. There is no opportunity to avoid instantiating the instance if it's not needed. Logging frameworks, for example, will recommend avoiding string interpolation because it will cause a string to be realized that may not be needed, depending on the current log-level of the application. 4. It can never use `Span` or other ref struct types today, because ref structs are not allowed as generic type parameters, meaning that if a user wants to avoid copying to intermediate locations they have to manually format strings. Internally, the runtime has a type called `ValueStringBuilder` to help deal with the first 2 of these scenarios. They pass a stackalloc'd buffer to the builder, repeatedly call `AppendFormat` with every part, and then get a final string out. If the resulting string goes past the bounds of the stack buffer, they can then move to an array on the heap. However, this type is dangerous to expose directly, as incorrect usage could lead to a rented array to be double-disposed, which then will cause all sorts of undefined behavior in the program as two locations think they have sole access to the rented array. This proposal creates a way to use this type safely from native C# code by just writing an interpolated string literal, leaving written code unchanged while improving every interpolated string that a user writes. It also extends this pattern to allow for interpolated strings passed as arguments to other methods to use a handler pattern, defined by receiver of the method, that will allow things like logging frameworks to avoid allocating strings that will never be needed, and giving C# users familiar, convenient interpolation syntax. ## Detailed Design ### The handler pattern We introduce a new handler pattern that can represent an interpolated string passed as an argument to a method. The simple English of the pattern is as follows: When an _interpolated\_string\_expression_ is passed as an argument to a method, we look at the type of the parameter. If the parameter type has a constructor that can be invoked with 2 int parameters, `literalLength` and `formattedCount`, optionally takes additional parameters specified by an attribute on the original parameter, optionally has an out boolean trailing parameter, and the type of the original parameter has instance `AppendLiteral` and `AppendFormatted` methods that can be invoked for every part of the interpolated string, then we lower the interpolation using that, instead of into a traditional call to `string.Format(formatStr, args)`. A more concrete example is helpful for picturing this: ```cs // The handler that will actually "build" the interpolated string" [InterpolatedStringHandler] public ref struct TraceLoggerParamsInterpolatedStringHandler { // Storage for the built-up string private bool _logLevelEnabled; public TraceLoggerParamsInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, out bool handlerIsValid) { if (!logger._logLevelEnabled) { handlerIsValid = false; return; } handlerIsValid = true; _logLevelEnabled = logger.EnabledLevel; } public void AppendLiteral(string s) { // Store and format part as required } public void AppendFormatted(T t) { // Store and format part as required } } // The logger class. The user has an instance of this, accesses it via static state, or some other access // mechanism public class Logger { // Initialization code omitted public LogLevel EnabledLevel; public void LogTrace([InterpolatedStringHandlerArguments("")]TraceLoggerParamsInterpolatedStringHandler handler) { // Impl of logging } } Logger logger = GetLogger(LogLevel.Info); // Given the above definitions, usage looks like this: var name = "Fred Silberberg"; logger.LogTrace($"{name} will never be printed because info is < trace!"); // This is converted to: var name = "Fred Silberberg"; var receiverTemp = logger; var handler = new TraceLoggerParamsInterpolatedStringHandler(literalLength: 47, formattedCount: 1, receiverTemp, out var handlerIsValid); if (handlerIsValid) { handler.AppendFormatted(name); handler.AppendLiteral(" will never be printed because info is < trace!"); } receiverTemp.LogTrace(handler); ``` Here, because `TraceLoggerParamsInterpolatedStringHandler` has a constructor with the correct parameters, we say that the interpolated string has an implicit handler conversion to that parameter, and it lowers to the pattern shown above. The specese needed for this is a bit complicated, and is expanded below. The rest of this proposal will use `Append...` to refer to either of `AppendLiteral` or `AppendFormatted` in cases when both are applicable. #### New attributes The compiler recognizes the `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute`: ```cs using System; namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] public sealed class InterpolatedStringHandlerAttribute : Attribute { public InterpolatedStringHandlerAttribute() { } } } ``` This attribute is used by the compiler to determine if a type is a valid interpolated string handler type. The compiler also recognizes the `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`: ```cs namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute { public InterpolatedHandlerArgumentAttribute(string argument); public InterpolatedHandlerArgumentAttribute(params string[] arguments); public string[] Arguments { get; } } } ``` This attribute is used on parameters, to inform the compiler how to lower an interpolated string handler pattern used in a parameter position. #### Interpolated string handler conversion Type `T` is said to be an _applicable\_interpolated\_string\_handler\_type_ if it is attributed with `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute`. There exists an implicit _interpolated\_string\_handler\_conversion_ to `T` from an _interpolated\_string\_expression_, or an _additive\_expression_ composed entirely of _interpolated\_string\_expression_s and using only `+` operators. For simplicity in the rest of this speclet, _interpolated\_string\_expression_ refers to both a simple _interpolated\_string\_expression_, and to an _additive\_expression_ composed entirely of _interpolated\_string\_expression_s and using only `+` operators. Note that this conversion always exists, regardless of whether there will be later errors when actually attempting to lower the interpolation using the handler pattern. This is done to help ensure that there are predictable and useful errors and that runtime behavior doesn't change based on the content of an interpolated string. #### Applicable function member adjustments We adjust the wording of the applicable function member algorithm ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12642-applicable-function-member)) as follows (a new sub-bullet is added to each section, in bold): A function member is said to be an ***applicable function member*** with respect to an argument list `A` when all of the following are true: * Each argument in `A` corresponds to a parameter in the function member declaration as described in Corresponding parameters ([§12.6.2.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12622-corresponding-parameters)), and any parameter to which no argument corresponds is an optional parameter. * For each argument in `A`, the parameter passing mode of the argument (i.e., value, `ref`, or `out`) is identical to the parameter passing mode of the corresponding parameter, and * for a value parameter or a parameter array, an implicit conversion ([§10.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#102-implicit-conversions)) exists from the argument to the type of the corresponding parameter, or * **for a `ref` parameter whose type is a struct type, an implicit _interpolated\_string\_handler\_conversion_ exists from the argument to the type of the corresponding parameter, or** * for a `ref` or `out` parameter, the type of the argument is identical to the type of the corresponding parameter. After all, a `ref` or `out` parameter is an alias for the argument passed. For a function member that includes a parameter array, if the function member is applicable by the above rules, it is said to be applicable in its ***normal form***. If a function member that includes a parameter array is not applicable in its normal form, the function member may instead be applicable in its ***expanded form***: * The expanded form is constructed by replacing the parameter array in the function member declaration with zero or more value parameters of the element type of the parameter array such that the number of arguments in the argument list `A` matches the total number of parameters. If `A` has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and is thus not applicable. * Otherwise, the expanded form is applicable if for each argument in `A` the parameter passing mode of the argument is identical to the parameter passing mode of the corresponding parameter, and * for a fixed value parameter or a value parameter created by the expansion, an implicit conversion ([§10.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#102-implicit-conversions)) exists from the type of the argument to the type of the corresponding parameter, or * **for a `ref` parameter whose type is a struct type, an implicit _interpolated\_string\_handler\_conversion_ exists from the argument to the type of the corresponding parameter, or** * for a `ref` or `out` parameter, the type of the argument is identical to the type of the corresponding parameter. Important note: this means that if there are 2 otherwise equivalent overloads, that only differ by the type of the _applicable\_interpolated\_string\_handler\_type_, these overloads will be considered ambiguous. Further, because we do not see through explicit casts, it is possible that there could arise an unresolvable scenario where both applicable overloads use `InterpolatedStringHandlerArguments` and are totally uncallable without manually performing the handler lowering pattern. We could potentially make changes to the better function member algorithm to resolve this if we so choose, but this scenario unlikely to occur and isn't a priority to address. #### Better conversion from expression adjustments We change the better conversion from expression ([§12.6.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12645-better-conversion-from-expression)) section to the following: Given an implicit conversion `C1` that converts from an expression `E` to a type `T1`, and an implicit conversion `C2` that converts from an expression `E` to a type `T2`, `C1` is a ***better conversion*** than `C2` if: 1. `E` is a non-constant _interpolated\_string\_expression_, `C1` is an _implicit\_string\_handler\_conversion_, `T1` is an _applicable\_interpolated\_string\_handler\_type_, and `C2` is not an _implicit\_string\_handler\_conversion_, or 2. `E` does not exactly match `T2` and at least one of the following holds: * `E` exactly matches `T1` ([§12.6.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12645-better-conversion-from-expression)) * `T1` is a better conversion target than `T2` ([§12.6.4.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12647-better-conversion-target)) This does mean that there are some potentially non-obvious overload resolution rules, depending on whether the interpolated string in question is a constant-expression or not. For example: ```cs void Log(string s) { ... } void Log(TraceLoggerParamsInterpolatedStringHandler p) { ... } Log($""); // Calls Log(string s), because $"" is a constant expression Log($"{"test"}"); // Calls Log(string s), because $"{"test"}" is a constant expression Log($"{1}"); // Calls Log(TraceLoggerParamsInterpolatedStringHandler p), because $"{1}" is not a constant expression ``` This is introduced so that things that can simply be emitted as constants do so, and don't incur any overhead, while things that cannot be constant use the handler pattern. ### InterpolatedStringHandler and Usage We introduce a new type in `System.Runtime.CompilerServices`: `DefaultInterpolatedStringHandler`. This is a ref struct with many of the same semantics as `ValueStringBuilder`, intended for direct use by the C# compiler. This struct would look approximately like this: ```cs // API Proposal issue: https://github.com/dotnet/runtime/issues/50601 namespace System.Runtime.CompilerServices { [InterpolatedStringHandler] public ref struct DefaultInterpolatedStringHandler { public DefaultInterpolatedStringHandler(int literalLength, int formattedCount); public string ToStringAndClear(); public void AppendLiteral(string value); public void AppendFormatted(T value); public void AppendFormatted(T value, string? format); public void AppendFormatted(T value, int alignment); public void AppendFormatted(T value, int alignment, string? format); public void AppendFormatted(ReadOnlySpan value); public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null); public void AppendFormatted(string? value); public void AppendFormatted(string? value, int alignment = 0, string? format = null); public void AppendFormatted(object? value, int alignment = 0, string? format = null); } } ``` We make a slight change to the rules for the meaning of an _interpolated\_string\_expression_ ([§12.8.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1283-interpolated-string-expressions)): **If the type of an interpolated string is `string` and the type `System.Runtime.CompilerServices.DefaultInterpolatedStringHandler` exists, and the current context supports using that type, the string** **is lowered using the handler pattern. The final `string` value is then obtained by calling `ToStringAndClear()` on the handler type.** **Otherwise, if** the type of an interpolated string is `System.IFormattable` or `System.FormattableString` [the rest is unchanged] The "and the current context supports using that type" rule is intentionally vague to give the compiler leeway in optimizing usage of this pattern. The handler type is likely to be a ref struct type, and ref struct types are normally not permitted in async methods. For this particular case, the compiler would be allowed to make use the handler if none of the interpolation holes contain an `await` expression, as we can statically determine that the handler type is safely used without additional complicated analysis because the handler will be dropped after the interpolated string expression is evaluated. **~~Open~~ Question**: Do we want to instead just make the compiler know about `DefaultInterpolatedStringHandler` and skip the `string.Format` call entirely? It would allow us to hide a method that we don't necessarily want to put in people's faces when they manually call `string.Format`. _Answer_: Yes. **~~Open~~ Question**: Do we want to have handlers for `System.IFormattable` and `System.FormattableString` as well? _Answer_: No. ### Handler pattern codegen In this section, method invocation resolution refers to the steps listed in [§12.8.10.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128102-method-invocations). #### Constructor resolution Given an _applicable\_interpolated\_string\_handler\_type_ `T` and an _interpolated\_string\_expression_ `i`, method invocation resolution and validation for a valid constructor on `T` is performed as follows: 1. Member lookup for instance constructors is performed on `T`. The resulting method group is called `M`. 2. The argument list `A` is constructed as follows: 1. The first two arguments are integer constants, representing the literal length of `i`, and the number of _interpolation_ components in `i`, respectively. 2. If `i` is used as an argument to some parameter `pi` in method `M1`, and parameter `pi` is attributed with `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`, then for every name `Argx` in the `Arguments` array of that attribute the compiler matches it to a parameter `px` that has the same name. The empty string is matched to the receiver of `M1`. * If any `Argx` is not able to be matched to a parameter of `M1`, or an `Argx` requests the receiver of `M1` and `M1` is a static method, an error is produced and no further steps are taken. * Otherwise, the type of every resolved `px` is added to the argument list, in the order specified by the `Arguments` array. Each `px` is passed with the same `ref` semantics as is specified in `M1`. 3. The final argument is a `bool`, passed as an `out` parameter. 3. Traditional method invocation resolution is performed with method group `M` and argument list `A`. For the purposes of method invocation final validation, the context of `M` is treated as a _member\_access_ through type `T`. * If a single-best constructor `F` was found, the result of overload resolution is `F`. * If no applicable constructors were found, step 3 is retried, removing the final `bool` parameter from `A`. If this retry also finds no applicable members, an error is produced and no further steps are taken. * If no single-best method was found, the result of overload resolution is ambiguous, an error is produced, and no further steps are taken. 4. Final validation on `F` is performed. * If any element of `A` occurred lexically after `i`, an error is produced and no further steps are taken. * If any `A` requests the receiver of `F`, and `F` is an indexer being used as an _initializer\_target_ in a _member\_initializer_, then an error is reported and no further steps are taken. Note: the resolution here intentionally do _not_ use the actual expressions passed as other arguments for `Argx` elements. We only consider the types post-conversion. This makes sure that we don't have double-conversion issues, or unexpected cases where a lambda is bound to one delegate type when passed to `M1` and bound to a different delegate type when passed to `M`. Note: We report an error for indexers uses as member initializers because of the order of evaluation for nested member initializers. Consider this code snippet: ```cs var x1 = new C1 { C2 = { [GetString()] = { A = 2, B = 4 } } }; /* Lowering: __c1 = new C1(); string argTemp = GetString(); __c1.C2[argTemp][1] = 2; __c1.C2[argTemp][3] = 4; Prints: GetString get_C2 get_C2 */ string GetString() { Console.WriteLine("GetString"); return ""; } class C1 { private C2 c2 = new C2(); public C2 C2 { get { Console.WriteLine("get_C2"); return c2; } set { } } } class C2 { public C3 this[string s] { get => new C3(); set { } } } class C3 { public int A { get => 0; set { } } public int B { get => 0; set { } } } ``` The arguments to `__c1.C2[]` are evaluated _before_ the receiver of the indexer. While we could come up with a lowering that works for this scenario (either by creating a temp for `__c1.C2` and sharing it across both indexer invocations, or only using it for the first indexer invocation and sharing the argument across both invocations) we think that any lowering would be confusing for what we believe is a pathological scenario. Therefore, we forbid the scenario entirely. **~~Open Question~~**: If we use a constructor instead of `Create`, we'd improve runtime codegen, at the expense of narrowing the pattern a bit. _Answer_: We will restrict to constructors for now. We can revisit adding a general `Create` method later if the scenario arises. #### `Append...` method overload resolution Given an _applicable\_interpolated\_string\_handler\_type_ `T` and an _interpolated\_string\_expression_ `i`, overload resolution for a set of valid `Append...` methods on `T` is performed as follows: 1. If there are any _interpolated\_regular\_string\_character_ components in `i`: 1. Member lookup on `T` with the name `AppendLiteral` is performed. The resulting method group is called `Ml`. 2. The argument list `Al` is constructed with one value parameter of type `string`. 3. Traditional method invocation resolution is performed with method group `Ml` and argument list `Al`. For the purposes of method invocation final validation, the context of `Ml` is treated as a _member\_access_ through an instance of `T`. * If a single-best method `Fi` is found and no errors were produced, the result of method invocation resolution is `Fi`. * Otherwise, an error is reported. 2. For every _interpolation_ `ix` component of `i`: 1. Member lookup on `T` with the name `AppendFormatted` is performed. The resulting method group is called `Mf`. 2. The argument list `Af` is constructed: 1. The first parameter is the `expression` of `ix`, passed by value. 2. If `ix` directly contains a _constant\_expression_ component, then an integer value parameter is added, with the name `alignment` specified. 3. If `ix` is directly followed by an _interpolation\_format_, then a string value parameter is added, with the name `format` specified. 3. Traditional method invocation resolution is performed with method group `Mf` and argument list `Af`. For the purposes of method invocation final validation, the context of `Mf` is treated as a _member\_access_ through an instance of `T`. * If a single-best method `Fi` is found, the result of method invocation resolution is `Fi`. * Otherwise, an error is reported. 3. Finally, for every `Fi` discovered in steps 1 and 2, final validation is performed: * If any `Fi` does not return `bool` by value or `void`, an error is reported. * If all `Fi` do not return the same type, an error is reported. Note that these rules do not permit extension methods for the `Append...` calls. We could consider enabling that if we choose, but this is analogous to the enumerator pattern, where we allow `GetEnumerator` to be an extension method, but not `Current` or `MoveNext()`. These rules _do_ permit default parameters for the `Append...` calls, which will work with things like `CallerLineNumber` or `CallerArgumentExpression` (when supported by the language). We have separate overload lookup rules for base elements vs interpolation holes because some handlers will want to be able to understand the difference between the components that were interpolated and the components that were part of the base string. **~~Open~~ Question** Some scenarios, like structured logging, want to be able to provide names for interpolation elements. For example, today a logging call might look like `Log("{name} bought {itemCount} items", name, items.Count);`. The names inside the `{}` provide important structure information for loggers that help with ensuring output is consistent and uniform. Some cases might be able to reuse the `:format` component of an interpolation hole for this, but many loggers already understand format specifiers and have existing behavior for output formatting based on this info. Is there some syntax we can use to enable putting these named specifiers in? Some cases may be able to get away with `CallerArgumentExpression`, provided that support does land in C# 10. But for cases that invoke a method/property, that may not be sufficient. _Answer_: While there are some interesting parts to templated strings we could explore in an orthogonal language feature, we don't think a specific syntax here has much benefit over solutions such as using a tuple: `$"{("StructuredCategory", myExpression)}"`. #### Performing the conversion Given an _applicable\_interpolated\_string\_handler\_type_ `T` and an _interpolated\_string\_expression_ `i` that had a valid constructor `Fc` and `Append...` methods `Fa` resolved, lowering for `i` is performed as follows: 1. Any arguments to `Fc` that occur lexically before `i` are evaluated and stored into temporary variables in lexical order. In order to preserve lexical ordering, if `i` occurred as part of a larger expression `e`, any components of `e` that occurred before `i` will be evaluated as well, again in lexical order. 2. `Fc` is called with the length of the interpolated string literal components, the number of _interpolation_ holes, any previously evaluated arguments, and a `bool` out argument (if `Fc` was resolved with one as the last parameter). The result is stored into a temporary value `ib`. 1. The length of the literal components is calculated after replacing any _open_brace_escape_sequence_ with a single `{`, and any _close_brace_escape_sequence_ with a single `}`. 3. If `Fc` ended with a `bool` out argument, a check on that `bool` value is generated. If true, the methods in `Fa` will be called. Otherwise, they will not be called. 4. For every `Fax` in `Fa`, `Fax` is called on `ib` with either the current literal component or _interpolation_ expression, as appropriate. If `Fax` returns a `bool`, the result is logically anded with all preceding `Fax` calls. 1. If `Fax` is a call to `AppendLiteral`, the literal component is unescaped by replacing any _open_brace_escape_sequence_ with a single `{`, and any _close_brace_escape_sequence_ with a single `}`. 5. The result of the conversion is `ib`. Again, note that arguments passed to `Fc` and arguments passed to `e` are the same temp. Conversions may occur on top of the temp to convert to a form that `Fc` requires, but for example lambdas cannot be bound to a different delegate type between `Fc` and `e`. **~~Open~~ Question** This lowering means that subsequent parts of the interpolated string after a false-returning `Append...` call don't get evaluated. This could potentially be very confusing, particularly if the format hole is side-effecting. We could instead evaluate all format holes first, then repeatedly call `Append...` with the results, stopping if it returns false. This would ensure that all expressions get evaluated as one might expect, but we call as few methods as we need to. While the partial evaluation might be desirable for some more advanced cases, it is perhaps non-intuitive for the general case. Another alternative, if we want to always evaluate all format holes, is to remove the `Append...` version of the API and just do repeated `Format` calls. The handler can track whether it should just be dropping the argument and immediately returning for this version. _Answer_: We will have conditional evaluation of the holes. **~~Open~~ Question** Do we need to dispose of disposable handler types, and wrap calls with try/finally to ensure that Dispose is called? For example, the interpolated string handler in the bcl might have a rented array inside it, and if one of the interpolation holes throws an exception during evaluation, that rented array could be leaked if it wasn't disposed. _Answer_: No. handlers can be assigned to locals (such as `MyHandler handler = $"{MyCode()};`), and the lifetime of such handlers is unclear. Unlike foreach enumerators, where the lifetime is obvious and no user-defined local is created for the enumerator. ### Impact on nullable reference types To minimize complexity of the implementation, we have a few limitations on how we perform nullable analysis on interpolated string handler constructors used as arguments to a method or indexer. In particular, we do not flow information from the constructor back through to the original slots of parameters or arguments from the original context, and we do not use constructor parameter types to inform generic type inference for type parameters in the containing method. An example of where this can have an impact is: ```cs string s = ""; C c = new C(); c.M(s, $"", c.ToString(), s.ToString()); // No warnings on c.ToString() or s.ToString(), as the `MaybeNull` does not flow back. public class C { public void M(string s1, [InterpolatedStringHandlerArgument("", "s1")] CustomHandler c1, string s2, string s3) { } } [InterpolatedStringHandler] public partial struct CustomHandler { public CustomHandler(int literalLength, int formattedCount, [MaybeNull] C c, [MaybeNull] string s) : this() { } } ``` ```cs string? s = null; M(s, $""); // Infers `string` for `T` because of the `T?` parameter, not `string?`, as flow analysis does not consider the unannotated `T` parameter of the constructor void M(T? t, [InterpolatedStringHandlerArgument("s1")] CustomHandler c) { } [InterpolatedStringHandler] public partial struct CustomHandler { public CustomHandler(int literalLength, int formattedCount, T t) : this() { } } ``` ## Other considerations ### Allow `string` types to be convertible to handlers as well For type author simplicity, we could consider allowing expressions of type `string` to be implicitly-convertible to _applicable\_interpolated\_string\_handler\_types_. As proposed today, authors will likely need to overload on both that handler type and regular `string` types, so their users don't have to understand the difference. This may be an annoying and non-obvious overhead, as a `string` expression can be viewed as an interpolation with `expression.Length` prefilled length and 0 holes to be filled. This would allow new APIs to only expose a handler, without also having to expose a `string`-accepting overload. However, it won't get around the need for changes to better conversion from expression, so while it would work it may be unnecessary overhead. _Answer_: We think that this could end up being confusing, and there's an easy workaround for custom handler types: add a user-defined conversion from string. ### Incorporating spans for heap-less strings `ValueStringBuilder` as it exists today has 2 constructors: one that takes a count, and allocates on the heap eagerly, and one that takes a `Span`. That `Span` is usually a fixed size in the runtime codebase, around 250 elements on average. To truly replace that type, we should consider an extension to this where we also recognize `GetInterpolatedString` methods that take a `Span`, instead of just the count version. However, we see a few potential thorny cases to resolve here: * We don't want to stackalloc repeatedly in a hot loop. If we were to do this extension to the feature, we'd likely want to share the stackalloc'd span between loop iterations. We know this is safe, as `Span` is a ref struct that can't be stored on the heap, and users would have to be pretty devious to manage to extract a reference to that `Span` (such as creating a method that accepts such a handler then deliberately retrieving the `Span` from the handler and returning it to the caller). However, allocating ahead of time produces other questions: * Should we eagerly stackalloc? What if the loop is never entered, or exits before it needs the space? * If we don't eagerly stackalloc, does that mean we introduce a hidden branch on every loop? Most loops likely won't care about this, but it could affect some tight loops that don't want to pay the cost. * Some strings can be quite big, and the appropriate amount to `stackalloc` is dependent on a number of factors, including runtime factors. We don't really want the C# compiler and specification to have to determine this ahead of time, so we'd want to resolve https://github.com/dotnet/runtime/issues/25423 and add an API for the compiler to call in these cases. It also adds more pros and cons to the points from the previous loop, where we don't want to potentially allocate large arrays on the heap many times or before one is needed. _Answer_: This is out of scope for C# 10. We can look at this in general when we look at the more general `params Span` feature. ### Non-try version of the API For simplicity, this spec currently just proposes recognizing a `Append...` method, and things that always succeed (like `InterpolatedStringHandler`) would always return true from the method. This was done to support partial formatting scenarios where the user wants to stop formatting if an error occurs or if it's unnecessary, such as the logging case, but could potentially introduce a bunch of unnecessary branches in standard interpolated string usage. We could consider an addendum where we use just `FormatX` methods if no `Append...` method is present, but it does present questions about what we do if there's a mix of both `Append...` and `FormatX` calls. _Answer_: We want the non-try version of the API. The proposal has been updated to reflect this. ### Passing previous arguments to the handler There is unfortunate lack of symmetry in the proposal at it currently exists: invoking an extension method in reduced form produces different semantics than invoking the extension method in normal form. This is different from most other locations in the language, where reduced form is just a sugar. We propose adding an attribute to the framework that we will recognize when binding a method, that informs the compiler that certain parameters should be passed to the constructor on the handler. Usage looks like this: ```cs namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute { public InterpolatedStringHandlerArgumentAttribute(string argument); public InterpolatedStringHandlerArgumentAttribute(params string[] arguments); public string[] Arguments { get; } } } ``` Usage of this is then: ```cs namespace System { public sealed class String { public static string Format(IFormatProvider? provider, [InterpolatedStringHandlerArgument("provider")] ref DefaultInterpolatedStringHandler handler); … } } namespace System.Runtime.CompilerServices { public ref struct DefaultInterpolatedStringHandler { public DefaultInterpolatedStringHandler(int baseLength, int holeCount, IFormatProvider? provider); // additional factory … } } var formatted = string.Format(CultureInfo.InvariantCulture, $"{X} = {Y}"); // Is lowered to var tmp1 = CultureInfo.InvariantCulture; var handler = new DefaultInterpolatedStringHandler(3, 2, tmp1); handler.AppendFormatted(X); handler.AppendLiteral(" = "); handler.AppendFormatted(Y); var formatted = string.Format(tmp1, handler); ``` The questions we need to answer: 1. Do we like this pattern in general? 2. Do we want to allow these arguments to come from after the handler parameter? Some existing patterns in the BCL, such as `Utf8Formatter`, put the value to be formatted _before_ the thing needed to format into. To fit in best with these patterns, we'd likely want to allow this, but we need to decide if this out-of-order evaluate is ok. _Answer_: We want to support this. The spec has been updated to reflect this. Arguments will be required to be specified in lexical order at the call site, and if a needed argument to the create method is specified after the interpolated string literal, an error is produced. ### `await` usage in interpolation holes Because `$"{await A()}"` is a valid expression today, we need to rationalize interpolation holes with await. We could solve this with a few rules: 1. If an interpolated string used as a `string`, `IFormattable`, or `FormattableString` has an `await` in an interpolation hole, fall back to old-style formatter. 2. If an interpolated string is subject to an _implicit\_string\_handler\_conversion_ and _applicable\_interpolated\_string\_handler\_type_ is a `ref struct`, `await` is not allowed to be used in the format holes. Fundamentally, this desugaring could use a ref struct in an async method as long as we guarantee that the `ref struct` will not need to be saved to the heap, which should be possible if we forbid `await`s in the interpolation holes. Alternatively, we could simply make all handler types non-ref structs, including the framework handler for interpolated strings. This would, however, preclude us from someday recognizing a `Span` version that does not need to allocate any scratch space at all. _Answer_: We will treat interpolated string handlers the same as any other type: this means that if the handler type is a ref struct and the current context doesn't allow the usage of ref structs, it is illegal to use handler here. The spec around lowering of string literals used as strings is intentionally vague to allow the compiler to decide on what rules it deems appropriate, but for custom handler types they will have to follow the same rules as the rest of the language. ### Handlers as ref parameters Some handlers might want to be passed as ref parameters (either `in` or `ref`). Should we allow either? And if so, what will a `ref` handler look like? `ref $""` is confusing, as you're not actually passing the string by ref, you're passing the handler that is created from the ref by ref, and has similar potential issues with async methods. _Answer_: We want to support this. The spec has been updated to reflect this. The rules should reflect the same rules that apply to extension methods on value types. ### Interpolated strings through binary expressions and conversions Because this proposal makes interpolated strings context sensitive, we would like to allow the compiler to treat a binary expression composed entirely of interpolated strings, or an interpolated string subjected to a cast, as an interpolated string literal for the purposes of overload resolution. For example, take the following scenario: ```cs struct Handler1 { public Handler1(int literalLength, int formattedCount, C c) => ...; // AppendX... methods as necessary } struct Handler2 { public Handler2(int literalLength, int formattedCount, C c) => ...; // AppendX... methods as necessary } class C { void M(Handler1 handler) => ...; void M(Handler2 handler) => ...; } c.M($"{X}"); // Ambiguous between the M overloads ``` This would be ambiguous, necessitating a cast to either `Handler1` or `Handler2` in order to resolve. However, in making that cast, we would potentially throw away the information that there is context from the method receiver, meaning that the cast would fail because there is nothing to fill in the information of `c`. A similar issue arises with binary concatenation of strings: the user could want to format the literal across several lines to avoid line wrapping, but would not be able to because that would no longer be an interpolated string literal convertible to the handler type. To resolve these cases, we make the following changes: * An _additive\_expression_ composed entirely of _interpolated\_string\_expressions_ and using only `+` operators is considered to be an _interpolated\_string\_literal_ for the purposes of conversions and overload resolution. The final interpolated string is created by logically concatinating all individual _interpolated\_string\_expression_ components, from left to right. * A _cast\_expression_ or a _relational\_expression_ with operator `as` whose operand is an _interpolated\_string\_expressions_ is considered an _interpolated\_string\_expressions_ for the purposes of conversions and overload resolution. **Open Questions**: Do we want to do this? We don't do this for `System.FormattableString`, for example, but that can be broken out onto a different line, whereas this can be context-dependent and therefore not able to be broken out into a different line. There are also no overload resolution concerns with `FormattableString` and `IFormattable`. _Answer_: We think that this is a valid use case for additive expressions, but that the cast version is not compelling enough at this time. We can add it later if necessary. The spec has been updated to reflect this decision. ## Other use cases See https://github.com/dotnet/runtime/issues/50635 for examples of proposed handler APIs using this pattern. ================================================ FILE: proposals/csharp-10.0/lambda-improvements.md ================================================ # Lambda improvements [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Proposed changes: 1. Allow lambdas with attributes 2. Allow lambdas with explicit return type 3. Infer a natural delegate type for lambdas and method groups ## Motivation Support for attributes on lambdas would provide parity with methods and local functions. Support for explicit return types would provide symmetry with lambda parameters where explicit types can be specified. Allowing explicit return types would also provide control over compiler performance in nested lambdas where overload resolution must bind the lambda body currently to determine the signature. A natural type for lambda expressions and method groups will allow more scenarios where lambdas and method groups may be used without an explicit delegate type, including as initializers in `var` declarations. Requiring explicit delegate types for lambdas and method groups has been a friction point for customers, and has become an impediment to progress in ASP.NET with recent work on [MapAction](https://github.com/dotnet/aspnetcore/pull/29878). [ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) without proposed changes (`MapAction()` takes a `System.Delegate` argument): ```csharp [HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name"); app.MapAction((Func)GetTodo); [HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo; app.MapAction((Func)PostTodo); ``` [ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) with natural types for method groups: ```csharp [HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name"); app.MapAction(GetTodo); [HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo; app.MapAction(PostTodo); ``` [ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) with attributes and natural types for lambda expressions: ```csharp app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name")); app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo); ``` ## Attributes Attributes may be added to lambda expressions and lambda parameters. To avoid ambiguity between method attributes and parameter attributes, a lambda expression with attributes must use a parenthesized parameter list. Parameter types are not required. ```csharp f = [A] () => { }; // [A] lambda f = [return:A] x => x; // syntax error at '=>' f = [return:A] (x) => x; // [A] lambda f = [A] static x => x; // syntax error at '=>' f = ([A] x) => x; // [A] x f = ([A] ref int x) => x; // [A] x ``` Multiple attributes may be specified, either comma-separated within the same attribute list or as separate attribute lists. ```csharp var f = [A1, A2][A3] () => { }; // ok var g = ([A1][A2, A3] int x) => x; // ok ``` Attributes are not supported for _anonymous methods_ declared with `delegate { }` syntax. ```csharp f = [A] delegate { return 1; }; // syntax error at 'delegate' f = delegate ([A] int x) { return x; }; // syntax error at '[' ``` The parser will look ahead to differentiate a collection initializer with an element assignment from a collection initializer with a lambda expression. ```csharp var y = new C { [A] = x }; // ok: y[A] = x var z = new C { [A] x => x }; // ok: z[0] = [A] x => x ``` The parser will treat `?[` as the start of a conditional element access. ```csharp x = b ? [A]; // ok y = b ? [A] () => { } : z; // syntax error at '(' ``` Attributes on the lambda expression or lambda parameters will be emitted to metadata on the method that maps to the lambda. In general, customers should not depend on how lambda expressions and local functions map from source to metadata. How lambdas and local functions are emitted can, and has, changed between compiler versions. The changes proposed here are targeted at the `Delegate` driven scenario. It should be valid to inspect the `MethodInfo` associated with a `Delegate` instance to determine the signature of the lambda expression or local function including any explicit attributes and additional metadata emitted by the compiler such as default parameters. This allows teams such as ASP.NET to make available the same behaviors for lambdas and local functions as ordinary methods. ## Explicit return type An explicit return type may be specified before the parenthesized parameter list. ```csharp f = T () => default; // ok f = short x => 1; // syntax error at '=>' f = ref int (ref int x) => ref x; // ok f = static void (_) => { }; // ok f = async async (async async) => async; // ok? ``` The parser will look ahead to differentiate a method call `T()` from a lambda expression `T () => e`. Explicit return types are not supported for anonymous methods declared with `delegate { }` syntax. ```csharp f = delegate int { return 1; }; // syntax error f = delegate int (int x) { return x; }; // syntax error ``` Method type inference should make an exact inference from an explicit lambda return type. ```csharp static void F(Func f) { ... } F(int (i) => i); // Func ``` Variance conversions are not allowed from lambda return type to delegate return type (matching similar behavior for parameter types). ```csharp Func f1 = string () => null; // error Func f2 = object () => x; // warning ``` The parser allows lambda expressions with `ref` return types within expressions without additional parentheses. ```csharp d = ref int () => x; // d = (ref int () => x) F(ref int () => x); // F((ref int () => x)) ``` `var` cannot be used as an explicit return type for lambda expressions. ```csharp class var { } d = var (var v) => v; // error: contextual keyword 'var' cannot be used as explicit lambda return type d = @var (var v) => v; // ok d = ref var (ref var v) => ref v; // error: contextual keyword 'var' cannot be used as explicit lambda return type d = ref @var (ref var v) => ref v; // ok ``` ## Natural (function) type An _anonymous function_ expression ([§12.19](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1219-anonymous-function-expressions)) (a _lambda expression_ or an _anonymous method_) has a natural type if the parameters types are explicit and the return type is either explicit or can be inferred (see [§12.6.3.13](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#116313-inferred-return-type)). A _method group_ has a natural type if all candidate methods in the method group have a common signature. (If the method group may include extension methods, the candidates include the containing type and all extension method scopes.) The natural type of an anonymous function expression or method group is a _function_type_. A _function_type_ represents a method signature: the parameter types and ref kinds, and return type and ref kind. Anonymous function expressions or method groups with the same signature have the same _function_type_. _Function_types_ are used in a few specific contexts only: - implicit and explicit conversions - method type inference ([§12.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1263-type-inference)) and best common type ([§12.6.3.15](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) - `var` initializers A _function_type_ exists at compile time only: _function_types_ do not appear in source or metadata. ### Conversions From a _function_type_ `F` there are implicit _function_type_ conversions: - To a _function_type_ `G` if the parameters and return types of `F` are variance-convertible to the parameters and return type of `G` - To `System.MulticastDelegate` or base classes or interfaces of `System.MulticastDelegate` - To `System.Linq.Expressions.Expression` or `System.Linq.Expressions.LambdaExpression` Anonymous function expressions and method groups already have _conversions from expression_ to delegate types and expression tree types (see anonymous function conversions [§10.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#107-anonymous-function-conversions) and method group conversions [§10.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#108-method-group-conversions)). Those conversions are sufficient for converting to strongly-typed delegate types and expression tree types. The _function_type_ conversions above add _conversions from type_ to the base types only: `System.MulticastDelegate`, `System.Linq.Expressions.Expression`, etc. There are no conversions to a _function_type_ from a type other than a _function_type_. There are no explicit conversions for _function_types_ since _function_types_ cannot be referenced in source. A conversion to `System.MulticastDelegate` or base type or interface realizes the anonymous function or method group as an instance of an appropriate delegate type. A conversion to `System.Linq.Expressions.Expression` or base type realizes the lambda expression as an expression tree with an appropriate delegate type. ```csharp Delegate d = delegate (object obj) { }; // Action Expression e = () => ""; // Expression> object o = "".Clone; // Func ``` _Function_type_ conversions are not implicit or explicit standard conversions [§10.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#104-standard-conversions) and are not considered when determining whether a user-defined conversion operator is applicable to an anonymous function or method group. From evaluation of user defined conversions [§10.5.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1053-evaluation-of-user-defined-conversions): > For a conversion operator to be applicable, it must be possible to perform a standard conversion ([§10.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#104-standard-conversions)) from the source type to the operand type of the operator, and it must be possible to perform a standard conversion from the result type of the operator to the target type. ```csharp class C { public static implicit operator C(Delegate d) { ... } } C c; c = () => 1; // error: cannot convert lambda expression to type 'C' c = (C)(() => 2); // error: cannot convert lambda expression to type 'C' ``` A warning is reported for an implicit conversion of a method group to `object`, since the conversion is valid but perhaps unintentional. ```csharp Random r = new Random(); object obj; obj = r.NextDouble; // warning: Converting method group to 'object'. Did you intend to invoke the method? obj = (object)r.NextDouble; // ok ``` ### Type inference The existing rules for type inference are mostly unchanged (see [§12.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1263-type-inference)). There are however a **couple of changes** below to specific phases of type inference. #### First phase The first phase ([§12.6.3.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12632-the-first-phase)) allows an anonymous function to bind to `Ti` even if `Ti` is not a delegate or expression tree type (perhaps a type parameter constrained to `System.Delegate` for instance). > For each of the method arguments `Ei`: > > * If `Ei` is an anonymous function **and `Ti` is a delegate type or expression tree type**, an *explicit parameter type inference* is made from `Ei` to `Ti` **and an *explicit return type inference* is made from `Ei` to `Ti`.** > * Otherwise, if `Ei` has a type `U` and `xi` is a value parameter then a *lower-bound inference* is made *from* `U` *to* `Ti`. > * Otherwise, if `Ei` has a type `U` and `xi` is a `ref` or `out` parameter then an *exact inference* is made *from* `U` *to* `Ti`. > * Otherwise, no inference is made for this argument. > #### **Explicit return type inference** > > **An *explicit return type inference* is made *from* an expression `E` *to* a type `T` in the following way:** > > * **If `E` is an anonymous function with explicit return type `Ur` and `T` is a delegate type or expression tree type with return type `Vr` then an *exact inference* ([§12.6.3.9](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12639-exact-inferences)) is made *from* `Ur` *to* `Vr`.** #### Fixing Fixing ([§12.6.3.12](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#126312-fixing)) ensures other conversions are preferred over _function_type_ conversions. (Lambda expressions and method group expressions only contribute to lower bounds so handling of _function_types_ is needed for lower bounds only.) > An *unfixed* type variable `Xi` with a set of bounds is *fixed* as follows: > > * The set of *candidate types* `Uj` starts out as the set of all types in the set of bounds for `Xi` **where function types are ignored in lower bounds if there any types that are not function types**. > * We then examine each bound for `Xi` in turn: For each exact bound `U` of `Xi` all types `Uj` which are not identical to `U` are removed from the candidate set. For each lower bound `U` of `Xi` all types `Uj` to which there is *not* an implicit conversion from `U` are removed from the candidate set. For each upper bound `U` of `Xi` all types `Uj` from which there is *not* an implicit conversion to `U` are removed from the candidate set. > * If among the remaining candidate types `Uj` there is a unique type `V` from which there is an implicit conversion to all the other candidate types, then `Xi` is fixed to `V`. > * Otherwise, type inference fails. ### Best common type Best common type ([§12.6.3.15](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions)) is defined in terms of type inference so the type inference changes above apply to best common type as well. ```csharp var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func[] ``` ### `var` Anonymous functions and method groups with function types can be used as initializers in `var` declarations. ```csharp var f1 = () => default; // error: cannot infer type var f2 = x => x; // error: cannot infer type var f3 = () => 1; // System.Func var f4 = string () => null; // System.Func var f5 = delegate (object o) { }; // System.Action static void F1() { } static void F1(this T t) { } static void F2(this string s) { } var f6 = F1; // error: multiple methods var f7 = "".F1; // error: the delegate type could not be inferred var f8 = F2; // System.Action ``` Function types are not used in assignments to discards. ```csharp d = () => 0; // ok _ = () => 1; // error ``` ### Delegate types The delegate type for the anonymous function or method group with parameter types `P1, ..., Pn` and return type `R` is: - if any parameter or return value is not by value, or there are more than 16 parameters, or any of the parameter types or return are not valid type arguments (say, `(int* p) => { }`), then the delegate is a synthesized `internal` anonymous delegate type with signature that matches the anonymous function or method group, and with parameter names `arg1, ..., argn` or `arg` if a single parameter; - if `R` is `void`, then the delegate type is `System.Action`; - otherwise the delegate type is `System.Func`. The compiler may allow more signatures to bind to `System.Action<>` and `System.Func<>` types in the future (if `ref struct` types are allowed type arguments for instance). `modopt()` or `modreq()` in the method group signature are ignored in the corresponding delegate type. If two anonymous functions or method groups in the same compilation require synthesized delegate types with the same parameter types and modifiers and the same return type and modifiers, the compiler will use the same synthesized delegate type. ### Overload resolution Better function member ([§12.6.4.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12643-better-function-member)) is updated to prefer members where none of the conversions and none of the type arguments involved inferred types from lambda expressions or method groups. > #### Better function member > ... > Given an argument list `A` with a set of argument expressions `{E1, E2, ..., En}` and two applicable function members `Mp` and `Mq` with parameter types `{P1, P2, ..., Pn}` and `{Q1, Q2, ..., Qn}`, `Mp` is defined to be a ***better function member*** than `Mq` if > > 1. **for each argument, the implicit conversion from `Ex` to `Px` is not a _function_type_conversion_, and** > * **`Mp` is a non-generic method or `Mp` is a generic method with type parameters `{X1, X2, ..., Xp}` and for each type parameter `Xi` the type argument is inferred from an expression or from a type other than a _function_type_, and** > * **for at least one argument, the implicit conversion from `Ex` to `Qx` is a _function_type_conversion_, or `Mq` is a generic method with type parameters `{Y1, Y2, ..., Yq}` and for at least one type parameter `Yi` the type argument is inferred from a _function_type_, or** > 2. for each argument, the implicit conversion from `Ex` to `Qx` is not better than the implicit conversion from `Ex` to `Px`, and for at least one argument, the conversion from `Ex` to `Px` is better than the conversion from `Ex` to `Qx`. Better conversion from expression ([§12.6.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12644-better-conversion-from-expression)) is updated to prefer conversions that did not involve inferred types from lambda expressions or method groups. > #### Better conversion from expression > > Given an implicit conversion `C1` that converts from an expression `E` to a type `T1`, and an implicit conversion `C2` that converts from an expression `E` to a type `T2`, `C1` is a ***better conversion*** than `C2` if: > 1. **`C1` is not a _function_type_conversion_ and `C2` is a _function_type_conversion_, or** > 2. `E` is a non-constant _interpolated\_string\_expression_, `C1` is an _implicit\_string\_handler\_conversion_, `T1` is an _applicable\_interpolated\_string\_handler\_type_, and `C2` is not an _implicit\_string\_handler\_conversion_, or > 3. `E` does not exactly match `T2` and at least one of the following holds: > * `E` exactly matches `T1` ([§12.6.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12645-better-conversion-from-expression)) > * `T1` is a better conversion target than `T2` ([§12.6.4.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12647-better-conversion-target)) ## Syntax ```antlr lambda_expression : modifier* identifier '=>' (block | expression) | attribute_list* modifier* type? lambda_parameters '=>' (block | expression) ; lambda_parameters : lambda_parameter | '(' (lambda_parameter (',' lambda_parameter)*)? ')' ; lambda_parameter : identifier | attribute_list* modifier* type? identifier equals_value_clause? ; ``` ## Open issues Should default values be supported for lambda expression parameters for completeness? Should `System.Diagnostics.ConditionalAttribute` be disallowed on lambda expressions since there are few scenarios where a lambda expression could be used conditionally? ```csharp ([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok? ``` Should the _function_type_ be available from the compiler API, in addition to the resulting delegate type? Currently, the inferred delegate type uses `System.Action<>` or `System.Func<>` when parameter and return types are valid type arguments _and_ there are no more than 16 parameters, and if the expected `Action<>` or `Func<>` type is missing, an error is reported. Instead, should the compiler use `System.Action<>` or `System.Func<>` regardless of arity? And if the expected type is missing, synthesize a delegate type otherwise? ================================================ FILE: proposals/csharp-10.0/parameterless-struct-constructors.md ================================================ # Parameterless struct constructors [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Support parameterless constructors and instance field initializers for struct types. ## Motivation Explicit parameterless constructors would give more control over minimally constructed instances of the struct type. Instance field initializers would allow simplified initialization across multiple constructors. Together these would close an obvious gap between `struct` and `class` declarations. Support for field initializers would also allow initialization of fields in `record struct` declarations without explicitly implementing the primary constructor. ```csharp record struct Person(string Name) { public object Id { get; init; } = GetNextId(); } ``` If struct field initializers are supported for constructors with parameters, it seems natural to extend that to parameterless constructors as well. ```csharp record struct Person() { public string Name { get; init; } public object Id { get; init; } = GetNextId(); } ``` ## Proposal ### Instance field initializers Instance field declarations for a struct may include initializers. As with class field initializers [§15.5.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15563-instance-field-initialization): > A variable initializer for an instance field cannot reference the instance being created. An error is reported if a struct has field initializers and no declared instance constructors since the field initializers will not be run. ```csharp struct S { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor ``` ### Constructors A struct may declare a parameterless instance constructor. A parameterless instance constructor is valid for all struct kinds including `struct`, `readonly struct`, `ref struct`, and `record struct`. If no parameterless instance constructor is declared, the struct (see [§16.4.9](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#1649-constructors)) ... > implicitly has a parameterless instance constructor which always returns the value that results from setting all value type fields to their default value and all reference type fields to null. ### Modifiers A parameterless instance struct constructor must be declared `public`. ```csharp struct S0 { } // ok struct S1 { public S1() { } } // ok struct S2 { internal S2() { } } // error: parameterless constructor must be 'public' ``` Non-public constructors are ignored when importing types from metadata. Constructors can be declared `extern` or `unsafe`. Constructors cannot be `partial`. ### Executing field initializers _Instance variable initializers_ ([§15.11.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15113-instance-variable-initializers)) is **modified** as follows: > When **a class** instance constructor has no constructor initializer, or it has a constructor initializer of the form `base(...)`, that constructor implicitly performs the initializations specified by the *variable_initializer*s of the instance fields declared in its class. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor and before the implicit invocation of the direct base class constructor. > > **When a struct instance constructor has no constructor initializer, that constructor implicitly performs the initializations specified by the *variable_initializer*s of the instance fields declared in its struct. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor.** > > **When a struct instance constructor has a `this()` constructor initializer that represents the _default parameterless constructor_, the declared constructor implicitly clears all instance fields and performs the initializations specified by the *variable_initializer*s of the instance fields declared in its struct. Immediately upon entry to the constructor, all value type fields are set to their default value and all reference type fields are set to `null`. Immediately after that, a sequence of assignments corresponding to the *variable_initializer*s are executed.** ### Definite assignment Instance fields (other than `fixed` fields) must be definitely assigned in struct instance constructors that do not have a `this()` initializer (see [§16.4.9](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#1649-constructors)). ```csharp struct S0 // ok { int x; object y; } struct S1 // error: 'struct' with field initializers must include an explicitly declared constructor { int x = 1; object y; } struct S2 { int x = 1; object y; public S2() { } // error in C# 10 (valid starting in C# 11): field 'y' must be assigned } struct S3 // ok { int x = 1; object y; public S3() { y = 2; } } ``` ### No `base()` initializer A `base()` initializer is disallowed in struct constructors. The compiler will not emit a call to the base `System.ValueType` constructor from struct instance constructors. ### `record struct` An error is reported if a `record struct` has field initializers and does not contain a primary constructor nor any instance constructors since the field initializers will not be run. ```csharp record struct R0; // ok record struct R1 { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor record struct R2() { int F = 42; } // ok record struct R3(int F); // ok ``` A `record struct` with an empty parameter list will have a parameterless primary constructor. ```csharp record struct R3(); // primary .ctor: public R3() { } record struct R4() { int F = 42; } // primary .ctor: public R4() { F = 42; } ``` An explicit parameterless constructor in a `record struct` must have a `this` initializer that calls the primary constructor or an explicitly declared constructor. ```csharp record struct R5(int F) { public R5() { } // error: must have 'this' initializer that calls explicit .ctor public R5(object o) : this() { } // ok public int F = F; } ``` ### Fields The implicitly-defined parameterless constructor will zero fields rather than calling any parameterless constructors for the field types. No warnings are reported that field constructors are ignored. _No change from C#9._ ```csharp struct S0 { public S0() { } } struct S1 { S0 F; // S0 constructor ignored } struct S where T : struct { T F; // constructor (if any) ignored } ``` ### `default` expression `default` ignores the parameterless constructor and generates a zeroed instance. _No change from C#9._ ```csharp // struct S { public S() { } } _ = default(S); // constructor ignored, no warning ``` ### `new()` Object creation invokes the parameterless constructor if public; otherwise the instance is zeroed. _No change from C#9._ ```csharp // public struct PublicConstructor { public PublicConstructor() { } } // public struct PrivateConstructor { private PrivateConstructor() { } } _ = new PublicConstructor(); // call PublicConstructor::.ctor() _ = new PrivateConstructor(); // initobj PrivateConstructor ``` A warning wave may report a warning for use of `new()` with a struct type that has constructors but no parameterless constructor. No warning will be reported when using substituting such a struct type for a type parameter with a `new()` or `struct` constraint. ```csharp struct S { public S(int i) { } } static T CreateNew() where T : new() => new T(); _ = new S(); // no warning called _ = CreateNew(); // ok ``` ### Uninitialized values A local or field of a struct type that is not explicitly initialized is zeroed. The compiler reports a definite assignment error for an uninitialized struct that is not empty. _No change from C#9._ ```csharp NoConstructor s1; PublicConstructor s2; s1.ToString(); // error: use of unassigned local (unless type is empty) s2.ToString(); // error: use of unassigned local (unless type is empty) ``` ### Array allocation Array allocation ignores any parameterless constructor and generates zeroed elements. _No change from C#9._ ```csharp // struct S { public S() { } } var a = new S[1]; // constructor ignored, no warning ``` ### Parameter default value `new()` A parameter default value of `new()` binds to the parameterless constructor if public (and reports an error that the value is not constant); otherwise the instance is zeroed. _No change from C#9._ ```csharp // public struct PublicConstructor { public PublicConstructor() { } } // public struct PrivateConstructor { private PrivateConstructor() { } } static void F1(PublicConstructor s1 = new()) { } // error: default value must be constant static void F2(PrivateConstructor s2 = new()) { } // ok: initobj ``` ### Type parameter constraints: `new()` and `struct` The `new()` and `struct` type parameter constraints require the parameterless constructor to be `public` if defined (see Satisfying constraints - [§8.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#845-satisfying-constraints)). The compiler assumes all structs satisfy `new()` and `struct` constraints. _No change from C#9._ ```csharp // public struct PublicConstructor { public PublicConstructor() { } } // public struct InternalConstructor { internal InternalConstructor() { } } static T CreateNew() where T : new() => new T(); static T CreateStruct() where T : struct => new T(); _ = CreateNew(); // ok _ = CreateStruct(); // ok _ = CreateNew(); // compiles; may fail at runtime _ = CreateStruct(); // compiles; may fail at runtime ``` `new T()` is emitted as a call to `System.Activator.CreateInstance()`, and the compiler assumes the implementation of `CreateInstance()` invokes the `public` parameterless constructor if defined. _With .NET Framework, `Activator.CreateInstance()` invokes the parameterless constructor if the constraint is `where T : new()` but appears to ignore the parameterless constructor if the constraint is `where T : struct`._ ### Optional parameters Constructors with optional parameters are not considered parameterless constructors. _No change from C#9._ ```csharp struct S1 { public S1(string s = "") { } } struct S2 { public S2(params object[] args) { } } _ = new S1(); // ok: ignores constructor _ = new S2(); // ok: ignores constructor ``` ### Metadata Explicit parameterless struct instance constructors will be emitted to metadata. Public parameterless struct instance constructors will be imported from metadata; non-public struct instance constructors will be ignored. _No change from C#9._ ## See also - https://github.com/dotnet/roslyn/issues/1029 ## Design meetings - https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-28.md#open-questions-in-record-and-parameterless-structs - https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-10.md#parameterless-struct-constructors - https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-27.md#field-initializers ================================================ FILE: proposals/csharp-10.0/record-structs.md ================================================ # Record structs [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: The syntax for a record struct is as follows: ```antlr record_struct_declaration : attributes? struct_modifier* 'partial'? 'record' 'struct' identifier type_parameter_list? parameter_list? struct_interfaces? type_parameter_constraints_clause* record_struct_body ; record_struct_body : struct_body | ';' ; ``` Record struct types are value types, like other struct types. They implicitly inherit from the class `System.ValueType`. The modifiers and members of a record struct are subject to the same restrictions as those of structs (accessibility on type, modifiers on members, `base(...)` instance constructor initializers, definite assignment for `this` in constructor, destructors, ...). Record structs will also follow the same rules as structs for parameterless instance constructors and field initializers, but this document assumes that we will lift those restrictions for structs generally. See [§16.4.9](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#1649-constructors) See [parameterless struct constructors](./parameterless-struct-constructors.md) spec. Record structs cannot use `ref` modifier. At most one partial type declaration of a partial record struct may provide a `parameter_list`. The `parameter_list` may be empty. Record struct parameters cannot use `ref`, `out` or `this` modifiers (but `in` and `params` are allowed). ## Members of a record struct In addition to the members declared in the record struct body, a record struct type has additional synthesized members. Members are synthesized unless a member with a "matching" signature is declared in the record struct body or an accessible concrete non-virtual member with a "matching" signature is inherited. Two members are considered matching if they have the same signature or would be considered "hiding" in an inheritance scenario. See Signatures and overloading [§7.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#76-signatures-and-overloading). It is an error for a member of a record struct to be named "Clone". It is an error for an instance field of a record struct to have an unsafe type. A record struct is not permitted to declare a destructor. The synthesized members are as follows: ### Equality members The synthesized equality members are similar as in a record class (`Equals` for this type, `Equals` for `object` type, `==` and `!=` operators for this type),\ except for the lack of `EqualityContract`, null checks or inheritance. The record struct implements `System.IEquatable` and includes a synthesized strongly-typed overload of `Equals(R other)` where `R` is the record struct. The method is `public`. The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility. If `Equals(R other)` is user-defined (not synthesized) but `GetHashCode` is not, a warning is produced. ```C# public readonly bool Equals(R other); ``` The synthesized `Equals(R)` returns `true` if and only if for each instance field `fieldN` in the record struct the value of `System.Collections.Generic.EqualityComparer.Default.Equals(fieldN, other.fieldN)` where `TN` is the field type is `true`. The record struct includes synthesized `==` and `!=` operators equivalent to operators declared as follows: ```C# public static bool operator==(R r1, R r2) => r1.Equals(r2); public static bool operator!=(R r1, R r2) => !(r1 == r2); ``` The `Equals` method called by the `==` operator is the `Equals(R other)` method specified above. The `!=` operator delegates to the `==` operator. It is an error if the operators are declared explicitly. The record struct includes a synthesized override equivalent to a method declared as follows: ```C# public override readonly bool Equals(object? obj); ``` It is an error if the override is declared explicitly. The synthesized override returns `other is R temp && Equals(temp)` where `R` is the record struct. The record struct includes a synthesized override equivalent to a method declared as follows: ```C# public override readonly int GetHashCode(); ``` The method can be declared explicitly. A warning is reported if one of `Equals(R)` and `GetHashCode()` is explicitly declared but the other method is not explicit. The synthesized override of `GetHashCode()` returns an `int` result of combining the values of `System.Collections.Generic.EqualityComparer.Default.GetHashCode(fieldN)` for each instance field `fieldN` with `TN` being the type of `fieldN`. For example, consider the following record struct: ```C# record struct R1(T1 P1, T2 P2); ``` For this record struct, the synthesized equality members would be something like: ```C# struct R1 : IEquatable { public T1 P1 { get; set; } public T2 P2 { get; set; } public override bool Equals(object? obj) => obj is R1 temp && Equals(temp); public bool Equals(R1 other) { return EqualityComparer.Default.Equals(P1, other.P1) && EqualityComparer.Default.Equals(P2, other.P2); } public static bool operator==(R1 r1, R1 r2) => r1.Equals(r2); public static bool operator!=(R1 r1, R1 r2) => !(r1 == r2); public override int GetHashCode() { return Combine( EqualityComparer.Default.GetHashCode(P1), EqualityComparer.Default.GetHashCode(P2)); } } ``` ### Printing members: PrintMembers and ToString methods The record struct includes a synthesized method equivalent to a method declared as follows: ```C# private bool PrintMembers(System.Text.StringBuilder builder); ``` The method does the following: 1. for each of the record struct's printable members (non-static public field and readable property members), appends that member's name followed by " = " followed by the member's value separated with ", ", 2. return true if the record struct has printable members. For a member that has a value type, we will convert its value to a string representation using the most efficient method available to the target platform. At present that means calling `ToString` before passing to `StringBuilder.Append`. If the record's printable members do not include a readable property with a non-`readonly` `get` accessor, then the synthesized `PrintMembers` is `readonly`. There is no requirement for the record's fields to be `readonly` for the `PrintMembers` method to be `readonly`. The `PrintMembers` method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility. The record struct includes a synthesized method equivalent to a method declared as follows: ```C# public override string ToString(); ``` If the record struct's `PrintMembers` method is `readonly`, then the synthesized `ToString()` method is `readonly`. The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility. The synthesized method: 1. creates a `StringBuilder` instance, 2. appends the record struct name to the builder, followed by " { ", 3. invokes the record struct's `PrintMembers` method giving it the builder, followed by " " if it returned true, 4. appends "}", 5. returns the builder's contents with `builder.ToString()`. For example, consider the following record struct: ``` csharp record struct R1(T1 P1, T2 P2); ``` For this record struct, the synthesized printing members would be something like: ```C# struct R1 : IEquatable { public T1 P1 { get; set; } public T2 P2 { get; set; } private bool PrintMembers(StringBuilder builder) { builder.Append(nameof(P1)); builder.Append(" = "); builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if P1 has a value type builder.Append(", "); builder.Append(nameof(P2)); builder.Append(" = "); builder.Append(this.P2); // or builder.Append(this.P2.ToString()); if P2 has a value type return true; } public override string ToString() { var builder = new StringBuilder(); builder.Append(nameof(R1)); builder.Append(" { "); if (PrintMembers(builder)) builder.Append(" "); builder.Append("}"); return builder.ToString(); } } ``` ## Positional record struct members In addition to the above members, record structs with a parameter list ("positional records") synthesize additional members with the same conditions as the members above. ### Primary Constructor A record struct has a public constructor whose signature corresponds to the value parameters of the type declaration. This is called the primary constructor for the type. It is an error to have a primary constructor and a constructor with the same signature already present in the struct. If the type declaration does not include a parameter list, no primary constructor is generated. ```csharp record struct R1 { public R1() { } // ok } record struct R2() { public R2() { } // error: 'R2' already defines constructor with same parameter types } ``` Instance field declarations for a record struct are permitted to include variable initializers. If there is no primary constructor, the instance initializers execute as part of the parameterless constructor. Otherwise, at runtime the primary constructor executes the instance initializers appearing in the record-struct-body. If a record struct has a primary constructor, any user-defined constructor must have an explicit `this` constructor initializer that calls the primary constructor or an explicitly declared constructor. Parameters of the primary constructor as well as members of the record struct are in scope within initializers of instance fields or properties. Instance members would be an error in these locations (similar to how instance members are in scope in regular constructor initializers today, but an error to use), but the parameters of the primary constructor would be in scope and useable and would shadow members. Static members would also be useable. A warning is produced if a parameter of the primary constructor is not read. The definite assignment rules for struct instance constructors apply to the primary constructor of record structs. For instance, the following is an error: ```csharp record struct Pos(int X) // definite assignment error in primary constructor { private int x; public int X { get { return x; } set { x = value; } } = X; } ``` ### Properties For each record struct parameter of a record struct declaration there is a corresponding public property member whose name and type are taken from the value parameter declaration. For a record struct: * A public `get` and `init` auto-property is created if the record struct has `readonly` modifier, `get` and `set` otherwise. Both kinds of set accessors (`set` and `init`) are considered "matching". So the user may declare an init-only property in place of a synthesized mutable one. An inherited `abstract` property with matching type is overridden. No auto-property is created if the record struct has an instance field with expected name and type. It is an error if the inherited property does not have `public` `get` and `set`/`init` accessors. It is an error if the inherited property or field is hidden. The auto-property is initialized to the value of the corresponding primary constructor parameter. Attributes can be applied to the synthesized auto-property and its backing field by using `property:` or `field:` targets for attributes syntactically applied to the corresponding record struct parameter. ### Deconstruct A positional record struct with at least one parameter synthesizes a public void-returning instance method called `Deconstruct` with an out parameter declaration for each parameter of the primary constructor declaration. Each parameter of the Deconstruct method has the same type as the corresponding parameter of the primary constructor declaration. The body of the method assigns each parameter of the Deconstruct method to the value from an instance member access to a member of the same name. If the instance members accessed in the body do not include a property with a non-`readonly` `get` accessor, then the synthesized `Deconstruct` method is `readonly`. The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or is static. ## Allow `with` expression on structs It is now valid for the receiver in a `with` expression to have a struct type. On the right hand side of the `with` expression is a `member_initializer_list` with a sequence of assignments to *identifier*, which must be an accessible instance field or property of the receiver's type. For a receiver with struct type, the receiver is first copied, then each `member_initializer` is processed the same way as an assignment to a field or property access of the result of the conversion. Assignments are processed in lexical order. ## Improvements on records ### Allow `record class` The existing syntax for record types allows `record class` with the same meaning as `record`: ```antlr record_declaration : attributes? class_modifier* 'partial'? 'record' 'class'? identifier type_parameter_list? parameter_list? record_base? type_parameter_constraints_clause* record_body ; ``` ### Allow user-defined positional members to be fields See https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-05.md#changing-the-member-type-of-a-primary-constructor-parameter No auto-property is created if the record has or inherits an instance field with expected name and type. ## Allow parameterless constructors and member initializers in structs See [parameterless struct constructors](./parameterless-struct-constructors.md) spec. ## Open questions - how to recognize record structs in metadata? (we don't have an unspeakable clone method to leverage...) ### Answered - confirm that we want to keep PrintMembers design (separate method returning `bool`) (answer: yes) - confirm we won't allow `record ref struct` (issue with `IEquatable` and ref fields) (answer: yes) - confirm implementation of equality members. Alternative is that synthesized `bool Equals(R other)`, `bool Equals(object? other)` and operators all just delegate to `ValueType.Equals`. (answer: yes) - confirm that we want to allow field initializers when there is a primary constructor. Do we also want to allow parameterless struct constructors while we're at it (the Activator issue was apparently fixed)? (answer: yes, updated spec should be reviewed in LDM) - how much do we want to say about `Combine` method? (answer: as little as possible) - should we disallow a user-defined constructor with a copy constructor signature? (answer: no, there is no notion of copy constructor in the record structs spec) - confirm that we want to disallow members named "Clone". (answer: correct) - double-check that synthesized `Equals` logic is functionally equivalent to runtime implementation (e.g. float.NaN) (answer: confirmed in LDM) - could field- or property-targeting attributes be placed in the positional parameter list? (answer: yes, same as for record class) - `with` on generics? (answer: out of scope for C# 10) - should `GetHashCode` include a hash of the type itself, to get different values between `record struct S1;` and `record struct S2;`? (answer: no) ================================================ FILE: proposals/csharp-11.0/auto-default-structs.md ================================================ # Auto-default structs [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary This feature makes it so that in struct constructors, we identify fields which were not explicitly assigned by the user before returning or before use, and initialize them implicitly to `default` instead of giving definite assignment errors. ## Motivation This proposal is raised as a possible mitigation for usability issues found in dotnet/csharplang#5552 and dotnet/csharplang#5635, as well as addressing #5563 (all fields must be definitely assigned, but `field` is not accessible within the constructor). --- Since C# 1.0, struct constructors have been required to definitely assign `this` as if it were an `out` parameter. ```cs public struct S { public int x, y; public S() // error: Fields 'S.x' and 'S.y' must be fully assigned before control is returned to the caller { } } ``` This presents issues when setters are manually defined on semi-auto properties, since the compiler can't treat assignment of the property as equivalent to assignment of the backing field. ```cs public struct S { public int X { get => field; set => field = value; } public S() // error: struct fields aren't fully assigned. But caller can only assign 'this.field' by assigning 'this'. { } } ``` We assume that introducing finer-grained restrictions for setters, such as a scheme where the setter doesn't take `ref this` but rather takes `out field` as a parameter, is going to be too niche and incomplete for some use cases. One fundamental tension we are struggling with is that when struct properties have manually implemented setters, users often have to do some form of "repetition" of either repeatedly assigning or repeating their logic: ```cs struct S { private int _x; public int X { get => _x; set => _x = value >= 0 ? value : throw new ArgumentOutOfRangeException(); } // Solution 1: assign some value in the constructor before "really" assigning through the property setter. public S(int x) { _x = default; X = x; } // Solution 2: assign the field once in the constructor, repeating the implementation of the setter. public S(int x) { _x = x >= 0 ? x : throw new ArgumentOutOfRangeException(); } } ``` ## Previous discussion A small group has looked at this issue and considered a few possible solutions: 1. Require users to assign `this = default` when semi-auto properties have manually implemented setters. We agree this is the wrong solution since it blows away values set in field initializers. 2. Implicitly initialize all backing fields of auto/semi-auto properties. - This solves the "semi-auto property setters" problem, and it squarely places explicitly declared fields under different rules: "don't implicitly initialize my fields, but do implicitly initialize my auto-properties." 3. Provide a way to assign the backing field of a semi-auto property and require users to assign it. - This could be cumbersome compared to (2). An auto property is supposed to be "automatic", and perhaps that includes "automatic" initialization of the field. It could introduce confusion as to when the underlying field is being assigned by an assignment to the property, and when the property setter is being called. We've also received [feedback](https://github.com/dotnet/csharplang/discussions/5635) from users who want to, for example, include a few field initializers in structs without having to explicitly assign everything. We can solve this issue as well as the "semi-auto property with manually implemented setter" issue at the same time. ```cs struct MagnitudeVector3d { double X, Y, Z; double Magnitude = 1; public MagnitudeVector3d() // error: must assign 'X', 'Y', 'Z' before returning { } } ``` ## Adjusting definite assignment Instead of performing a definite assignment analysis to give errors for unassigned fields on `this`, we do it to determine *which fields need to be initialized implicitly*. Such initialization is inserted at the *beginning of the constructor*. ```cs struct S { int x, y; // Example 1 public S() { // ok. Compiler inserts an assignment of `this = default`. } // Example 2 public S() { // ok. Compiler inserts an assignment of `y = default`. x = 1; } // Example 3 public S() { // valid since C# 1.0. Compiler inserts no implicit assignments. x = 1; y = 2; } // Example 4 public S(bool b) { // ok. Compiler inserts assignment of `this = default`. if (b) x = 1; else y = 2; } // Example 5 void M() { } public S(bool b) { // ok. Compiler inserts assignment of `y = default`. x = 1; if (b) M(); y = 2; } } ``` In examples (4) and (5), the resulting codegen sometimes has "double assignments" of fields. This is generally fine, but for users who are concerned with such double assignments, we can emit what used to be definite assignment error diagnostics as *disabled-by-default* warning diagnostics. ```cs struct S { int x; public S() // warning: 'S.x' is implicitly initialized to 'default'. { } } ``` Users who set the severity of this diagnostic to "error" will opt in to the pre-C# 11 behavior. Such users are essentially "shut out" of semi-auto properties with manually implemented setters. ```cs struct S { public int X { get => field; set => field = field < value ? value : field; } public S() // error: backing field of 'S.X' is implicitly initialized to 'default'. { X = 1; } } ``` At first glance, this feels like a "hole" in the feature, but it's **actually the right thing to do**. By enabling the diagnostic, the user is telling us that they don't want the compiler to implicitly initialize their fields in the constructor. There's no way to avoid the implicit initialization here, so the solution for them is to use a different way of initializing the field than a manually implemented setter, such as manually declaring the field and assigning it, or by including a field initializer. Currently, the JIT does not eliminate dead stores through refs, which means that these implicit initializations do have a real cost. But that might be fixable. https://github.com/dotnet/runtime/issues/13727 It's worth noting that initializing individual fields instead of the entire instance is really just an optimization. The compiler should probably be free to implement whatever heuristic it wants, as long as it meets the invariant that fields which are not definitely assigned at all return points or before any non-field member access of `this` are implicitly initialized. For example, if a struct has 100 fields, and just one of them is explicitly initialized, it might make more sense to do an `initobj` on the entire thing, than to implicitly emit `initobj` for the 99 other fields. However, an implementation which implicitly emits `initobj` for the 99 other fields would still be valid. ## Changes to language specification We adjust the following section of the standard: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12814-this-access > If the constructor declaration has no constructor initializer, the `this` variable behaves exactly the same as an `out` parameter of the struct type. In particular, this means that the variable shall be definitely assigned in every execution path of the instance constructor. We adjust this language to read: If the constructor declaration has no constructor initializer, the `this` variable behaves similarly to an `out` parameter of the struct type, except that it is not an error when the definite assignment requirements ([§9.4.1](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#941-general)) are not met. Instead, we introduce the following behaviors: 1. When the `this` variable itself does not meet the requirements, then all unassigned instance variables within `this` at all points where requirements are violated are implicitly initialized to the default value ([§9.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#93-default-values)) in an *initialization* phase before any other code in the constructor runs. 2. When an instance variable *v* within `this` does not meet the requirements, or any instance variable at any level of nesting within *v* does not meet the requirements, then *v* is implicitly initialized to the default value in an *initialization* phase before any other code in the constructor runs. ## Design meetings https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-14.md#definite-assignment-in-structs ================================================ FILE: proposals/csharp-11.0/checked-user-defined-operators.md ================================================ # Checked user-defined operators [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary C# should support defining `checked` variants of the following user-defined operators so that users can opt into or out of overflow behavior as appropriate: * The `++` and `--` unary operators [§12.8.16](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators) and [§12.9.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators). * The `-` unary operator [§12.9.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1293-unary-minus-operator). * The `+`, `-`, `*`, and `/` binary operators [§12.10](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1210-arithmetic-operators). * Explicit conversion operators. ## Motivation [motivation]: #motivation There is no way for a user to declare a type and support both checked and unchecked versions of an operator. This will make it hard to port various algorithms to use the proposed `generic math` interfaces exposed by the libraries team. Likewise, this makes it impossible to expose a type such as `Int128` or `UInt128` without the language simultaneously shipping its own support to avoid breaking changes. ## Detailed design [design]: #detailed-design ### Syntax Grammar at operators ([§15.10](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1510-operators)) will be adjusted to allow `checked` keyword after the `operator` keyword right before the operator token: ```antlr overloadable_unary_operator : '+' | 'checked'? '-' | '!' | '~' | 'checked'? '++' | 'checked'? '--' | 'true' | 'false' ; overloadable_binary_operator : 'checked'? '+' | 'checked'? '-' | 'checked'? '*' | 'checked'? '/' | '%' | '&' | '|' | '^' | '<<' | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<=' ; conversion_operator_declarator : 'implicit' 'operator' type '(' type identifier ')' | 'explicit' 'operator' 'checked'? type '(' type identifier ')' ; ``` For example: ``` C# public static T operator checked ++(T x) {...} public static T operator checked --(T x) {...} public static T operator checked -(T x) {...} public static T operator checked +(T lhs, T rhs) {...} public static T operator checked -(T lhs, T rhs) {...} public static T operator checked *(T lhs, T rhs) {...} public static T operator checked /(T lhs, T rhs) {...} public static explicit operator checked U(T x) {...} ``` ``` C# public static T I1.operator checked ++(T x) {...} public static T I1.operator checked --(T x) {...} public static T I1.operator checked -(T x) {...} public static T I1.operator checked +(T lhs, T rhs) {...} public static T I1.operator checked -(T lhs, T rhs) {...} public static T I1.operator checked *(T lhs, T rhs) {...} public static T I1.operator checked /(T lhs, T rhs) {...} public static explicit I1.operator checked U(T x) {...} ``` For brevity below, an operator with the `checked` keyword is referred to as a `checked operator` and an operator without it is referred to as a `regular operator`. These terms are not applicable to operators that don't have a `checked` form. ### Semantics A user-defined `checked operator` is expected to throw an exception when the result of an operation is too large to represent in the destination type. What does it mean to be too large actually depends on the nature of the destination type and is not prescribed by the language. Typically the exception thrown is a `System.OverflowException`, but the language doesn't have any specific requirements regarding this. A user-defined `regular operator` is expected to not throw an exception when the result of an operation is too large to represent in the destination type. Instead, it is expected to return an instance representing a truncated result. What does it mean to be too large and to be truncated actually depends on the nature of the destination type and is not prescribed by the language. All existing user-defined operators out there that will have `checked` form supported fall into the category of `regular operators`. It is understood that many of them are likely to not follow the semantics specified above, but for the purpose of semantic analysis, compiler will assume that they are. ### Checked vs. unchecked context within a `checked operator` Checked/unchecked context within the body of a `checked operator` is not affected by the presence of the `checked` keyword. In other words, the context is the same as immediately at the beginning of the operator declaration. The developer would need to explicitly switch the context if part of their algorithm cannot rely on default context. ### Names in metadata Section "I.10.3.1 Unary operators" of ECMA-335 will be adjusted to include *op_CheckedIncrement*, *op_CheckedDecrement*, *op_CheckedUnaryNegation* as the names for methods implementing checked `++`, `--` and `-` unary operators. Section "I.10.3.2 Binary operators" of ECMA-335 will be adjusted to include *op_CheckedAddition*, *op_CheckedSubtraction*, *op_CheckedMultiply*, *op_CheckedDivision* as the names for methods implementing checked `+`, `-`, `*`, and `/` binary operators. Section "I.10.3.3 Conversion operators" of ECMA-335 will be adjusted to include *op_CheckedExplicit* as the name for a method implementing checked explicit conversion operator. ### Unary operators Unary `checked operators` follow the rules from [§15.10.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15102-unary-operators). Also, a `checked operator` declaration requires a pair-wise declaration of a `regular operator` (the return type should match as well). A compile-time error occurs otherwise. ``` C# public struct Int128 { // This is fine, both a checked and regular operator are defined public static Int128 operator checked -(Int128 lhs); public static Int128 operator -(Int128 lhs); // This is fine, only a regular operator is defined public static Int128 operator --(Int128 lhs); // This should error, a regular operator must also be defined public static Int128 operator checked ++(Int128 lhs); } ``` ### Binary operators Binary `checked operators` follow the rules from [§15.10.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15103-binary-operators). Also, a `checked operator` declaration requires a pair-wise declaration of a `regular operator` (the return type should match as well). A compile-time error occurs otherwise. ``` C# public struct Int128 { // This is fine, both a checked and regular operator are defined public static Int128 operator checked +(Int128 lhs, Int128 rhs); public static Int128 operator +(Int128 lhs, Int128 rhs); // This is fine, only a regular operator is defined public static Int128 operator -(Int128 lhs, Int128 rhs); // This should error, a regular operator must also be defined public static Int128 operator checked *(Int128 lhs, Int128 rhs); } ``` ### Candidate user-defined operators The Candidate user operators ([§12.4.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-operators)) section will be adjusted as follows (additions/changes are in bold). Given a type `T` and an operation `operator op(A)`, where `op` is an overloadable operator and `A` is an argument list, the set of candidate user-defined operators provided by `T` for `operator op(A)` is determined as follows: * Determine the type `T0`. If `T` is a nullable type, `T0` is its underlying type, otherwise `T0` is equal to `T`. * **Find the set of user-defined operators, `U`. This set consists of:** * **In `unchecked` evaluation context, all regular `operator op` declarations in `T0`.** * **In `checked` evaluation context, all checked and regular `operator op` declarations in `T0` except regular declarations that have pair-wise matching `checked operator` declaration.** * For all `operator op` declarations in **`U`** and all lifted forms of such operators, if at least one operator is applicable ([§12.4.6 - Applicable function member](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-operators)) with respect to the argument list `A`, then the set of candidate operators consists of all such applicable operators in `T0`. * Otherwise, if `T0` is `object`, the set of candidate operators is empty. * Otherwise, the set of candidate operators provided by `T0` is the set of candidate operators provided by the direct base class of `T0`, or the effective base class of `T0` if `T0` is a type parameter. Similar rules will be applied while determining the set of candidate operators in interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces. The section [§12.8.20](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12820-the-checked-and-unchecked-operators) will be adjusted to reflect the effect that the checked/unchecked context has on unary and binary operator overload resolution. #### Example #1: ``` C# public class MyClass { public static void Add(Int128 lhs, Int128 rhs) { // Resolves to `op_CheckedAddition` Int128 r1 = checked(lhs + rhs); // Resolves to `op_Addition` Int128 r2 = unchecked(lhs + rhs); // Resolve to `op_Subtraction` Int128 r3 = checked(lhs - rhs); // Resolve to `op_Subtraction` Int128 r4 = unchecked(lhs - rhs); // Resolves to `op_CheckedMultiply` Int128 r5 = checked(lhs * rhs); // Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128' Int128 r6 = unchecked(lhs * rhs); } public static void Divide(Int128 lhs, byte rhs) { // Resolves to `op_Division` - it is a better match than `op_CheckedDivision` Int128 r4 = checked(lhs / rhs); } } public struct Int128 { public static Int128 operator checked +(Int128 lhs, Int128 rhs); public static Int128 operator +(Int128 lhs, Int128 rhs); public static Int128 operator -(Int128 lhs, Int128 rhs); // Cannot be declared in C# - missing unchecked operator, but could be declared by some other language public static Int128 operator checked *(Int128 lhs, Int128 rhs); public static Int128 operator checked /(Int128 lhs, int rhs); public static Int128 operator /(Int128 lhs, byte rhs); } ``` #### Example #2: ``` C# class C { static void Add(C2 x, C3 y) { object o; // error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3' o = checked(x + y); // C2.op_Addition o = unchecked(x + y); } } class C1 { // Cannot be declared in C# - missing unchecked operator, but could be declared by some other language public static C1 operator checked + (C1 x, C3 y) => new C3(); } class C2 : C1 { public static C2 operator + (C2 x, C1 y) => new C2(); } class C3 : C1 { } ``` #### Example #3: ``` C# class C { static void Add(C2 x, C3 y) { object o; // error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3' o = checked(x + y); // C1.op_Addition o = unchecked(x + y); } } class C1 { public static C1 operator + (C1 x, C3 y) => new C3(); } class C2 : C1 { // Cannot be declared in C# - missing unchecked operator, but could be declared by some other language public static C2 operator checked + (C2 x, C1 y) => new C2(); } class C3 : C1 { } ``` ### Conversion operators Conversion `checked operators` follow the rules from [§15.10.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15104-conversion-operators). However, a `checked operator` declaration requires a pair-wise declaration of a `regular operator`. A compile-time error occurs otherwise. The following paragraph >The signature of a conversion operator consists of the source type and the target type. (This is the only form of member for which the return type participates in the signature.) The implicit or explicit classification of a conversion operator is not part of the operator's signature. Thus, a class or struct cannot declare both an implicit and an explicit conversion operator with the same source and target types. will be adjusted to allow a type to declare checked and regular forms of explicit conversions with the same source and target types. A type will not be allowed to declare both an implicit and a checked explicit conversion operator with the same source and target types. ### Processing of user-defined explicit conversions The third bullet in [§10.5.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1055-user-defined-explicit-conversions): >* Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. will be replaced with the following bullet points: * **Find the set of conversion operators, `U0`. This set consists of:** * **In `unchecked` evaluation context, the user-defined implicit or regular explicit conversion operators declared by the classes or structs in `D`.** * **In `checked` evaluation context, the user-defined implicit or regular/checked explicit conversion operators declared by the classes or structs in `D` except regular explicit conversion operators that have pair-wise matching `checked operator` declaration within the same declaring type.** * Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit or explicit conversion operators **in `U0`** that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. The Checked and unchecked operators [§11.8.20](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12820-the-checked-and-unchecked-operators) section will be adjusted to reflect the effect that the checked/unchecked context has on processing of user-defined explicit conversions. ### Implementing operators A `checked operator` does not implement a `regular operator` and vice versa. ### Linq Expression Trees `Checked operators` will be supported in Linq Expression Trees. A `UnaryExpression`/`BinaryExpression` node will be created with corresponding `MethodInfo`. The following factory methods will be used: ``` C# public static UnaryExpression NegateChecked (Expression expression, MethodInfo? method); public static BinaryExpression AddChecked (Expression left, Expression right, MethodInfo? method); public static BinaryExpression SubtractChecked (Expression left, Expression right, MethodInfo? method); public static BinaryExpression MultiplyChecked (Expression left, Expression right, MethodInfo? method); public static UnaryExpression ConvertChecked (Expression expression, Type type, MethodInfo? method); ``` Note, that C# doesn't support assignments in expression trees, therefore checked increment/decrement will not be supported as well. There is no factory method for checked divide. There is an open question regarding this - [Checked division in Linq Expression Trees](checked-user-defined-operators.md#checked-division-in-linq-expression-trees). ### Dynamic We will investigate the cost of adding support for checked operators in dynamic invocation in CoreCLR and pursue an implementation if the cost is not too high. This is a quote from https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md. ## Drawbacks [drawbacks]: #drawbacks This adds additional complexity to the language and allows users to introduce more kinds of breaking changes to their types. ## Alternatives [alternatives]: #alternatives The generic math interfaces that the libraries plans to expose could expose named methods (such as `AddChecked`). The primary drawback is that this is less readable/maintainable and doesn't get the benefit of the language precedence rules around operators. This section lists alternatives discussed, but not implemented
### Placement of the `checked` keyword Alternatively the `checked` keyword could be moved to the place right before the `operator` keyword: ``` C# public static T checked operator ++(T x) {...} public static T checked operator --(T x) {...} public static T checked operator -(T x) {...} public static T checked operator +(T lhs, T rhs) {...} public static T checked operator -(T lhs, T rhs) {...} public static T checked operator *(T lhs, T rhs) {...} public static T checked operator /(T lhs, T rhs) {...} public static explicit checked operator U(T x) {...} ``` ``` C# public static T checked I1.operator ++(T x) {...} public static T checked I1.operator --(T x) {...} public static T checked I1.operator -(T x) {...} public static T checked I1.operator +(T lhs, T rhs) {...} public static T checked I1.operator -(T lhs, T rhs) {...} public static T checked I1.operator *(T lhs, T rhs) {...} public static T checked I1.operator /(T lhs, T rhs) {...} public static explicit checked I1.operator U(T x) {...} ``` Or it could be moved into the set of operator modifiers: ```antlr operator_modifier : 'public' | 'static' | 'extern' | 'checked' | operator_modifier_unsafe ; ``` ``` C# public static checked T operator ++(T x) {...} public static checked T operator --(T x) {...} public static checked T operator -(T x) {...} public static checked T operator +(T lhs, T rhs) {...} public static checked T operator -(T lhs, T rhs) {...} public static checked T operator *(T lhs, T rhs) {...} public static checked T operator /(T lhs, T rhs) {...} public static checked explicit operator U(T x) {...} ``` ``` C# public static checked T I1.operator ++(T x) {...} public static checked T I1.operator --(T x) {...} public static checked T I1.operator -(T x) {...} public static checked T I1.operator +(T lhs, T rhs) {...} public static checked T I1.operator -(T lhs, T rhs) {...} public static checked T I1.operator *(T lhs, T rhs) {...} public static checked T I1.operator /(T lhs, T rhs) {...} public static checked explicit I1.operator U(T x) {...} ``` ### `unchecked` keyword There were suggestions to support `unchecked` keyword at the same position as the `checked` keyword with the following possible meanings: - Simply to explicitly reflect the regular nature of the operator, or - Perhaps to designate a distinct flavor of an operator that is supposed to be used in an `unchecked` context. The language could support `op_Addition`, `op_CheckedAddition`, and `op_UncheckedAddition` to help limit the number of breaking changes. This adds another layer of complexity that is likely not necessary in most code. ### Operator names in ECMA-335 Alternatively the operator names could be *op_UnaryNegationChecked*, *op_AdditionChecked*, *op_SubtractionChecked*, *op_MultiplyChecked*, *op_DivisionChecked*, with *Checked* at the end. However, it looks like there is already a pattern established to end the names with the operator word. For example, there is a *op_UnsignedRightShift* operator rather than *op_RightShiftUnsigned* operator. ### `Checked operators` are inapplicable in an `unchecked` context The compiler, when performing member lookup to find candidate user-defined operators within an `unchecked` context, could ignore `checked operators`. If metadata is encountered that only defines a `checked operator`, then a compilation error will occur. ``` C# public class MyClass { public static void Add(Int128 lhs, Int128 rhs) { // Resolves to `op_CheckedMultiply` Int128 r5 = checked(lhs * rhs); // Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128' Int128 r5 = unchecked(lhs * rhs); } } public struct Int128 { public static Int128 operator checked *(Int128 lhs, Int128 rhs); } ``` ### More complicated operator lookup and overload resolution rules in a `checked` context The compiler, when performing member lookup to find candidate user-defined operators within a `checked` context will also consider applicable operators ending with `Checked`. That is, if the compiler was attempting to find applicable function members for the binary addition operator, it would look for both `op_Addition` and `op_AdditionChecked`. If the only applicable function member is a `checked operator`, it will be used. If both a `regular operator` and `checked operator` exist and are equally applicable the `checked operator` will be preferred. If both a `regular operator` and a `checked operator` exist but the `regular operator` is an exact match while the `checked operator` is not, the compiler will prefer the `regular operator`. ``` C# public class MyClass { public static void Add(Int128 lhs, Int128 rhs) { // Resolves to `op_CheckedAddition` Int128 r1 = checked(lhs + rhs); // Resolves to `op_Addition` Int128 r2 = unchecked(lhs + rhs); // Resolve to `op_Subtraction` Int128 r3 = checked(lhs - rhs); // Resolve to `op_Subtraction` Int128 r4 = unchecked(lhs - rhs); } public static void Multiply(Int128 lhs, byte rhs) { // Resolves to `op_Multiply` even though `op_CheckedMultiply` is also applicable Int128 r4 = checked(lhs * rhs); } } public struct Int128 { public static Int128 operator checked +(Int128 lhs, Int128 rhs); public static Int128 operator +(Int128 lhs, Int128 rhs); public static Int128 operator -(Int128 lhs, Int128 rhs); public static Int128 operator checked *(Int128 lhs, int rhs); public static Int128 operator *(Int128 lhs, byte rhs); } ``` ### Yet another way to build the set of candidate user-defined operators #### Unary operator overload resolution Assuming that `regular operator` matches `unchecked` evaluation context, `checked operator` matches `checked` evaluation context and an operator that doesn't have `checked` form (for example, `+`) matches either context, the first bullet in [§12.4.4 - Unary operator overload resolution](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1244-unary-overload-resolution): >* The set of candidate user-defined operators provided by `X` for the operation `operator op(x)` is determined using the rules of [§12.4.6 - Candidate user-defined operators](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-defined-operators). will be replaced with the following two bullet points: * The set of candidate user-defined operators provided by `X` for the operation `operator op(x)` **matching the current checked/unchecked context** is determined using the rules of [Candidate user-defined operators](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-defined-operators). * If the set of candidate user-defined operators is not empty, then this becomes the set of candidate operators for the operation. Otherwise, the set of candidate user-defined operators provided by `X` for the operation `operator op(x)` **matching the opposite checked/unchecked context** is determined using the rules of [§12.4.6 - Candidate user-defined operators](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-defined-operators). #### Binary operator overload resolution Assuming that `regular operator` matches `unchecked` evaluation context, `checked operator` matches `checked` evaluation context and an operator that doesn't have a `checked` form (for example, `%`) matches either context, the first bullet in [§12.4.5 - Binary operator overload resolution](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1245-binary-overload-resolution): >* The set of candidate user-defined operators provided by `X` and `Y` for the operation `operator op(x,y)` is determined. The set consists of the union of the candidate operators provided by `X` and the candidate operators provided by `Y`, each determined using the rules of [§12.4.6 - Candidate user-defined operators](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-defined-operators). If `X` and `Y` are the same type, or if `X` and `Y` are derived from a common base type, then shared candidate operators only occur in the combined set once. will be replaced with the following two bullet points: * The set of candidate user-defined operators provided by `X` and `Y` for the operation `operator op(x,y)` **matching the current checked/unchecked context** is determined. The set consists of the union of the candidate operators provided by `X` and the candidate operators provided by `Y`, each determined using the rules of [§12.4.6 - Candidate user-defined operators](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-defined-operators). If `X` and `Y` are the same type, or if `X` and `Y` are derived from a common base type, then shared candidate operators only occur in the combined set once. * If the set of candidate user-defined operators is not empty, then this becomes the set of candidate operators for the operation. Otherwise, the set of candidate user-defined operators provided by `X` and `Y` for the operation `operator op(x,y)` **matching the opposite checked/unchecked context** is determined. The set consists of the union of the candidate operators provided by `X` and the candidate operators provided by `Y`, each determined using the rules of [§12.4.6 - Candidate user-defined operators](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-defined-operators). If `X` and `Y` are the same type, or if `X` and `Y` are derived from a common base type, then shared candidate operators only occur in the combined set once. ##### Example #1: ``` C# public class MyClass { public static void Add(Int128 lhs, Int128 rhs) { // Resolves to `op_CheckedAddition` Int128 r1 = checked(lhs + rhs); // Resolves to `op_Addition` Int128 r2 = unchecked(lhs + rhs); // Resolve to `op_Subtraction` Int128 r3 = checked(lhs - rhs); // Resolve to `op_Subtraction` Int128 r4 = unchecked(lhs - rhs); // Resolves to `op_CheckedMultiply` Int128 r5 = checked(lhs * rhs); // Resolves to `op_CheckedMultiply` Int128 r5 = unchecked(lhs * rhs); } public static void Divide(Int128 lhs, byte rhs) { // Resolves to `op_CheckedDivision` Int128 r4 = checked(lhs / rhs); } } public struct Int128 { public static Int128 operator checked +(Int128 lhs, Int128 rhs); public static Int128 operator +(Int128 lhs, Int128 rhs); public static Int128 operator -(Int128 lhs, Int128 rhs); public static Int128 operator checked *(Int128 lhs, Int128 rhs); public static Int128 operator checked /(Int128 lhs, int rhs); public static Int128 operator /(Int128 lhs, byte rhs); } ``` ##### Example #2: ``` C# class C { static void Add(C2 x, C3 y) { object o; // C1.op_CheckedAddition o = checked(x + y); // C2.op_Addition o = unchecked(x + y); } } class C1 { public static C1 operator checked + (C1 x, C3 y) => new C3(); } class C2 : C1 { public static C2 operator + (C2 x, C1 y) => new C2(); } class C3 : C1 { } ``` ##### Example #3: ``` C# class C { static void Add(C2 x, C3 y) { object o; // C2.op_CheckedAddition o = checked(x + y); // C1.op_Addition o = unchecked(x + y); } } class C1 { public static C1 operator + (C1 x, C3 y) => new C3(); } class C2 : C1 { public static C2 operator checked + (C2 x, C1 y) => new C2(); } class C3 : C1 { } ``` ##### Example #4: ``` C# class C { static void Add(C2 x, byte y) { object o; // C1.op_CheckedAddition o = checked(x + y); // C2.op_Addition o = unchecked(x + y); } static void Add2(C2 x, int y) { object o; // C2.op_Addition o = checked(x + y); // C2.op_Addition o = unchecked(x + y); } } class C1 { public static C1 operator checked + (C1 x, byte y) => new C1(); } class C2 : C1 { public static C2 operator + (C2 x, int y) => new C2(); } ``` ##### Example #5: ``` C# class C { static void Add(C2 x, byte y) { object o; // C2.op_CheckedAddition o = checked(x + y); // C1.op_Addition o = unchecked(x + y); } static void Add2(C2 x, int y) { object o; // C1.op_Addition o = checked(x + y); // C1.op_Addition o = unchecked(x + y); } } class C1 { public static C1 operator + (C1 x, int y) => new C1(); } class C2 : C1 { public static C2 operator checked + (C2 x, byte y) => new C2(); } ``` #### Processing of user-defined explicit conversions Assuming that `regular operator` matches `unchecked` evaluation context and `checked operator` matches `checked` evaluation context, the third bullet in [§10.5.3 Evaluation of user-defined conversions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1053-evaluation-of-user-defined-conversions): >* Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. will be replaced with the following bullet points: * Find the set of applicable user-defined and lifted explicit conversion operators **matching the current checked/unchecked context**, `U0`. This set consists of the user-defined and lifted explicit conversion operators declared by the classes or structs in `D` that **match the current checked/unchecked context** and convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. * Find the set of applicable user-defined and lifted explicit conversion operators **matching the opposite checked/unchecked context**, `U1`. If `U0` is not empty, `U1` is empty. Otherwise, this set consists of the user-defined and lifted explicit conversion operators declared by the classes or structs in `D` that **match the opposite checked/unchecked context** and convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. * Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of operators from `U0`, `U1`, and the user-defined and lifted implicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. ### Yet another another way to build the set of candidate user-defined operators #### Unary operator overload resolution The first bullet in section [§12.4.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1244-unary-operator-overload-resolution) will be adjusted as follows (additions are in bold). * The set of candidate user-defined operators provided by `X` for the operation `operator op(x)` is determined using the rules of "Candidate user-defined operators" section below. **If the set contains at least one operator in checked form, all operators in regular form are removed from the set.** The section [§12.8.20](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12820-the-checked-and-unchecked-operators) will be adjusted to reflect the effect that the checked/unchecked context has on unary operator overload resolution. #### Binary operator overload resolution The first bullet in section [§12.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1245-binary-operator-overload-resolution) will be adjusted as follows (additions are in bold). * The set of candidate user-defined operators provided by `X` and `Y` for the operation `operator op(x,y)` is determined. The set consists of the union of the candidate operators provided by `X` and the candidate operators provided by `Y`, each determined using the rules of "Candidate user-defined operators" section below. If `X` and `Y` are the same type, or if `X` and `Y` are derived from a common base type, then shared candidate operators only occur in the combined set once. **If the set contains at least one operator in checked form, all operators in regular form are removed from the set.** The Checked and unchecked operators [§12.8.20](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12820-the-checked-and-unchecked-operators) section will be adjusted to reflect the effect that the checked/unchecked context has on binary operator overload resolution. #### Candidate user-defined operators The [§12.4.6 - Candidate user-defined operators](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-defined-operators) section will be adjusted as follows (additions are in bold). Given a type `T` and an operation `operator op(A)`, where `op` is an overloadable operator and `A` is an argument list, the set of candidate user-defined operators provided by `T` for `operator op(A)` is determined as follows: * Determine the type `T0`. If `T` is a nullable type, `T0` is its underlying type, otherwise `T0` is equal to `T`. * For all `operator op` declarations **in their checked and regular forms in `checked` evaluation context and only in their regular form in `unchecked` evaluation context** in `T0` and all lifted forms of such operators, if at least one operator is applicable ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12642-applicable-function-member)) with respect to the argument list `A`, then the set of candidate operators consists of all such applicable operators in `T0`. * Otherwise, if `T0` is `object`, the set of candidate operators is empty. * Otherwise, the set of candidate operators provided by `T0` is the set of candidate operators provided by the direct base class of `T0`, or the effective base class of `T0` if `T0` is a type parameter. Similar filtering will be applied while determining the set of candidate operators in interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces. The [§12.8.20](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12820-the-checked-and-unchecked-operators) section will be adjusted to reflect the effect that the checked/unchecked context has on unary and binary operator overload resolution. ##### Example #1: ``` C# public class MyClass { public static void Add(Int128 lhs, Int128 rhs) { // Resolves to `op_CheckedAddition` Int128 r1 = checked(lhs + rhs); // Resolves to `op_Addition` Int128 r2 = unchecked(lhs + rhs); // Resolve to `op_Subtraction` Int128 r3 = checked(lhs - rhs); // Resolve to `op_Subtraction` Int128 r4 = unchecked(lhs - rhs); // Resolves to `op_CheckedMultiply` Int128 r5 = checked(lhs * rhs); // Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128' Int128 r5 = unchecked(lhs * rhs); } public static void Divide(Int128 lhs, byte rhs) { // Resolves to `op_CheckedDivision` Int128 r4 = checked(lhs / rhs); } } public struct Int128 { public static Int128 operator checked +(Int128 lhs, Int128 rhs); public static Int128 operator +(Int128 lhs, Int128 rhs); public static Int128 operator -(Int128 lhs, Int128 rhs); public static Int128 operator checked *(Int128 lhs, Int128 rhs); public static Int128 operator checked /(Int128 lhs, int rhs); public static Int128 operator /(Int128 lhs, byte rhs); } ``` ##### Example #2: ``` C# class C { static void Add(C2 x, C3 y) { object o; // C1.op_CheckedAddition o = checked(x + y); // C2.op_Addition o = unchecked(x + y); } } class C1 { public static C1 operator checked + (C1 x, C3 y) => new C3(); } class C2 : C1 { public static C2 operator + (C2 x, C1 y) => new C2(); } class C3 : C1 { } ``` ##### Example #3: ``` C# class C { static void Add(C2 x, C3 y) { object o; // C2.op_CheckedAddition o = checked(x + y); // C1.op_Addition o = unchecked(x + y); } } class C1 { public static C1 operator + (C1 x, C3 y) => new C3(); } class C2 : C1 { public static C2 operator checked + (C2 x, C1 y) => new C2(); } class C3 : C1 { } ``` ##### Example #4: ``` C# class C { static void Add(C2 x, byte y) { object o; // C2.op_Addition o = checked(x + y); // C2.op_Addition o = unchecked(x + y); } static void Add2(C2 x, int y) { object o; // C2.op_Addition o = checked(x + y); // C2.op_Addition o = unchecked(x + y); } } class C1 { public static C1 operator checked + (C1 x, byte y) => new C1(); } class C2 : C1 { public static C2 operator + (C2 x, int y) => new C2(); } ``` ##### Example #5: ``` C# class C { static void Add(C2 x, byte y) { object o; // C2.op_CheckedAddition o = checked(x + y); // C1.op_Addition o = unchecked(x + y); } static void Add2(C2 x, int y) { object o; // C1.op_Addition o = checked(x + y); // C1.op_Addition o = unchecked(x + y); } } class C1 { public static C1 operator + (C1 x, int y) => new C1(); } class C2 : C1 { public static C2 operator checked + (C2 x, byte y) => new C2(); } ``` #### Processing of user-defined explicit conversions The third bullet in [§10.5.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1055-user-defined-explicit-conversions): >* Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. will be replaced with the following bullet points: * Find the set of applicable user-defined and lifted explicit conversion operators, `U0`. This set consists of the user-defined and lifted explicit conversion operators declared by the classes or structs in `D` **in their checked and regular forms in `checked` evaluation context and only in their regular form in `unchecked` evaluation context** and convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. * If `U0` contains at least one operator in checked form, all operators in regular form are removed from the set. * Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of operators from `U0`, and the user-defined and lifted implicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. The Checked and unchecked operators [§12.8.20](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12820-the-checked-and-unchecked-operators) section will be adjusted to reflect the effect that the checked/unchecked context has on processing of user-defined explicit conversions. ### Checked vs. unchecked context within a `checked operator` The compiler could treat the default context of a `checked operator` as checked. The developer would need to explicitly use `unchecked` if part of their algorithm should not participate in the `checked context`. However, this might not work well in the future if we start allowing `checked`/`unchecked` tokens as modifiers on operators to set the context within the body. The modifier and the keyword could contradict each other. Also, we wouldn't be able to do the same (treat default context as unchecked) for a `regular operator` because that would be a breaking change.
## Unresolved questions [unresolved]: #unresolved-questions Should the language allow `checked` and `unchecked` modifiers on methods (e.g. `static checked void M()`)? This would allow removing nesting levels for methods that require it. ### Checked division in Linq Expression Trees There is no factory method to create a checked division node and there is no `ExpressionType.DivideChecked` member. We could still use the following factory method to create regular divide node with `MethodInfo` pointing to the `op_CheckedDivision` method. Consumers will have to check the name to infer the context. ``` C# public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method); ``` Note, even though [§12.8.20](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12820-the-checked-and-unchecked-operators) section lists `/` operator as one of the operators affected by checked/unchecked evaluation context, IL doesn't have a special op code to perform checked division. Compiler always uses the factory method reardless of the context today. *Proposal:* Checked user-defined devision will not be supported in Linq Expression Trees. ### (Resolved) Should we support implicit checked conversion operators? In general, implicit conversion operators are not supposed to throw. *Proposal:* No. *Resolution:* Approved - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions ## Design meetings https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-14.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-23.md ================================================ FILE: proposals/csharp-11.0/extended-nameof-scope.md ================================================ # Extended `nameof` scope [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Allow `nameof(parameter)` inside an attribute on a method or parameter. For example: - `[MyAttribute(nameof(parameter))] void M(int parameter) { }` - `[MyAttribute(nameof(TParameter))] void M() { }` - `void M(int parameter, [MyAttribute(nameof(parameter))] int other) { }` ## Motivation Attributes like `NotNullWhen` or `CallerExpression` need to refer to parameters, but those parameters are currently not in scope. ## Detailed design [Methods](https://github.com/dotnet/csharplang/blob/master/spec/classes.md#methods) The method's *type_parameters* are in scope throughout the *method_declaration*, and can be used to form types throughout that scope in *return_type*, *method_body*, and *type_parameter_constraints_clauses* but not in *attributes*, **except within a `nameof` expression in *attributes*.** [Method parameters](https://github.com/dotnet/csharplang/blob/master/spec/classes.md#method-parameters) A method declaration creates a separate declaration space for parameters, type parameters and local variables. Names are introduced into this declaration space by the type parameter list and the formal parameter list of the method and by local variable declarations in the block of the method. **Names are introduced into this declaration space by the type parameter list and the formal parameter list of the method in `nameof` expressions in attributes placed on the method or its parameters.** \[...] Within the block of a method, formal parameters can be referenced by their identifiers in simple_name expressions (Simple names). **Within a `nameof` expression in attributes placed on the method or its parameters, formal parameters can be referenced by their identifiers in *simple_name* expressions.** [Anonymous function signatures](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12192-anonymous-function-signatures) The scope of the parameters of the anonymous function is the anonymous_function_body (§7.7) **and `nameof` expressions in attributes placed on the anonymous function or its parameters**. [Delegate declarations](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/delegates.md#202-delegate-declarations) **The scope of the parameters of the delegate is `nameof` expressions in attributes placed on the declaration, its type parameters or its parameters**. [Simple names](https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#simple-names) A *simple_name* is either of the form `I` or of the form `I`, where `I` is a single identifier and `` is an optional *type_argument_list*. When no *type_argument_list* is specified, consider `K` to be zero. The *simple_name* is evaluated and classified as follows: - If `K` is zero and the *simple_name* appears within a block and if the block's (or an enclosing block's) local variable declaration space (Declarations) contains a local variable, parameter or constant with name `I`, then the *simple_name* refers to that local variable, parameter or constant and is classified as a variable or value. - If `K` is zero and the *simple_name* appears within the body of a generic method declaration and if that declaration includes a type parameter with name `I`, then the *simple_name* refers to that type parameter. - **If `K` is zero and the *simple_name* appears within a `nameof` expression in an attribute on the method declaration or its parameters and if that declaration includes a parameter or type parameter with name `I`, then the *simple_name* refers to that parameter or type parameter.** - Otherwise, for each instance type `T` (The instance type), starting with the instance type of the immediately enclosing type declaration and continuing with the instance type of each enclosing class or struct declaration (if any): \[...] - Otherwise, for each namespace `N`, starting with the namespace in which the *simple_name* occurs, continuing with each enclosing namespace (if any), and ending with the global namespace, the following steps are evaluated until an entity is located: \[...] - Otherwise, the simple_name is undefined and a compile-time error occurs. [Scopes](https://github.com/dotnet/csharplang/blob/master/spec/basic-concepts.md#scopes) - The scope of a type parameter declared by a type_parameter_list on a method_declaration is \[...] **and `nameof` expressions in an attribute on the method declaration or its parameters.** - The scope of a parameter declared in a method_declaration (Methods) is the *method_body* of that method_declaration **and `nameof` expressions in an attribute on the method declaration or its parameters.** ## Related spec sections - [Declarations](https://github.com/dotnet/csharplang/blob/master/spec/basic-concepts.md#declarations) ================================================ FILE: proposals/csharp-11.0/file-local-types.md ================================================ # File-local types [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Permit a `file` modifier on top-level type declarations. The type only exists in the file where it is declared. ```cs // File1.cs namespace NS; file class Widget { } // File2.cs namespace NS; file class Widget // different symbol than the Widget in File1 { } // File3.cs using NS; var widget = new Widget(); // error: The type or namespace name 'Widget' could not be found. ``` ## Motivation [motivation]: #motivation Our primary motivation is from source generators. Source generators work by adding files to the user's compilation. 1. Those files should be able to contain implementation details which are hidden from the rest of the compilation, yet are usable throughout the file they are declared in. 2. We want to reduce the need for generators to "search" for type names which won't collide with declarations in user code or code from other generators. ## Detailed design [design]: #detailed-design - We add the `file` modifier to the following modifier sets: - [class](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1536-class-modifiers) - [struct](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#1622-struct-modifiers) - [interface](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/interfaces.md#1822-interface-modifiers) - [enum](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/enums.md#193-enum-modifiers) - [delegate](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/delegates.md#202-delegate-declarations) - record - record struct. - The `file` modifier can only be used on a top-level type. When a type has the `file` modifier, it is said to be a *file-local* type. ### Accessibility The `file` modifier is not classified as an accessibility modifier. No accessibility modifiers can be used in combination with `file` on a type. `file` is treated as an independent concept from accessibility. Since file-local types can't be nested, only the default accessibility `internal` is usable with `file` types. ```cs public file class C1 { } // error internal file class C2 { } // error file class C3 { } // ok ``` ### Naming The implementation guarantees that file-local types in different files with the same name will be distinct to the runtime. The type's accessibility and name in metadata is implementation-defined. The intention is to permit the compiler to adopt any future access-limitation features in the runtime which are suited to the feature. It's expected that in the initial implementation, an `internal` accessibility would be used and an unspeakable generated name will be used which depends on the file the type is declared in. ### Lookup We amend the [member lookup](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#125-member-lookup) section as follows (new text in **bold**): > - Next, if `K` is zero, all nested types whose declarations include type parameters are removed. If `K` is not zero, all members with a different number of type parameters are removed. When `K` is zero, methods having type parameters are not removed, since the type inference process ([§11.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1263-type-inference)) might be able to infer the type arguments. > - **Next, let *F* be the compilation unit which contains the expression where member lookup is occurring. All members which are file-local types and are not declared in *F* are removed from the set.** > - **Next, if the set of accessible members contains file-local types, all members which are not file-local types are removed from the set.** #### Remarks These rules disallow usage of file-local types outside the file in which they are declared. These rules also permit a file-local type to *shadow* a namespace or a non-file-local type: ```cs // File1.cs class C { public static void M() { } } ``` ```cs // File2.cs file class C { public static void M() { } } class Program { static void Main() { C.M(); // refers to the 'C' in File2.cs } } ``` Note that we don't update the [scopes](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#77-scopes) section of the spec. This is because, as the spec states: > The ***scope*** of a name is the region of program text within which it is possible to refer to the entity declared by the name without qualification of the name. In effect, scope only impacts the lookup of non-qualified names. This isn't quite the right concept for us to leverage because we need to also impact the lookup of qualified names: ```cs // File1.cs namespace NS1 { file class C { public static void M() { } } } namespace NS2 { class Program { public static void M() { C.M(); // error: C is not in scope NS1.C.M(); // ok: C can be accessed through NS1. } } } ``` ```cs // File2.cs namespace NS1 { class Program { C.M(); // error NS1.C.M(); // error } } ``` Therefore, we don't specify the feature in terms of which scope the type is contained in, but rather as additional "filtering rules" in member lookup. ### Attributes File-local classes are permitted to be attribute types, and can be used as attributes within both file-local types and non-file-local types, just as if the attribute type were a non-file-local type. The metadata name of the file-local attribute type still goes through the same name generation strategy as other file-local types. This means detecting the presence of a file-local type by a hard-coded string name is likely to be impractical, because it requires depending on the internal name generation strategy of the compiler, which may change over time. However, detecting via `typeof(MyFileLocalAttribute)` works. ```cs using System; using System.Linq; file class MyFileLocalAttribute : Attribute { } [MyFileLocalAttribute] public class C { public static void Main() { var attribute = typeof(C).CustomAttributes.Where(attr => attr.AttributeType == typeof(MyFileLocalAttribute)).First(); Console.Write(attribute); // outputs the generated name of the file-local attribute type } } ``` ### Usage in signatures There is a general need to prevent file-local types from appearing in member parameters, returns, and type parameter constraints where the file-local type might not be in scope at the point of usage of the member. Note that non-file-local types are permitted to implement file-local interfaces, similar to how types can implement less-accessible interfaces. Depending on the types present in the interface members, it could result in a violation of the rules in the following section. #### Only allow signature usage in members of file-local types Perhaps the simplest way to ensure this is to enforce that file-local types can only appear in signatures or as base types of other file-local types: ```cs file class FileBase { } public class Derived : FileBase // error { private FileBase M2() => new FileBase() // error } file class FileDerived : FileBase // ok { private FileBase M2() => new FileBase(); // ok } ``` Note that this does restrict usage in explicit implementations, even though such usages are safe. We do this in order to simplify the rules for the initial iteration of the feature. ```cs file interface I { void M(I i); } class C : I { void I.M(I i) { } // error } ``` ### `global using static` It is a compile-time error to use a file-local type in a `global using static` directive, i.e. ```cs global using static C; // error file class C { public static void M() { } } ``` ### Implementation/overrides file-local type declarations can implement interfaces, override virtual methods, etc. just like regular type declarations. ```cs file struct Widget : IEquatable { public bool Equals(Widget other) => true; } ``` ================================================ FILE: proposals/csharp-11.0/generic-attributes.md ================================================ # Generic Attributes [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary When generics were introduced in C# 2.0, attribute classes were not allowed to participate. We can make the language more composable by removing (rather, loosening) this restriction. The .NET Core runtime has added support for generic attributes. Now, all that's missing is support for generic attributes in the compiler. ## Motivation [motivation]: #motivation Currently attribute authors can take a `System.Type` as a parameter and have users pass a `typeof` expression to provide the attribute with types that it needs. However, outside of analyzers, there's no way for an attribute author to constrain what types are allowed to be passed to an attribute via `typeof`. If attributes could be generic, then attribute authors could use the existing system of type parameter constraints to express the requirements for the types they take as input. ## Detailed design [design]: #detailed-design The following section is amended: [§15.2.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15242-base-classes) > The direct base class of a class type must not be any of the following types: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, or System.ValueType. ~~Furthermore, a generic class declaration cannot use System.Attribute as a direct or indirect base class.~~ One important note is that the following section of the spec is *unaffected* when referencing the point of usage of an attribute, i.e. within an attribute list: Type parameters - [§8.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#85-type-parameters). > A type parameter cannot be used anywhere within an attribute. This means that when a generic attribute is used, its construction needs to be fully "closed", i.e. not containing any type parameters, which means the following is still disallowed: ```cs using System; using System.Collections.Generic; public class Attr : Attribute { } public class Program { [Attr] // error [Attr>] // error void M() { } } ``` When a generic attribute is used in an attribute list, its type arguments have the same restrictions that `typeof` has on its argument. For example, `[Attr]` is an error. This is because "attribute-dependent" types like `dynamic`, `List`, `nint`, and so on can't be fully represented in the final IL for an attribute type argument, because there isn't a symbol to "attach" the `DynamicAttribute` or other well-known attribute to. ## Drawbacks [drawbacks]: #drawbacks Removing the restriction, reasoning out the implications, and adding the appropriate tests is work. ## Alternatives [alternatives]: #alternatives Attribute authors who want users to be able to discover the requirements for the types they provide to attributes need to write analyzers and guide their users to use those analyzers in their builds. ## Unresolved questions [unresolved]: #unresolved-questions - [x] What does `AllowMultiple = false` mean on a generic attribute? If we have `[Attr]` and `[Attr]` both used on a symbol, does that mean "multiple" of the attribute are in use? - For now we are inclined to take the more restrictive route here and consider the attribute class's original definition when deciding whether multiple of it have been applied. In other words, `[Attr]` and `[Attr]` applied together is incompatible with `AllowMultiple = false`. ## Design meetings - https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-02-21.md#generic-attributes - At the time there was a concern that we would have to gate the feature on whether the target runtime supports it. (However, we now only support C# 10 on .NET 6. It would be nice for the implementation to be aware of what minimum target framework supports the feature, but seems less essential today.) ================================================ FILE: proposals/csharp-11.0/list-patterns.md ================================================ # List patterns [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Lets you to match an array or a list with a sequence of patterns e.g. `array is [1, 2, 3]` will match an integer array of the length three with 1, 2, 3 as its elements, respectively. ## Detailed design The pattern syntax is modified as follow: ```antlr list_pattern_clause : '[' (pattern (',' pattern)* ','?)? ']' ; list_pattern : list_pattern_clause simple_designation? ; slice_pattern : '..' pattern? ; primary_pattern : list_pattern | slice_pattern | // all of the pattern forms previously defined ; ``` There are two new patterns: - The *list_pattern* is used to match elements. - A *slice_pattern* is only permitted once and only directly in a *list_pattern_clause* and discards _**zero or more**_ elements. #### Pattern compatibility A *list_pattern* is compatible with any type that is *countable* as well as *indexable* — it has an accessible indexer that takes an `Index` as an argument or otherwise an accessible indexer with a single `int` parameter. If both indexers are present, the former is preferred. A *slice_pattern* with a subpattern is compatible with any type that is *countable* as well as *sliceable* — it has an accessible indexer that takes a `Range` as an argument or otherwise an accessible `Slice` method with two `int` parameters. If both are present, the former is preferred. A *slice_pattern* without a subpattern is compatible with any type that is compatible with a *list_pattern*. This set of rules is derived from the [***range indexer pattern***](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/ranges.md#implicit-index-support). #### Subsumption checking Subsumption checking works just like [positional patterns with `ITuple`](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/patterns.md#positional-pattern) - corresponding subpatterns are matched by position plus an additional node for testing length. For example, the following code produces an error because both patterns yield the same DAG: ```cs case [_, .., 1]: // expr.Length is >= 2 && expr[^1] is 1 case [.., _, 1]: // expr.Length is >= 2 && expr[^1] is 1 ``` Unlike: ```cs case [_, 1, ..]: // expr.Length is >= 2 && expr[1] is 1 case [.., 1, _]: // expr.Length is >= 2 && expr[^2] is 1 ``` The order in which subpatterns are matched at runtime is unspecified, and a failed match may not attempt to match all subpatterns. Given a specific length, it's possible that two subpatterns refer to the same element, in which case a test for this value is inserted into the decision DAG. - For instance, `[_, >0, ..] or [.., <=0, _]` becomes `length >= 2 && ([1] > 0 || length == 3 || [^2] <= 0)` where the length value of 3 implies the other test. - Conversely, `[_, >0, ..] and [.., <=0, _]` becomes `length >= 2 && [1] > 0 && length != 3 && [^2] <= 0` where the length value of 3 disallows the other test. As a result, an error is produced for something like `case [.., p]: case [p]:` because at runtime, we're matching the same element in the second case. If a slice subpattern matches a list or a length value, subpatterns are treated as if they were a direct subpattern of the containing list. For instance, `[..[1, 2, 3]]` subsumes a pattern of the form `[1, 2, 3]`. The following assumptions are made on the members being used: - The property that makes the type *countable* is assumed to always return a non-negative value, if and only if the type is *indexable*. For instance, the pattern `{ Length: -1 }` can never match an array. - The member that makes the type *sliceable* is assumed to be well-behaved, that is, the return value is never null and that it is a proper subslice of the containing list. The behavior of a pattern-matching operation is undefined if any of the above assumptions doesn't hold. #### Lowering A pattern of the form `expr is [1, 2, 3]` is equivalent to the following code: ```cs expr.Length is 3 && expr[new Index(0, fromEnd: false)] is 1 && expr[new Index(1, fromEnd: false)] is 2 && expr[new Index(2, fromEnd: false)] is 3 ``` A *slice_pattern* acts like a proper discard i.e. no tests will be emitted for such pattern, rather it only affects other nodes, namely the length and indexer. For instance, a pattern of the form `expr is [1, .. var s, 3]` is equivalent to the following code (if compatible via explicit `Index` and `Range` support): ```cs expr.Length is >= 2 && expr[new Index(0, fromEnd: false)] is 1 && expr[new Range(new Index(1, fromEnd: false), new Index(1, fromEnd: true))] is var s && expr[new Index(1, fromEnd: true)] is 3 ``` The *input type* for the *slice_pattern* is the return type of the underlying `this[Range]` or `Slice` method with two exceptions: For `string` and arrays, `string.Substring` and `RuntimeHelpers.GetSubArray` will be used, respectively. ## Unresolved questions 1. Should we support multi-dimensional arrays? (answer [LDM 2021-05-26]: Not supported. If we want to make a general MD-array focused release, we would want to revisit all the areas they're currently lacking, not just list patterns.) 2. Should we accept a general *pattern* following `..` in a *slice_pattern*? (answer [LDM 2021-05-26]: Yes, any pattern is allowed after a slice.) 3. By this definition, the pattern `[..]` tests for `expr.Length >= 0`. Should we omit such test, assuming `Length` is always non-negative? (answer [LDM 2021-05-26]: `[..]` will not emit a Length check) ================================================ FILE: proposals/csharp-11.0/low-level-struct-improvements.md ================================================ # Low Level Struct Improvements [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issues: , ## Summary This proposal is an aggregation of several different proposals for `struct` performance improvements: `ref` fields and the ability to override lifetime defaults. The goal being a design which takes into account the various proposals to create a single overarching feature set for low level `struct` improvements. > Note: Previous versions of this spec used the terms "ref-safe-to-escape" and "safe-to-escape", which were introduced in the [Span safety](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md) feature specification. The [ECMA standard committee](https://www.ecma-international.org/task-groups/tc49-tg2/) changed the names to ["ref-safe-context"](https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/variables#972-ref-safe-contexts) and ["safe-context"](https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/structs#16412-safe-context-constraint), respectively. The values of the safe context have been refined to use "declaration-block", "function-member", and "caller-context" consistently. The speclets had used different phrasing for these terms, and also used "safe-to-return" as a synonym for "caller-context". This speclet has been updated to use the terms in the C# 7.3 standard. Not all the features outlined in this document have been implemented in C# 11. C# 11 includes: 1. `ref` fields and `scoped` 1. `[UnscopedRef]` These features remain open proposals for a future version of C#: 1. `ref` fields to `ref struct` 1. Sunset restricted types ## Motivation Earlier versions of C# added a number of low level performance features to the language: `ref` returns, `ref struct`, function pointers, etc. ... These enabled .NET developers to write highly performant code while continuing to leverage the C# language rules for type and memory safety. It also allowed the creation of fundamental performance types in the .NET libraries like `Span`. As these features have gained traction in the .NET ecosystem developers, both internal and external, have been providing us with information on remaining friction points in the ecosystem. Places where they still need to drop to `unsafe` code to get their work done, or require the runtime to special case types like `Span`. Today `Span` is accomplished by using the `internal` type `ByReference` which the runtime effectively treats as a `ref` field. This provides the benefit of `ref` fields but with the downside that the language provides no safety verification for it, as it does for other uses of `ref`. Further only dotnet/runtime can use this type as it's `internal`, so 3rd parties can not design their own primitives based on `ref` fields. Part of the [motivation for this work](https://github.com/dotnet/runtime/issues/32060) is to remove `ByReference` and use proper `ref` fields in all code bases. This proposal plans to address these issues by building on top of our existing low level features. Specifically it aims to: - Allow `ref struct` types to declare `ref` fields. - Allow the runtime to fully define `Span` using the C# type system and remove special case type like `ByReference` - Allow `struct` types to return `ref` to their fields. - Allow runtime to remove `unsafe` uses caused by limitations of lifetime defaults - Allow the declaration of safe `fixed` buffers for managed and unmanaged types in `struct` ## Detailed Design The rules for `ref struct` safety are defined in the [span safety document](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md) using the previous terms. Those rules have been incorporated into the C# 7 standard in [§9.7.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#972-ref-safe-contexts) and [§16.4.12](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#16412-safe-context-constraint). This document will describe the required changes to this document as a result of this proposal. Once accepted as an approved feature these changes will be incorporated into that document. Once this design is complete our `Span` definition will be as follows: ```c# readonly ref struct Span { readonly ref T _field; readonly int _length; // This constructor does not exist today but will be added as a part // of changing Span to have ref fields. It is a convenient, and // safe, way to create a length one span over a stack value that today // requires unsafe code. public Span(ref T value) { _field = ref value; _length = 1; } } ``` ### Provide ref fields and scoped The language will allow developers to declare `ref` fields inside of a `ref struct`. This can be useful for example when encapsulating large mutable `struct` instances or defining high performance types like `Span` in libraries besides the runtime. ``` C# ref struct S { public ref int Value; } ``` A `ref` field will be emitted into metadata using the `ELEMENT_TYPE_BYREF` signature. This is no different than how we emit `ref` locals or `ref` arguments. For example `ref int _field` will be emitted as `ELEMENT_TYPE_BYREF ELEMENT_TYPE_I4`. This will require us to update ECMA335 to allow this entry but this should be rather straight forward. Developers can continue to initialize a `ref struct` with a `ref` field using the `default` expression in which case all declared `ref` fields will have the value `null`. Any attempt to use such fields will result in a `NullReferenceException` being thrown. ```c# ref struct S { public ref int Value; } S local = default; local.Value.ToString(); // throws NullReferenceException ``` While the C# language pretends that a `ref` cannot be `null` this is legal at the runtime level and has well defined semantics. Developers who introduce `ref` fields into their types need to be aware of this possibility and should be **strongly** discouraged from leaking this detail into consuming code. Instead `ref` fields should be validated as non-null using the [runtime helpers](https://github.com/dotnet/runtime/pull/40008) and throwing when an uninitialized `struct` is used incorrectly. ```c# ref struct S1 { private ref int Value; public int GetValue() { if (System.Runtime.CompilerServices.Unsafe.IsNullRef(ref Value)) { throw new InvalidOperationException(...); } return Value; } } ``` A `ref` field can be combined with `readonly` modifiers in the following ways: - `readonly ref`: this is a field that cannot be ref reassigned outside a constructor or `init` methods. It can be value assigned though outside those contexts - `ref readonly`: this is a field that can be ref reassigned but cannot be value assigned at any point. This how an `in` parameter could be ref reassigned to a `ref` field. - `readonly ref readonly`: a combination of `ref readonly` and `readonly ref`. ```c# ref struct ReadOnlyExample { ref readonly int Field1; readonly ref int Field2; readonly ref readonly int Field3; void Uses(int[] array) { Field1 = ref array[0]; // Okay Field1 = array[0]; // Error: can't assign ref readonly value (value is readonly) Field2 = ref array[0]; // Error: can't repoint readonly ref Field2 = array[0]; // Okay Field3 = ref array[0]; // Error: can't repoint readonly ref Field3 = array[0]; // Error: can't assign ref readonly value (value is readonly) } } ``` A `readonly ref struct` will require that `ref` fields are declared `readonly ref`. There is no requirement that they are declared `readonly ref readonly`. This does allow a `readonly struct` to have indirect mutations via such a field but that is no different than a `readonly` field that pointed to a reference type today ([more details](#reason-readonly-shallow)) A `readonly ref` will be emitted to metadata using the `initonly` flag, same as any other field. A `ref readonly` field will be attributed with `System.Runtime.CompilerServices.IsReadOnlyAttribute`. A `readonly ref readonly` will be emitted with both items. This feature requires runtime support and changes to the ECMA spec. As such these will only be enabled when the corresponding feature flag is set in corelib. The issue tracking the exact API is tracked here https://github.com/dotnet/runtime/issues/64165 The set of changes to our safe context rules necessary to allow `ref` fields is small and targeted. The rules already account for `ref` fields existing and being consumed from APIs. The changes need to focus on only two aspects: how they are created and how they are ref reassigned. First the rules establishing *ref-safe-context* values for fields need to be updated for `ref` fields as follows: > An expression in the form `ref e.F` *ref-safe-context* as follows: > 1. If `F` is a `ref` field its *ref-safe-context* is the *safe-context* of `e`. > 2. Else if `e` is of a reference type, it has *ref-safe-context* of *caller-context* > 3. Else its *ref-safe-context* is taken from the *ref-safe-context* of `e`. This does not represent a rule change though as the rules have always accounted for `ref` state to exist inside a `ref struct`. This is in fact how the `ref` state in `Span` has always worked and the consumption rules correctly account for this. The change here is just accounting for developers to be able to access `ref` fields directly and ensure they do so by the existing rules implicitly applied to `Span`. This does mean though that `ref` fields can be returned as `ref` from a `ref struct` but normal fields cannot. ```c# ref struct RS { ref int _refField; int _field; // Okay: this falls into bullet one above. public ref int Prop1 => ref _refField; // Error: This is bullet four above and the ref-safe-context of `this` // in a `struct` is function-member. public ref int Prop2 => ref _field; } ``` This may seem like an error at first glance but this is a deliberate design point. Again though, this is not a new rule being created by this proposal, it is instead acknowledging the existing rules `Span` behaved by now that developers can declare their own `ref` state. Next the rules for ref reassignment need to be adjusted for the presence of `ref` fields. The primary scenario for ref reassignment is `ref struct` constructors storing `ref` parameters into `ref` fields. The support will be more general but this is the core scenario. To support this the rules for ref reassignment will be adjusted to account for `ref` fields as follows: #### Ref reassignment rules The left operand of the `= ref` operator must be an expression that binds to a ref local variable, a ref parameter (other than `this`), an out parameter, **or a ref field**. > For a ref reassignment in the form `e1 = ref e2` both of the following must be true: > 1. `e2` must have *ref-safe-context* at least as large as the *ref-safe-context* of `e1` > 2. `e1` must have the same *safe-context* as `e2` [Note](#examples-ref-reassignment-safety) That means the desired `Span` constructor works without any extra annotation: ```c# readonly ref struct Span { readonly ref T _field; readonly int _length; public Span(ref T value) { // Falls into the `x.e1 = ref e2` case, where `x` is the implicit `this`. The // safe-context of `this` is *return-only* and ref-safe-context of `value` is // *caller-context* hence this is legal. _field = ref value; _length = 1; } } ``` The change to ref reassignment rules means `ref` parameters can now escape from a method as a `ref` field in a `ref struct` value. As discussed in the [compat considerations section](#new-span-challenges) this can change the rules for existing APIs that never intended for `ref` parameters to escape as a `ref` field. The lifetime rules for parameters are based solely on their declaration not on their usage. All `ref` and `in` parameters have *ref-safe-context* of *caller-context* and hence can now be returned by `ref` or a `ref` field. In order to support APIs having `ref` parameters that can be escaping or non-escaping, and thus restore C# 10 call site semantics, the language will introduce limited lifetime annotations. #### `scoped` modifier The keyword `scoped` will be used to restrict the lifetime of a value. It can be applied to a `ref` or a value that is a `ref struct` and has the impact of restricting the *ref-safe-context* or *safe-context* lifetime, respectively, to the *function-member*. For example: | Parameter or Local | ref-safe-context | safe-context | |---|---|---| | `Span s` | *function-member* | *caller-context* | | `scoped Span s` | *function-member* | *function-member* | | `ref Span s` | *caller-context* | *caller-context* | | `scoped ref Span s` | *function-member* | *caller-context* | In this relationship the *ref-safe-context* of a value can never be wider the *safe-context*. This allows for APIs in C# 11 to be annotated such that they have the same rules as C# 10: ```c# Span CreateSpan(scoped ref int parameter) { // Just as with C# 10, the implementation of this method isn't relevant to callers. } Span BadUseExamples(int parameter) { // Legal in C# 10 and legal in C# 11 due to scoped ref return CreateSpan(ref parameter); // Legal in C# 10 and legal in C# 11 due to scoped ref int local = 42; return CreateSpan(ref local); // Legal in C# 10 and legal in C# 11 due to scoped ref Span span = stackalloc int[42]; return CreateSpan(ref span[0]); } ``` The `scoped` annotation also means that the `this` parameter of a `struct` can now be defined as `scoped ref T`. Previously it had to be special cased in the rules as `ref` parameter that had different *ref-safe-context* rules than other `ref` parameters (see all the references to including or excluding the receiver in the safe context rules). Now it can be expressed as a general concept throughout the rules which further simplifies them. The `scoped` annotation can also be applied to the following locations: - locals: This annotation sets the lifetime as *safe-context*, or *ref-safe-context* in case of a `ref` local, to of *function-member* irrespective of the initializer lifetime. ```c# Span ScopedLocalExamples() { // Error: `span` has a safe-context of *function-member*. That is true even though the // initializer has a safe-context of *caller-context*. The annotation overrides the // initializer scoped Span span = default; return span; // Okay: the initializer has safe-context of *caller-context* hence so does `span2` // and the return is legal. Span span2 = default; return span2; // The declarations of `span3` and `span4` are functionally identical because the // initializer has a safe-context of *function-member* meaning the `scoped` annotation // is effectively implied on `span3` Span span3 = stackalloc int[42]; scoped Span span4 = stackalloc int[42]; } ``` Other uses for `scoped` on locals are discussed [below](#examples-scoped-locals). The `scoped` annotation cannot be applied to any other location including returns, fields, array elements, etc ... Further while `scoped` has impact when applied to any `ref`, `in` or `out` it only has impact when applied to values which are `ref struct`. Having declarations like `scoped int` has no impact because a non `ref struct` is always safe to return. The compiler will create a diagnostic for such cases to avoid developer confusion. #### Change the behavior of `out` parameters To further limit the impact of the compat change of making `ref` and `in` parameters returnable as `ref` fields, the language will change the default *ref-safe-context* value for `out` parameters to be *function-member*. Effectively `out` parameters are implicitly `scoped out` going forward. From a compat perspective this means they cannot be returned by `ref`: ```c# ref int Sneaky(out int i) { i = 42; // Error: ref-safe-context of out is now function-member return ref i; } ``` This will increase the flexibility of APIs that return `ref struct` values and have `out` parameters because it does not have to consider the parameter being captured by reference anymore. This is important because it's a common pattern in reader style APIs: ```c# Span Read(Span buffer, out int read) { // .. } Span Use() { var buffer = new byte[256]; // If we keep current `out` ref-safe-context this is an error. The language must consider // the `read` parameter as returnable as a `ref` field // // If we change `out` ref-safe-context this is legal. The language does not consider the // `read` parameter to be returnable hence this is safe int read; return Read(buffer, out read); } ``` The language will also no longer consider arguments passed to an `out` parameter to be returnable. Treating the input to an `out` parameter as returnable was extremely confusing to developers. It essentially subverts the intent of `out` by forcing developers to consider the value passed by the caller which is never used except in languages that don't respect `out`. Going forward languages that support `ref struct` must ensure the original value passed to an `out` parameter is never read. C# achieves this via it's definite assignment rules. That both achieves our ref safe context rules as well as allowing for existing code which assigns and then returns `out` parameters values. ```c# Span StrangeButLegal(out Span span) { span = default; return span; } ``` Together these changes mean the argument to an `out` parameter does not contribute *safe-context* or *ref-safe-context* values to method invocations. This significantly reduces the overall compat impact of `ref` fields as well as simplifies how developers think about `out`. An argument to an `out` parameter does not contribute to the return, it is simply an output. #### Infer *safe-context* of declaration expressions The *safe-context* of a declaration variable from an `out` argument (`M(x, out var y)`) or deconstruction (`(var x, var y) = M()`) is the *narrowest* of the following: * caller-context * if out variable is marked `scoped`, then *declaration-block* (i.e. function-member or narrower). * if out variable's type is `ref struct`, consider all arguments to the containing invocation, including the receiver: * *safe-context* of any argument where its corresponding parameter is not `out` and has *safe-context* of *return-only* or wider * *ref-safe-context* of any argument where its corresponding parameter has *ref-safe-context* of *return-only* or wider See also [Examples of inferred *safe-context* of declaration expressions](#examples-of-inferred-safe-context-of-declaration-expressions). #### Implicitly `scoped` parameters Overall there are two `ref` location which are implicitly declared as `scoped`: - `this` on a `struct` instance method - `out` parameters The ref safe context rules will be written in terms of `scoped ref` and `ref`. For ref safe context purposes an `in` parameter is equivalent to `ref` and `out` is equivalent to `scoped ref`. Both `in` and `out` will only be specifically called out when it is important to the semantic of the rule. Otherwise they are just considered `ref` and `scoped ref` respectively. When discussing the *ref-safe-context* of arguments that correspond to `in` parameters they will be generalized as `ref` arguments in the spec. In the case the argument is an lvalue then the *ref-safe-context* is that of the lvalue, otherwise it is *function-member*. Again `in` will only be called out here when it is important to the semantic of the current rule. #### Return-only safe context The design also requires that the introduction of a new safe-context: *return-only*. This is similar to *caller-context* in that it can be returned but it can **only** be returned through a `return` statement. The details of *return-only* is that it's a context which is greater than *function-member* but smaller than *caller-context*. An expression provided to a `return` statement must be at least *return-only*. As such most existing rules fall out. For example assignment into a `ref` parameter from an expression with a *safe-context* of *return-only* will fail because it's smaller than the `ref` parameter's *safe-context* which is *caller-context*. The need for this new escape context will be discussed [below](#rules-unscoped). There are three locations which default to *return-only*: - A `ref` or `in` parameter will have a *ref-safe-context* of *return-only*. This is done in part for `ref struct` to prevent [silly cyclic assignment](#cyclic-assignment) issues. It is done uniformly though to simplify the model as well as minimize compat changes. - A `out` parameter for a `ref struct` will have *safe-context* of *return-only*. This allows for return and `out` to be equally expressive. This does not have the silly cyclic assignment problem because `out` is implicitly `scoped` so the *ref-safe-context* is still smaller than the *safe-context*. - A `this` parameter for a `struct` constructor will have a *safe-context* of *return-only*. This falls out due to being modeled as `out` parameters. Any expression or statement which explicitly returns a value from a method or lambda must have a *safe-context*, and if applicable a *ref-safe-context*, of at least *return-only*. That includes `return` statements, expression bodied members and lambda expressions. Likewise any assignment to an `out` must have a *safe-context* of at least *return-only*. This is not a special case though, this just follows from the existing assignment rules. Note: An expression whose type is not a `ref struct` type always has a *safe-context* of *caller-context*. #### Rules for method invocation The ref safe context rules for method invocation will be updated in several ways. The first is by recognizing the impact that `scoped` has on arguments. For a given argument `expr` that is passed to parameter `p`: > 1. If `p` is `scoped ref` then `expr` does not contribute *ref-safe-context* when considering arguments. > 2. If `p` is `scoped` then `expr` does not contribute *safe-context* when considering arguments. > 3. If `p` is `out` then `expr` does not contribute *ref-safe-context* or *safe-context* [more details](#out-compat-change) The language "does not contribute" means the arguments are simply not considered when calculating the *ref-safe-context* or *safe-context* value of the method return respectively. That is because the values can't contribute to that lifetime as the `scoped` annotation prevents it. The method invocation rules can now be simplified. The receiver no longer needs to be special cased, in the case of `struct` it is now simply a `scoped ref T`. The value rules need to change to account for `ref` field returns: > A value resulting from a method invocation `e1.M(e2, ...)`, where `M()` does not return ref-to-ref-struct, has a *safe-context* taken from the narrowest of the following: > 1. The *caller-context* > 2. When the return is a `ref struct` the *safe-context* contributed by all argument expressions > 3. When the return is a `ref struct` the *ref-safe-context* contributed by all `ref` arguments > > If `M()` does return ref-to-ref-struct, the *safe-context* is the same as the *safe-context* of all arguments which are ref-to-ref-struct. It is an error if there are multiple arguments with different *safe-context* because of [method arguments must match](#rules-method-arguments-must-match). The `ref` calling rules can be simplified to: > A value resulting from a method invocation `ref e1.M(e2, ...)`, where `M()` does not return ref-to-ref-struct, is *ref-safe-context* the narrowest of the following contexts: > 1. The *caller-context* > 2. The *safe-context* contributed by all argument expressions > 3. The *ref-safe-context* contributed by all `ref` arguments > > If `M()` does return ref-to-ref-struct, the *ref-safe-context* is the narrowest *ref-safe-context* contributed by all arguments which are ref-to-ref-struct. This rule now lets us define the two variants of desired methods: ```c# Span CreateWithoutCapture(scoped ref int value) { // Error: value Rule 3 specifies that the safe-context be limited to the ref-safe-context // of the ref argument. That is the *function-member* for value hence this is not allowed. return new Span(ref value); } Span CreateAndCapture(ref int value) { // Okay: value Rule 3 specifies that the safe-context be limited to the ref-safe-context // of the ref argument. That is the *caller-context* for value hence this is not allowed. return new Span(ref value); } Span ComplexScopedRefExample(scoped ref Span span) { // Okay: the safe-context of `span` is *caller-context* hence this is legal. return span; // Okay: the local `refLocal` has a ref-safe-context of *function-member* and a // safe-context of *caller-context*. In the call below it is passed to a // parameter that is `scoped ref` which means it does not contribute // ref-safe-context. It only contributes its safe-context hence the returned // rvalue ends up as safe-context of *caller-context* Span local = default; ref Span refLocal = ref local; return ComplexScopedRefExample(ref refLocal); // Error: similar analysis as above but the safe-context of `stackLocal` is // *function-member* hence this is illegal Span stackLocal = stackalloc int[42]; return ComplexScopedRefExample(ref stackLocal); } ``` #### Rules for object initializers The *safe-context* of an object initializer expression is narrowest of: 1. The *safe-context* of the constructor call. 2. The *safe-context* and *ref-safe-context* of arguments to member initializer indexers that can escape to the receiver. 3. The *safe-context* of the RHS of assignments in member initializers to non-readonly setters or *ref-safe-context* in case of ref assignment. Another way of modeling this is to think of any argument to a member initializer that can be assigned to the receiver as being an argument to the constructor. This is because the member initializer is effectively a constructor call. ```c# Span heapSpan = default; Span stackSpan = stackalloc int[42]; var x = new S(ref heapSpan) { Field = stackSpan; } // Can be modeled as var x = new S(ref heapSpan, stackSpan); ``` This modeling is important because it demonstrates that our [MAMM](#rules-method-arguments-must-match) need to account specially for member initializers. Consider that this particular case needs to be illegal as it allows for a value with a narrower *safe-context* to be assigned to a higher one. ### Method arguments must match The presence of `ref` fields means the rules around method arguments must match need to be updated as a `ref` parameter can now be stored as a field in a `ref struct` argument to the method. Previously the rule only had to consider another `ref struct` being stored as a field. The impact of this is discussed in [the compat considerations](#compat-considerations). The new rule is ... > For any method invocation `e.M(a1, a2, ... aN)` > 1. Calculate the narrowest *safe-context* from: > - *caller-context* > - The *safe-context* of all arguments > - The *ref-safe-context* of all ref arguments whose corresponding parameters have a *ref-safe-context* of *caller-context* > 2. All `ref` arguments of `ref struct` types must be assignable by a value with that *safe-context*. This is a case where `ref` does **not** generalize to include `in` and `out` > For any method invocation `e.M(a1, a2, ... aN)` > 1. Calculate the narrowest *safe-context* from: > - *caller-context* > - The *safe-context* of all arguments > - The *ref-safe-context* of all ref arguments whose corresponding parameters are not `scoped` > 2. All `out` arguments of `ref struct` types must be assignable by a value with that *safe-context*. The presence of `scoped` allows developers to reduce the friction this rule creates by marking parameters which are not returned as `scoped`. This removes their arguments from (1) in both cases above and provides greater flexibility to callers. Impact of this change is discussed more deeply [below](#examples-method-arguments-must-match). Overall this will allow developers to make call sites more flexible by annotating non-escaping ref-like values with `scoped`. #### Parameter scope variance The `scoped` modifier and `[UnscopedRef]` attribute (see [below](#rules-unscoped)) on parameters also impacts our object overriding, interface implementation and `delegate` conversion rules. The signature for an override, interface implementation or `delegate` conversion can: - Add `scoped` to a `ref` or `in` parameter - Add `scoped` to a `ref struct` parameter - Remove `[UnscopedRef]` from an `out` parameter - Remove `[UnscopedRef]` from a `ref` parameter of a `ref struct` type Any other difference with respect to `scoped` or `[UnscopedRef]` is considered a mismatch. The compiler will report a diagnostic for _unsafe scoped mismatches_ across overrides, interface implementations, and delegate conversions when: - The method has a `ref` or `out` parameter of `ref struct` type with a mismatch of adding `[UnscopedRef]` (not removing `scoped`). (In this case, a [silly cyclic assignment](#cyclic-assignment) is possible, hence no other parameters are necessary.) - Or both of these are true: - The method returns a `ref struct` or returns a `ref` or `ref readonly`, or the method has a `ref` or `out` parameter of `ref struct` type. - The method has at least one additional `ref`, `in`, or `out` parameter, or a parameter of `ref struct` type. The diagnostic is not reported in other cases because: - The methods with such signatures cannot capture the refs passed in, so any scoped mismatch is not dangerous. - These include very common and simple scenarios (e.g., plain old `out` parameters which are used in `TryParse` method signatures) and reporting scoped mismatches just because they are used across language version 11 (and hence the `out` parameter is differently scoped) would be confusing. The diagnostic is reported as an _error_ if the mismatched signatures are both using C#11 ref safe context rules; otherwise, the diagnostic is a _warning_. The scoped mismatch warning may be reported on a module compiled with C#7.2 ref safe context rules where `scoped` is not available. In some such cases, it may be necessary to suppress the warning if the other mismatched signature cannot be modified. The `scoped` modifier and `[UnscopedRef]` attribute also have the following effects on method signatures: - The `scoped` modifier and `[UnscopedRef]` attribute do not affect hiding - Overloads cannot differ only on `scoped` or `[UnscopedRef]` The section on `ref` field and `scoped` is long so wanted to close with a brief summary of the proposed breaking changes: * A value that has *ref-safe-context* to the *caller-context* is returnable by `ref` or `ref` field. * A `out` parameter would have a *safe-context* of *function-member*. Detailed Notes: - A `ref` field can only be declared inside of a `ref struct` - A `ref` field cannot be declared `static`, `volatile` or `const` - A `ref` field cannot have a type that is `ref struct` - The reference assembly generation process must preserve the presence of a `ref` field inside a `ref struct` - A `readonly ref struct` must declare its `ref` fields as `readonly ref` - For by-ref values the `scoped` modifier must appear before `in`, `out`, or `ref` - The span safety rules document will be updated as outlined in this document - The new ref safe context rules will be in effect when either - The core library contains the feature flag indicating support for `ref` fields - The `langversion` value is 11 or higher ### Syntax [13.6.2 Local variable declarations](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1362-local-variable-declarations): added `'scoped'?`. ```antlr local_variable_declaration : 'scoped'? local_variable_mode_modifier? local_variable_type local_variable_declarators ; local_variable_mode_modifier : 'ref' 'readonly'? ; ``` [13.9.4 The `for` statement](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1394-the-for-statement): added `'scoped'?` _indirectly_ from `local_variable_declaration`. [13.9.5 The `foreach` statement](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement): added `'scoped'?`. ```antlr foreach_statement : 'foreach' '(' 'scoped'? local_variable_type identifier 'in' expression ')' embedded_statement ; ``` [12.6.2 Argument lists](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1262-argument-lists): added `'scoped'?` for `out` declaration variable. ```antlr argument_value : expression | 'in' variable_reference | 'ref' variable_reference | 'out' ('scoped'? local_variable_type)? identifier ; ``` [12.7 Deconstruction expressions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#127-deconstruction): ```antlr [TBD] ``` [15.6.2 Method parameters](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1562-method-parameters): added `'scoped'?` to `parameter_modifier`. ```antlr fixed_parameter : attributes? parameter_modifier? type identifier default_argument? ; parameter_modifier | 'this' 'scoped'? parameter_mode_modifier? | 'scoped' parameter_mode_modifier? | parameter_mode_modifier ; parameter_mode_modifier : 'in' | 'ref' | 'out' ; ``` [20.2 Delegate declarations](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/delegates.md#202-delegate-declarations): added `'scoped'?` _indirectly_ from `fixed_parameter`. [12.19 Anonymous function expressions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1219-anonymous-function-expressions): added `'scoped'?`. ```antlr explicit_anonymous_function_parameter : 'scoped'? anonymous_function_parameter_modifier? type identifier ; anonymous_function_parameter_modifier : 'in' | 'ref' | 'out' ; ``` ### Sunset restricted types The compiler has a concept of a set of "restricted types" which is largely undocumented. These types were given a special status because in C# 1.0 there was no general purpose way to express their behavior. Most notably the fact that the types can contain references to the execution stack. Instead the compiler had special knowledge of them and restricted their use to ways that would always be safe: disallowed returns, cannot use as array elements, cannot use in generics, etc ... Once `ref` fields are available and extended to support `ref struct` these types can be correctly defined in C# using a combination of `ref struct` and `ref` fields. Therefore when the compiler detects that a runtime supports `ref` fields it will no longer have a notion of restricted types. It will instead use the types as they are defined in the code. To support this our ref safe context rules will be updated as follows: - `__makeref` will be treated as a method with the signature `static TypedReference __makeref(ref T value)` - `__refvalue` will be treated as a method with the signature `static ref T __refvalue(TypedReference tr)`. The expression `__refvalue(tr, int)` will effectively use the second argument as the type parameter. - `__arglist` as a parameter will have a *ref-safe-context* and *safe-context* of *function-member*. - `__arglist(...)` as an expression will have a *ref-safe-context* and *safe-context* of *function-member*. Conforming runtimes will ensure that `TypedReference`, `RuntimeArgumentHandle` and `ArgIterator` are defined as `ref struct`. Further `TypedReference` must be viewed as having a `ref` field to a `ref struct` for any possible type (it can store any value). That combined with the above rules will ensure references to the stack do not escape beyond their lifetime. Note: strictly speaking this is a compiler implementation detail vs. part of the language. But given the relationship with `ref` fields it is being included in the language proposal for simplicity. ### Provide unscoped One of the most notable friction points is the inability to return fields by `ref` in instance members of a `struct`. This means developers can't create `ref` returning methods / properties and have to resort to exposing fields directly. This reduces the usefulness of `ref` returns in `struct` where it is often the most desired. ```c# struct S { int _field; // Error: this, and hence _field, can't return by ref public ref int Prop => ref _field; } ``` The [rationale](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md#struct-this-escape) for this default is reasonable but there is nothing inherently wrong with a `struct` escaping `this` by reference, it is simply the default chosen by the ref safe context rules. To fix this the language will provide the opposite of the `scoped` lifetime annotation by supporting an `UnscopedRefAttribute`. This can be applied to any `ref` and it will change the *ref-safe-context* to be one level wider than its default. For example: | UnscopedRef applied to | Original *ref-safe-context* | New *ref-safe-context* | | --- | --- | --- | | instance member | function-member | return-only | | `in` / `ref` parameter | return-only | caller-context | | `out` parameter | function-member | return-only | When applying `[UnscopedRef]` to an instance method of a `struct` it has the impact of modifying the implicit `this` parameter. This means `this` acts as an unannotated `ref` of the same type. ```c# struct S { int field; // Error: `field` has the ref-safe-context of `this` which is *function-member* because // it is a `scoped ref` ref int Prop1 => ref field; // Okay: `field` has the ref-safe-context of `this` which is *caller-context* because // it is a `ref` [UnscopedRef] ref int Prop1 => ref field; } ``` The annotation can also be placed on `out` parameters to restore them to C# 10 behavior. ```c# ref int SneakyOut([UnscopedRef] out int i) { i = 42; return ref i; } ``` For the purposes of ref safe context rules, such an `[UnscopedRef] out` is considered simply a `ref`. Similar to how `in` is considered `ref` for lifetime purposes. The `[UnscopedRef]` annotation will be disallowed on `init` members and constructors inside `struct`. Those members are already special with respect to `ref` semantics as they view `readonly` members as mutable. This means taking `ref` to those members appears as a simple `ref`, not `ref readonly`. This is allowed within the boundary of constructors and `init`. Allowing `[UnscopedRef]` would permit such a `ref` to incorrectly escape outside the constructor and permit mutation after `readonly` semantics had taken place. The attribute type will have the following definition: ```c# namespace System.Diagnostics.CodeAnalysis { [AttributeUsage( AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] public sealed class UnscopedRefAttribute : Attribute { } } ``` Detailed Notes: - An instance method or property annotated with `[UnscopedRef]` has *ref-safe-context* of `this` set to the *caller-context*. - A member annotated with `[UnscopedRef]` cannot implement an interface. - It is an error to use `[UnscopedRef]` on - A member that is not declared on a `struct` - A `static` member, `init` member or constructor on a `struct` - A parameter marked `scoped` - A parameter passed by value - A parameter passed by reference that is not implicitly scoped ### ScopedRefAttribute The `scoped` annotations will be emitted into metadata via the type `System.Runtime.CompilerServices.ScopedRefAttribute` attribute. The attribute will be matched by namespace-qualified name so the definition does not need to appear in any specific assembly. The `ScopedRefAttribute` type is for compiler use only - it is not permitted in source. The type declaration is synthesized by the compiler if not already included in the compilation. The type will have the following definition: ```c# namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] internal sealed class ScopedRefAttribute : Attribute { } } ``` The compiler will emit this attribute on the parameter with `scoped` syntax. This will only be emitted when the syntax causes the value to differ from its default state. For example `scoped out` will cause no attribute to be emitted. ### RefSafetyRulesAttribute There are several differences in the _ref safe context_ rules between C#7.2 and C#11. Any of these differences could result in breaking changes when recompiling with C#11 against references compiled with C#10 or earlier. 1. unscoped `ref`/`in`/`out` parameters may escape a method invocation as a `ref` field of a `ref struct` in C#11, not in C#7.2 1. `out` parameters are implicitly scoped in C#11, and unscoped in C#7.2 1. `ref`/`in` parameters to `ref struct` types are implicitly scoped in C#11, and unscoped in C#7.2 To reduce the chance of breaking changes when recompiling with C#11, we will update the C#11 compiler to use the ref safe context rules _for method invocation_ that _match the rules that were used to analyze the method declaration_. Essentially, when analyzing a call to a method compiled with an older compiler, the C#11 compiler will use C#7.2 ref safe context rules. To enable this, the compiler will emit a new `[module: RefSafetyRules(11)]` attribute when the module is compiled with `-langversion:11` or higher or compiled with a corlib containing the feature flag for `ref` fields. The argument to the attribute indicates the language version of the _ref safe context_ rules used when the module was compiled. The version is currently fixed at `11` regardless of the actual language version passed to the compiler. The expectation is that future versions of the compiler will update the ref safe context rules and emit attributes with distinct versions. If the compiler loads a module that includes a `[module: RefSafetyRules(version)]` _with a `version` other than `11`_, the compiler will report an error for the unrecognized version if there are any calls to methods declared in that module. When the C#11 compiler _analyzes a method call_: - If the module containing the method declaration includes `[module: RefSafetyRules(version)]`, where `version` is `11`, the method call is analyzed with C#11 rules. - If the module containing the method declaration is from source, and compiled with `-langversion:11` or with a corlib containing the feature flag for `ref` fields, the method call is analyzed with C#11 rules. - _If the module containing the method declaration references `System.Runtime { ver: 7.0 }`, the method call is analyzed with C#11 rules. This rule is a temporary mitigation for modules compiled with earlier previews of C#11 / .NET 7 and will be removed later._ - Otherwise, the method call is analyzed with C#7.2 rules. A pre-C#11 compiler will ignore any `RefSafetyRulesAttribute` and analyze method calls with C#7.2 rules only. The `RefSafetyRulesAttribute` will be matched by namespace-qualified name so the definition does not need to appear in any specific assembly. The `RefSafetyRulesAttribute` type is for compiler use only - it is not permitted in source. The type declaration is synthesized by the compiler if not already included in the compilation. ```csharp namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public RefSafetyRulesAttribute(int version) { Version = version; } public readonly int Version; } } ``` ### Safe fixed size buffers Safe fixed size buffers was not delivered in C# 11. This feature may be implemented in a future version of C#.
The language will relax the restrictions on fixed sized arrays such that they can be declared in safe code and the element type can be managed or unmanaged. This will make types like the following legal: ```c# internal struct CharBuffer { internal char Data[128]; } ``` These declarations, much like their `unsafe` counter parts, will define a sequence of `N` elements in the containing type. These members can be accessed with an indexer and can also be converted to `Span` and `ReadOnlySpan` instances. When indexing into a `fixed` buffer of type `T` the `readonly` state of the container must be taken into account. If the container is `readonly` then the indexer returns `ref readonly T` else it returns `ref T`. Accessing a `fixed` buffer without an indexer has no natural type however it is convertible to `Span` types. In the case the container is `readonly` the buffer is implicitly convertible to `ReadOnlySpan`, else it can implicitly convert to `Span` or `ReadOnlySpan` (the `Span` conversion is considered *better*). The resulting `Span` instance will have a length equal to the size declared on the `fixed` buffer. The *safe-context* of the returned value will be equal to the *safe-context* of the container, just as it would if the backing data was accessed as a field. For each `fixed` declaration in a type where the element type is `T` the language will generate a corresponding `get` only indexer method whose return type is `ref T`. The indexer will be annotated with the `[UnscopedRef]` attribute as the implementation will be returning fields of the declaring type. The accessibility of the member will match the accessibility on the `fixed` field. For example, the signature of the indexer for `CharBuffer.Data` will be the following: ```c# [UnscopedRef] internal ref char DataIndexer(int index) => ...; ``` If the provided index is outside the declared bounds of the `fixed` array then an `IndexOutOfRangeException` will be thrown. In the case a constant value is provided then it will be replaced with a direct reference to the appropriate element. Unless the constant is outside the declared bounds in which case a compile time error would occur. There will also be a named accessor generated for each `fixed` buffer that provides by value `get` and `set` operations. Having this means that `fixed` buffers will more closely resemble existing array semantics by having a `ref` accessor as well as byval `get` and `set` operations. This means compilers will have the same flexibility when emitting code consuming `fixed` buffers as they do when consuming arrays. This should make operations like `await` over `fixed` buffers easier to emit. This also has the added benefit that it will make `fixed` buffers easier to consume from other languages. Named indexers is a feature that has existed since the 1.0 release of .NET. Even languages which cannot directly emit a named indexer can generally consume them (C# is actually a good example of this). The backing storage for the buffer will be generated using the `[InlineArray]` attribute. This is a mechanism discussed in [issue 12320](https://github.com/dotnet/runtime/issues/12320) which allows specifically for the case of efficiently declaring sequence of fields of the same type. This particular issue is still under active discussion and the expectation is that the implementation of this feature will follow however that discussion goes.
### Initializers with `ref` values in `new` and `with` expressions In section [12.8.17.3 Object initializers](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128173-object-initializers), we update the grammar to: ```antlr initializer_value : 'ref' expression // added | expression | object_or_collection_initializer ; ``` In the section for [`with` expression](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/records.md#with-expression), we update the grammar to: ```antlr member_initializer : identifier '=' 'ref' expression // added | identifier '=' expression ; ``` The left operand of the assignment must be an expression that binds to a ref field. The right operand must be an expression that yields an lvalue designating a value of the same type as the left operand. We add a similar rule to [ref local reassignment](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.3/ref-local-reassignment.md): If the left operand is a writeable ref (i.e. it designates anything other than a `ref readonly` field), then the right operand must be a writeable lvalue. The escape rules for [constructor invocations](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md#constructor-invocations) remain: > A `new` expression that invokes a constructor obeys the same rules as a method invocation that is considered to return the type being constructed. Namely the rules of [method invocation](#rules-method-invocation) updated above: > An rvalue resulting from a method invocation `e1.M(e2, ...)` has *safe-context* from the smallest of the following contexts: > 1. The *caller-context* > 2. The *safe-context* contributed by all argument expressions > 3. When the return is a `ref struct` then *ref-safe-context* contributed by all `ref` arguments For a `new` expression with initializers, the initializer expressions count as arguments (they contribute their *safe-context*) and the `ref` initializer expressions count as `ref` arguments (they contribute their *ref-safe-context*), recursively. ## Changes in unsafe context Pointer types ([section 23.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#233-pointer-types)) are extended to allow managed types as referent type. Such pointer types are written as a managed type followed by a `*` token. They produce a warning. The address-of operator ([section 23.6.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2365-the-address-of-operator)) is relaxed to accept a variable with a managed type as its operand. The `fixed` statement ([section 23.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#237-the-fixed-statement)) is relaxed to accept _fixed_pointer_initializer_ that is the address of a variable of managed type `T` or that is an expression of an _array_type_ with elements of a managed type `T`. The stack allocation initializer ([section 12.8.22](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12822-stack-allocation)) is similarly relaxed. ## Considerations There are considerations other parts of the development stack should consider when evaluating this feature. ### Compat Considerations The challenge in this proposal is the compatibility implications this design has to our existing [span safety rules](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md), or [§9.7.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#972-ref-safe-contexts). While those rules fully support the concept of a `ref struct` having `ref` fields they do not allow for APIs, other than `stackalloc`, to capture `ref` state that refers to the stack. The ref safe context rules have a [hard assumption](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md#span-constructor), or [§16.4.12.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#164128-constructor-invocations) that a constructor of the form `Span(ref T value)` does not exist. That means the safety rules do not account for a `ref` parameter being able to escape as a `ref` field hence it allows for code like the following. ```c# Span CreateSpanOfInt() { // This is legal according to the 7.2 span rules because they do not account // for a constructor in the form Span(ref T value) existing. int local = 42; return new Span(ref local); } ``` Effectively there are three ways for a `ref` parameter to escape from a method invocation: 1. By value return 2. By `ref` return 3. By `ref` field in `ref struct` that is returned or passed as `ref` / `out` parameter The existing rules only account for (1) and (2). They do not account for (3) hence gaps like returning locals as `ref` fields are not accounted for. This design must change the rules to account for (3). This will have a small impact to compatibility for existing APIs. Specifically it will impact APIs that have the following properties. - Have a `ref struct` in the signature - Where the `ref struct` is a return type, `ref` or `out` parameter - Has an additional `in` or `ref` parameter excluding the receiver In C# 10 callers of such APIs never had to consider that `ref` state input to the API could be captured as a `ref` field. That allowed for several patterns to exist, safely in C# 10, that will be unsafe in C# 11 due to the ability for `ref` state to escape as a `ref` field. For example: ```c# Span CreateSpan(ref int parameter) { // The implementation of this method is irrelevant when considering the lifetime of the // returned Span. The ref safe context rules only look at the method signature, not the // implementation. In C# 10 ref fields didn't exist hence there was no way for `parameter` // to escape by ref in this method } Span BadUseExamples(int parameter) { // Legal in C# 10 but would be illegal with ref fields return CreateSpan(ref parameter); // Legal in C# 10 but would be illegal with ref fields int local = 42; return CreateSpan(ref local); // Legal in C# 10 but would be illegal with ref fields Span span = stackalloc int[42]; return CreateSpan(ref span[0]); } ``` The impact of this compatibility break is expected to be very small. The impacted API shape made little sense in the absence of `ref` fields hence it is unlikely customers created many of these. Experiments running tools to spot this API shape over existing repositories back up that assertion. The only repository with any significant counts of this shape is [dotnet/runtime](https://github.com/dotnet/runtime) and that is because that repo can create `ref` fields via the `ByReference` intrinsic type. Even so the design must account for such APIs existing because it expresses a valid pattern, just not a common one. Hence the design must give developers the tools to restore the existing lifetime rules when upgrading to C# 10. Specifically it must provide mechanisms that allow developers to annotate `ref` parameters as unable to escape by `ref` or `ref` field. That allows customers to define APIs in C# 11 that have the same C# 10 callsite rules. ### Reference Assemblies A reference assembly for a compilation using features described in this proposal must maintain the elements that convey ref safe context information. That means all lifetime annotation attributes must be preserved in their original position. Any attempt to replace or omit them can lead to invalid reference assemblies. Representing `ref` fields is more nuanced. Ideally a `ref` field would appear in a reference assembly as would any other field. However a `ref` field represents a change to the metadata format and that can cause issues with tool chains that are not updated to understand this metadata change. A concrete example is C++/CLI which will likely error if it consumes a `ref` field. Hence it's advantageous if `ref` fields can be omitted from reference assemblies in our core libraries. A `ref` field by itself has no impact on ref safe context rules. As a concrete example consider that flipping the existing `Span` definition to use a `ref` field has no impact on consumption. Hence the `ref` itself can be omitted safely. However a `ref` field does have other impacts to consumption that must be preserved: - A `ref struct` which has a `ref` field is never considered `unmanaged` - The type of the `ref` field impacts infinite generic expansion rules. Hence if the type of a `ref` field contains a type parameter that must be preserved Given those rules here is a valid reference assembly transformation for a `ref struct`: ```c# // Impl assembly ref struct S { ref T _field; } // Ref assembly ref struct S { object _o; // force managed T _f; // maintain generic expansion protections } ``` ### Annotations Lifetimes are most naturally expressed using types. A given program's lifetimes are safe when the lifetime types type check. While the syntax of C# implicitly adds lifetimes to values, there is an underlying type system that describes the fundamental rules here. It's often easier to discuss the implication of changes to the design in terms of these rules so they are included here for discussion sake. Note that this is not meant to be a 100% complete documentation. Documenting every single behavior isn't a goal here. Instead it's meant to establish a general understanding and common verbiage by which the model, and potential changes to it, can be discussed. Usually it's not necessary to directly talk about lifetime types. The exceptions are places where lifetimes can vary based on particular "instantiation" sites. This is a kind of polymorphism and we call these varying lifetimes "generic lifetimes", represented as generic parameters. C# does not provide syntax for expressing lifetime generics, so we define an implicit "translation" from C# to an expanded lowered language that contains explicit generic parameters. The below examples make use of named lifetimes. The syntax `$a` refers to a lifetime named `a`. It is a lifetime that has no meaning by itself but can be given a relationship to other lifetimes via the `where $a : $b` syntax. This establishes that `$a` is convertible to `$b`. It may help to think of this as establishing that `$a` is a lifetime at least as long as `$b`. There are a few predefined lifetimes for convenience and brevity below: - `$heap`: this is the lifetime of any value that exists on the heap. It is available in all contexts and method signatures. - `$local`: this is the lifetime of any value that exists on the method stack. It's effectively a name place holder for *function-member*. It is implicitly defined in methods and can appear in method signatures except for any output position. - `$ro`: name place holder for *return only* - `$cm`: name place holder for *caller-context* There are a few predefined relationships between lifetimes: - `where $heap : $a` for all lifetimes `$a` - `where $cm : $ro` - `where $x : $local` for all predefined lifetimes. User defined lifetimes have no relationship to local unless explicitly defined. Lifetime variables when defined on types can be invariant or covariant. These are expressed using the same syntax as generic parameters: ```csharp // $this is covariant // $a is invariant ref struct S ``` The lifetime parameter `$this` on type definitions is _not_ predefined but it does have a few rules associated with it when it is defined: - It must be the first lifetime parameter. - It must be covariant: `out $this`. - The lifetime of `ref` fields must be convertible to `$this` - The `$this` lifetime of all non-ref fields must be `$heap` or `$this`. The lifetime of a ref is expressed by providing a lifetime argument to the ref. For example a `ref` that refers to the heap is expressed as `ref<$heap>`. When defining a constructor in the model the name `new` will be used for the method. It is necessary to have a parameter list for the returned value as well as the constructor arguments. This is necessary to express the relationship between constructor inputs and the constructed value. Rather than having `Span<$a><$ro>` the model will use `Span<$a> new<$ro>` instead. The type of `this` in the constructor, including lifetimes, will be the defined return value. The basic rules for the lifetime are defined as: - All lifetimes are expressed syntactically as generic arguments, coming before type arguments. This is true for predefined lifetimes except `$heap` and `$local`. - All types `T` that are not a `ref struct` implicitly have lifetime of `T<$heap>`. This is implicit, there is no need to write `int<$heap>` in every sample. - For a `ref` field defined as `ref<$l0> T<$l1, $l2, ... $ln>`: - All lifetimes `$l1` through `$ln` must be invariant. - The lifetime of `$l0` must be convertible to `$this` - For a `ref` defined as `ref<$a> T<$b, ...>`, `$b` must be convertible to `$a` - The `ref` of a variable has a lifetime defined by: - For a `ref` local, parameter, field or return of type `ref<$a> T` the lifetime is `$a` - `$heap` for all reference types and fields of reference types - `$local` for everything else - An assignment or return is legal when the underlying type conversion is legal - Lifetimes of expressions can be made explicit by using cast annotations: - `(T<$a> expr)` the value lifetime is explicitly `$a` for `T<...>` - `ref<$a> (T<$b>)expr` the value lifetime is `$b` for `T<...>` and the ref lifetime is `$a`. For the purpose of lifetime rules a `ref` is considered part of the type of the expression for purposes of conversions. It is logically represented by converting `ref<$a> T<...>` to `ref<$a, T<...>>` where `$a` is covariant and `T` is invariant. Next let's define the rules that allow us to map C# syntax to the underlying model. For brevity sake a type which has no explicit lifetime parameters treated as if there is `out $this` defined and applied to all fields of the type. A type with a `ref` field must define explicit lifetime parameters. These rules exists to support our existing invariant that `T` can be assigned to `scoped T` for all types. That maps down to `T<$a, ...>` being assignable to `T<$local, ...>` for all lifetimes known to be convertible to `$local`. Further this supports other items like being able to assign `Span` from the heap to those on the stack. This does exclude types where fields have differing lifetimes for non-ref values but that is the reality of C# today. Changing that would require a significant change of C# rules that would need to be mapped out. The type of `this` for a type `S` inside an instance method is implicitly defined as the following: - For normal instance method: `ref<$local> S<$cm, ...>` - For instance method annotated with `[UnscopedRef]`: `ref<$ro> S<$cm, ...>` The lack of an explicit `this` parameter forces the implicit rules here. For complex samples and discussions consider writing as a `static` method and making `this` an explicit parameter. ```csharp ref struct S { // Implicit this can make discussion confusing void M<$ro, $cm>(ref<$ro> S<$cm> s) { } // Rewrite as explicit this to simplify discussion static void M<$ro, $cm>(ref<$local> S<$cm> this, ref<$ro> S<$cm> s) { } } ``` The C# method syntax maps to the model in the following ways: - `ref` parameters have a ref lifetime of `$ro` - parameters of type `ref struct` have a this lifetime of `$cm` - ref returns have a ref lifetime of `$ro` - returns of type `ref struct` have a value lifetime of `$ro` - `scoped` on a parameter or `ref` changes the ref lifetime to be `$local` Given that let's explore a simple example that demonstrates the model here: ```csharp ref int M1(ref int i) => ... // Maps to the following. ref<$ro> int Identity<$ro>(ref<$ro> int i) { // okay: has ref lifetime $ro which is equal to $ro return ref i; // okay: has ref lifetime $heap which convertible $ro int[] array = new int[42]; return ref array[0]; // error: has ref lifetime $local which has no conversion to $a hence // it's illegal int local = 42; return ref local; } ``` Now let's explore the same example using a `ref struct`: ```csharp ref struct S { ref int Field; S(ref int f) { Field = ref f; } } S M2(ref int i, S span1, scoped S span2) => ... // Maps to ref struct S { // Implicitly ref<$this> int Field; S<$ro> new<$ro>(ref<$ro> int f) { Field = ref f; } } S<$ro> M2<$ro>( ref<$ro> int i, S<$ro> span1) S<$local> span2) { // okay: types match exactly return span1; // error: has lifetime $local which has no conversion to $ro return span2; // okay: type S<$heap> has a conversion to S<$ro> because $heap has a // conversion to $ro and the first lifetime parameter of S<> is covariant return default(S<$heap>) // okay: the ref lifetime of ref $i is $ro so this is just an // identity conversion S<$ro> local = new S<$ro>(ref $i); return local; int[] array = new int[42]; // okay: S<$heap> is convertible to S<$ro> return new S<$heap>(ref<$heap> array[0]); // okay: the parameter of the ctor is $ro ref int and the argument is $heap ref int. These // are convertible. return new S<$ro>(ref<$heap> array[0]); // error: has ref lifetime $local which has no conversion to $a hence // it's illegal int local = 42; return ref local; } ``` Next let's see how this helps with the cyclic self assignment problem: ```csharp ref struct S { int field; ref int refField; static void SelfAssign(ref S s) { s.refField = ref s.field; } } // Maps to ref struct S { int field; ref<$this> int refField; static void SelfAssign<$ro, $cm>(ref<$ro> S<$cm> s) { // error: the types work out here to ref<$cm> int = ref<$ro> int and that is // illegal as $ro has no conversion to $cm (the relationship is the other direction) s.refField = ref<$ro> s.field; } } ``` Next let's see how this helps with the silly capture parameter problem: ```csharp ref struct S { ref int refField; void Use(ref int parameter) { // error: this needs to be an error else every call to this.Use(ref local) would fail // because compiler would assume the `ref` was captured by ref. this.refField = ref parameter; } } // Maps to ref struct S { ref<$this> int refField; // Using static form of this method signature so the type of this is explicit. static void Use<$ro, $cm>(ref<$local> S<$cm> @this, ref<$ro> int parameter) { // error: the types here are: // - refField is ref<$cm> int // - ref parameter is ref<$ro> int // That means the RHS is not convertible to the LHS ($ro is not covertible to $cm) and // hence this reassignment is illegal @this.refField = ref<$ro> parameter; } } ``` ## Open Issues ### Change the design to avoid compat breaks This design proposes several compatibility breaks with our existing ref-safe-context rules. Even though the breaks are believed to be minimally impactful significant consideration was given to a design which had no breaking changes. The compat preserving design though was significantly more complex than this one. In order to preserve compat `ref` fields need distinct lifetimes for the ability to return by `ref` and return by `ref` field. Essentially it requires us to provide *ref-field-safe-context* tracking for all parameters to a method. This needs to be calculated for all expressions and tracked in all values virtually everywhere that *ref-safe-context* is tracked today. Further this value has relationships with *ref-safe-context*. For example it's non-sensical to have a value can be returned as a `ref` field but not directly as `ref`. That is because `ref` fields can be trivially returned by `ref` already (`ref` state in a `ref struct` can be returned by `ref` even when the containing value cannot). Hence the rules further need constant adjustment to ensure these values are sensible with respect to each other. Also it means the language needs syntax to represent `ref` parameters that can be returned in three different ways: by `ref` field, by `ref` and by value. The default being returnable by `ref`. Going forward though the more natural return, particularly when `ref struct` are involved is expected to be by `ref` field or `ref`. That means new APIs require an extra syntax annotation to be correct by default. This is undesirable. These compat changes though will impact methods that have the following properties: - Have a `Span` or `ref struct` - Where the `ref struct` is a return type, `ref` or `out` parameter - Has an additional `in` or `ref` parameter (excluding the receiver) To understand the impact it's helpful to break APIs into categories: 1. Want consumers to account for `ref` being captured as a `ref` field. Prime example is the `Span(ref T value)` constructors 2. Do not want consumers to account for `ref` being captured as a `ref` field. These though break into two categories 1. Unsafe APIs. These are APIS inside the `Unsafe` and `MemoryMarshal` types, of which `MemoryMarshal.CreateSpan` is the most prominent. These APIs do capture the `ref` unsafely but they are also known to be unsafe APIs. 2. Safe APIs. These are APIs which take `ref` parameters for efficiency but it is not actually captured anywhere. Examples are small but one is `AsnDecoder.ReadEnumeratedBytes` This change primarily benefits (1) above. These are expected to make up the majority of APIs that take a `ref` and return a `ref struct` going forward. The changes negatively impact (2.1) and (2.2) as it breaks the existing calling semantics because the lifetime rules change. The APIs in category (2.1) though are largely authored by Microsoft or by developers who stand the most to benefit from `ref` fields (the Tanner's of the world). It is reasonable to assume this class of developers would be amenable to a compatibility tax on upgrade to C# 11 in the form of a few annotations to retain the existing semantics if `ref` fields were provided in return. The APIs in category (2.2) are the biggest issue. It is unknown how many such APIs exist and it's unclear if these would be more / less frequent in 3rd party code. The expectation is there is a very small number of them, particularly if we take the compat break on `out`. Searches so far have revealed a very small number of these existing in `public` surface area. This is a hard pattern to search for though as it requires semantic analysis. Before taking this change a tool based approach would be needed to verify the assumptions around this impacting a small number of known cases. For both cases in category (2) though the fix is straight forward. The `ref` parameters that do not want to be considered capturable must add `scoped` to the `ref`. In (2.1) this will likely also force the developer to use `Unsafe` or `MemoryMarshal` but that is expected for unsafe style APIs. Ideally the language could reduce the impact of silent breaking changes by issuing a warning when an API silently falls into the troublesome behavior. That would be a method that both takes a `ref`, returns `ref struct` but does not actually capture the `ref` in the `ref struct`. The compiler could issue a diagnostic in that case informing developers such `ref` should be annotated as `scoped ref` instead. **Decision** This design can be achieved but the resulting feature is more difficult to use to the point the decision was made to take the compat break. **Decision** The compiler will provide a warning when a method meets the criteria but does not capture the `ref` parameter as a `ref` field. This should suitably warn customers on upgrade about the potential issues they are creating ### Keywords vs. attributes This design calls for using attributes to annotate the new lifetime rules. This also could've been done just as easily with contextual keywords. For instance `[DoesNotEscape]` could map to `scoped`. However keywords, even the contextual ones, generally must meet a very high bar for inclusion. They take up valuable language real estate and are more prominent parts of the language. This feature, while valuable, is going to serve a minority of C# developers. On the surface that would seem to favor not using keywords but there are two important points to consider: 1. The annotations will effect program semantics. Having attributes impact program semantics is a line C# is reluctant to cross and it's unclear if this is the feature that should justify the language taking that step. 1. The developers most likely to use this feature intersect strongly with the set of developers that use function pointers. That feature, while also used by a minority of developers, did warrant a new syntax and that decision is still seen as sound. Taken together this means syntax should be considered. A rough sketch of the syntax would be: - `[RefDoesNotEscape]` maps to `scoped ref` - `[DoesNotEscape]` maps to `scoped` - `[RefDoesEscape]` maps to `unscoped` **Decision** Use syntax for `scoped` and `scoped ref`; use attribute for `unscoped`. ### Allow fixed buffer locals This design allows for safe `fixed` buffers that can support any type. One possible extension here is allowing such `fixed` buffers to be declared as local variables. This would allow a number of existing `stackalloc` operations to be replaced with a `fixed` buffer. It would also expand the set of scenarios we could have stack style allocations as `stackalloc` is limited to unmanaged element types while `fixed` buffers are not. ```c# class FixedBufferLocals { void Example() { Span span = stackalloc int[42]; int buffer[42]; } } ``` This holds together but does require us to extend the syntax for locals a bit. Unclear if this is or isn't worth the extra complexity. Possible we could decide no for now and bring back later if sufficient need is demonstrated. Example of where this would be beneficial: https://github.com/dotnet/runtime/pull/34149 **Decision** hold off on this for now ### To use modreqs or not A decision needs to be made if methods marked with new lifetime attributes should or should not translate to `modreq` in emit. There would be effectively a 1:1 mapping between annotations and `modreq` if this approach was taken. The rationale for adding a `modreq` is the attributes change the semantics of ref safe context rules. Only languages which understand these semantics should be calling the methods in question. Further when applied to OHI scenarios, the lifetimes become a contract that all derived methods must implement. Having the annotations exist without `modreq` can lead to situations where `virtual` method chains with conflicting lifetime annotations are loaded (can happen if only one part of `virtual` chain is compiled and other is not). The initial ref safe context work did not use `modreq` but instead relied on languages and the framework to understand. At the same time though all of the elements that contribute to the ref safe context rules are a strong part of the method signature: `ref`, `in`, `ref struct`, etc ... Hence any change to the existing rules of a method already results in a binary change to the signature. To give the new lifetime annotations the same impact they will need `modreq` enforcement. The concern is whether or not this is overkill. It does have the negative impact that making signatures more flexible, by say adding `[DoesNotEscape]` to a parameter, will result in a binary compat change. That trade off means that over time frameworks like BCL likely won't be able to relax such signatures. It could be mitigated to a degree by taking some approach the language does with `in` parameters and only apply `modreq` in virtual positions. **Decision** Do not use `modreq` in metadata. The difference between `out` and `ref` is not `modreq` but they now have different ref safe context values. There is no real benefit to only half enforcing the rules with `modreq` here. ### Allow multi-dimensional fixed buffers Should the design for `fixed` buffers be extended to include multi-dimensional style arrays? Essentially allowing for declarations like the following: ```c# struct Dimensions { int array[42, 13]; } ``` **Decision** Do not allow for now ### Violating scoped The runtime repository has several non-public APIs that capture `ref` parameters as `ref` fields. These are unsafe because the lifetime of the resulting value is not tracked. For example the `Span(ref T value, int length)` constructor. The majority of these APIs will likely choose to have proper lifetime tracking on the return which will be achieved simply by updating to C# 11. A few though will want to keep their current semantics of not tracking the return value because their entire intent is to be unsafe. The most notable examples are `MemoryMarshal.CreateSpan` and `MemoryMarshal.CreateReadOnlySpan`. This will be achieved by marking the parameters as `scoped`. That means the runtime needs an established pattern for unsafely removing `scoped` from a parameter: 1. `Unsafe.AsRef(in T value)` could expand its existing purpose by changing to `scoped in T value`. This would allow it to both remove `in` and `scoped` from parameters. It then becomes the universal "remove ref safety" method 2. Introduce a new method whose entire purpose is to remove `scoped`: `ref T Unsafe.AsUnscoped(scoped in T value)`. This removes `in` as well because if it did not then callers still need a combination of method calls to "remove ref safety" at which point the existing solution is likely sufficient. ### Unscoped this by default? The design only has two locations which are `scoped` by default: - `this` is `scoped ref` - `out` is `scoped ref` The decision on `out` is to significantly reduce the compat burden of `ref` fields and at the same time is a more natural default. It lets developers actually think of `out` as data flowing outward only where as if it's `ref` then the rules must consider data flowing in both directions. This leads to significant developer confusion. The decision on `this` is undesirable because it means a `struct` cannot return a field by `ref`. This is an important scenario to high perf developers and the `[UnscopedRef]` attribute was added essentially for this one scenario. Keywords have a high bar and adding it for a single scenario is suspect. As such thought was given to whether we could avoid this keyword at all by making `this` simply `ref` by default and not `scoped ref`. All members that need `this` to be `scoped ref` could do so by marking the method `scoped` (as a method can be marked `readonly` to create a `readonly ref` today). On a normal `struct` this is mostly a positive change as it only introduces compat issues when a member has a `ref` return. There are **very** few of these methods and a tool could spot these and convert them to `scoped` members quickly. On a `ref struct` this change introduces significantly bigger compat issues. Consider the following: ```c# ref struct Sneaky { int Field; ref int RefField; public void SelfAssign() { // This pattern of ref reassign to fields on this inside instance methods would now // completely legal. RefField = ref Field; } static Sneaky UseExample() { Sneaky local = default; // Error: this is illegal, and must be illegal, by our existing rules as the // ref-safe-context of local is now an input into method arguments must match. local.SelfAssign(); // This would be dangerous as local now has a dangerous `ref` but the above // prevents us from getting here. return local; } } ``` Essentially it would mean all instance method invocations on *mutable* `ref struct` locals would be illegal unless the local was further marked as `scoped`. The rules have to consider the case where fields were ref reassigned to other fields in `this`. A `readonly ref struct` doesn't have this problem because the `readonly` nature prevents ref reassignment. Still this would be a significant back compat breaking change as it would impact virtually every existing mutable `ref struct`. A `readonly ref struct` though is still problematic once we expand to having `ref` fields to `ref struct`. It allows for the same basic problem by just moving the capture into the value of the `ref` field: ```c# readonly ref struct ReadOnlySneaky { readonly int Field; readonly ref ReadOnlySpan Span; public void SelfAssign() { // Instance method captures a ref to itself Span = new ReadOnlySpan(ref Field, 1); } } ``` Some thought was given to the idea of having `this` have different defaults based on the type of `struct` or member. For example: - `this` as `ref`: `struct`, `readonly ref struct` or `readonly member` - `this` as `scoped ref`: `ref struct` or `readonly ref struct` with `ref` field to `ref struct` This minimizes compat breaks and maximizes flexibility but at the cost of complicating the story for customers. It also doesn't fully solve the problem because future features, like safe `fixed` buffers, require that a mutable `ref struct` have `ref` returns for fields which don't work by this design alone as it would fall into the `scoped ref` category. **Decision** Keep `this` as `scoped ref`. That means the preceding sneaky examples produce compiler errors. ### ref fields to ref struct This feature opens up a new set of ref safe context rules because it allows for a `ref` field to refer to a `ref struct`. This generic nature of `ByReference` meant that up until now the runtime could not have such a construct. As a result all of our rules are written under the assumption this is not possible. The `ref` field feature is largely not about making new rules but codifying the existing rules in our system. Allowing `ref` fields to `ref struct` requires us to codify new rules because there are several new scenarios to consider. The first is that a `readonly ref` is now capable of storing `ref` state. For example: ```c# readonly ref struct Container { readonly ref Span Span; void Store(Span span) { Span = span; } } ``` This means when thinking about method arguments must match rules we must consider `readonly ref T` is potential method output when `T` potentially has a `ref` field to a `ref struct`. The second issue is language must consider a new type of safe context: *ref-field-safe-context*. All `ref struct` which transitively contain a `ref` field have another escape scope representing the value(s) in the `ref` field(s). In the case of multiple `ref` fields they can be collectively tracked as a single value. The default value for this for parameters is *caller-context*. ```c# ref struct Nested { ref Span Span; } Span M(ref Nested nested) => nested.Span; ``` This value is not related to the *safe-context* of the container; that is as the container context gets smaller it has no impact on the *ref-field-safe-context* of the `ref` field values. Further the *ref-field-safe-context* can never be smaller than the *safe-context* of the container. ```c# ref struct Nested { ref Span Span; } void M(ref Nested nested) { scoped ref Nested refLocal = ref nested; // the ref-field-safe-context of local is still *caller-context* which means the following // is illegal refLocal.Span = stackalloc int[42]; scoped Nested valLocal = nested; // the ref-field-safe-context of local is still *caller-context* which means the following // is still illegal valLocal.Span = stackalloc int[42]; } ``` This *ref-field-safe-context* has essentially always existed. Up until now `ref` fields could only point to normal `struct` hence it was trivially collapsed to *caller-context*. To support `ref` fields to `ref struct` our existing rules need to be updated to take into account this new *ref-safe-context*. Third the rules for ref reassignment need to be updated to ensure that we don't violate *ref-field-context* for the values. Essentially for `x.e1 = ref e2` where the type of `e1` is a `ref struct` the *ref-field-safe-context* must be equal. These problems are very solvable. The compiler team has sketched out a few versions of these rules and they largely fall out from our existing analysis. The problem is there is no consuming code for such rules that helps prove out there correctness and usability. This makes us very hesitant to add support because of the fear we'll pick wrong defaults and back the runtime into usability corner when it does take advantage of this. This concern is particularly strong because .NET 8 likely pushes us in this direction with `allow T: ref struct` and `Span>`. The rules would be better written if it's done in conjunction with consumption code. **Decision** Delay allowing `ref` field to `ref struct` until .NET 8 where we have scenarios that will help drive the rules around these scenarios. This has not been implemented as of .NET 9 ### What will make C# 11.0? The features outlined in this document don't need to be implemented in a single pass. Instead they can be implemented in phases across several language releases in the following buckets: 1. `ref` fields and `scoped` 2. `[UnscopedRef]` 3. `ref` fields to `ref struct` 4. Sunset restricted types 5. fixed sized buffers What gets implemented in which release is merely a scoping exercise. **Decision** Only (1) and (2) made C# 11.0. The rest will be considered in future versions of C#. ## Future Considerations ### Advanced lifetime annotations The lifetime annotations in this proposal are limited in that they allow developers to change the default escape / don't escape behavior of values. This does add powerful flexibility to our model but it does not radically change the set of relationships that can be expressed. At the core the C# model is still effectively binary: can a value be returned or not? That allows limited lifetime relationships to be understood. For example a value that can't be returned from a method has a smaller lifetime than one that can be returned from a method. There is no way to describe the lifetime relationship between values that can be returned from a method though. Specifically there is no way to say that one value has a larger lifetime than the other once it's established both can be returned from a method. The next step in our lifetime evolution would be allowing such relationships to be described. Other methods such as Rust allow this type of relationship to be expressed and hence can implement more complex `scoped` style operations. Our language could similarly benefit if such a feature were included. At the moment there is no motivating pressure to do this but if there is in the future our `scoped` model could be expanded to include it in a fairly straight forward fashion. Every `scoped` could be assigned a named lifetime by adding a generic style argument to the syntax. For example `scoped<'a>` is a value that has lifetime `'a`. Constraints like `where` could then be used to describe the relationships between these lifetimes. ```c# void M(scoped<'a> ref MyStruct s, scoped<'b> Span span) where 'b >= 'a { s.Span = span; } ``` This method defines two lifetimes `'a` and `'b` and their relationship, specifically that `'b` is greater than `'a`. This allows for the callsite to have more granular rules for how values can be safely passed into methods vs. the more coarse grained rules present today. ## Related Information ### Issues The following issues are all related to this proposal: - https://github.com/dotnet/csharplang/issues/1130 - https://github.com/dotnet/csharplang/issues/1147 - https://github.com/dotnet/csharplang/issues/992 - https://github.com/dotnet/csharplang/issues/1314 - https://github.com/dotnet/csharplang/issues/2208 - https://github.com/dotnet/runtime/issues/32060 - https://github.com/dotnet/runtime/issues/61135 - https://github.com/dotnet/csharplang/discussions/78 ### Proposals The following proposals are related to this proposal: - https://github.com/dotnet/csharplang/blob/725763343ad44a9251b03814e6897d87fe553769/proposals/fixed-sized-buffers.md ### Existing samples [Utf8JsonReader](https://github.com/dotnet/runtime/blob/f1a7cb3fdd7ffc4ce7d996b7ac6867ffe2c953b9/src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs#L523-L528) This particular snippet requires unsafe because it runs into issues with passing around a `Span` which can be stack allocated to an instance method on a `ref struct`. Even though this parameter is not captured the language must assume it is and hence needlessly causes friction here. [Utf8JsonWriter](https://github.com/dotnet/runtime/blob/f1a7cb3fdd7ffc4ce7d996b7ac6867ffe2c953b9/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs#L122-L127) This snippet wants to mutate a parameter by escaping elements of the data. The escaped data can be stack allocated for efficiency. Even though the parameter is not escaped the compiler assigns it a *safe-context* of outside the enclosing method because it is a parameter. This means in order to use stack allocation the implementation must use `unsafe` in order to assign back to the parameter after escaping the data. ### Fun Samples #### ReadOnlySpan\ ```c# public readonly ref struct ReadOnlySpan { readonly ref readonly T _value; readonly int _length; public ReadOnlySpan(in T value) { _value = ref value; _length = 1; } } ``` #### Frugal list ```c# struct FrugalList { private T _item0; private T _item1; private T _item2; public int Count = 3; public FrugalList(){} public ref T this[int index] { [UnscopedRef] get { switch (index) { case 0: return ref _item0; case 1: return ref _item1; case 2: return ref _item2; default: throw null; } } } } ``` ### Examples and Notes Below are a set of examples demonstrating how and why the rules work the way they do. Included are several examples showing dangerous behaviors and how the rules prevent them from happening. It's important to keep these in mind when making adjustments to the proposal. #### Ref reassignment and call sites Demonstrating how [ref reassignment](#rules-ref-reassignment) and [method invocation](#rules-method-invocation) work together. ```c# ref struct RS { ref int _refField; public ref int Prop => ref _refField; public RS(int[] array) { _refField = ref array[0]; } public RS(ref int i) { _refField = ref i; } public RS CreateRS() => ...; public ref int M1(RS rs) { // The call site arguments for Prop contribute here: // - `rs` contributes no ref-safe-context as the corresponding parameter, // which is `this`, is `scoped ref` // - `rs` contribute safe-context of *caller-context* // // This is an lvalue invocation and the arguments contribute only safe-context // values of *caller-context*. That means `local1` has ref-safe-context of // *caller-context* ref int local1 = ref rs.Prop; // Okay: this is legal because `local` has ref-safe-context of *caller-context* return ref local1; // The arguments contribute here: // - `this` contributes no ref-safe-context as the corresponding parameter // is `scoped ref` // - `this` contributes safe-context of *caller-context* // // This is an rvalue invocation and following those rules the safe-context of // `local2` will be *caller-context* RS local2 = CreateRS(); // Okay: this follows the same analysis as `ref rs.Prop` above return ref local2.Prop; // The arguments contribute here: // - `local3` contributes ref-safe-context of *function-member* // - `local3` contributes safe-context of *caller-context* // // This is an rvalue invocation which returns a `ref struct` and following those // rules the safe-context of `local4` will be *function-member* int local3 = 42; var local4 = new RS(ref local3); // Error: // The arguments contribute here: // - `local4` contributes no ref-safe-context as the corresponding parameter // is `scoped ref` // - `local4` contributes safe-context of *function-member* // // This is an lvalue invocation and following those rules the ref-safe-context // of the return is *function-member* return ref local4.Prop; } } ``` #### Ref reassignment and unsafe escapes The reason for the following line in the [ref reassignment rules](#rules-ref-reassignment) may not be obvious at first glance: > `e1` must have the same *safe-context* as `e2` This is because the lifetime of the values pointed to by `ref` locations are invariant. The indirection prevents us from allowing any kind of variance here, even to narrower lifetimes. If narrowing is allowed then it opens up the following unsafe code: ```csharp void Example(ref Span p) { Span local = stackalloc int[42]; ref Span refLocal = ref local; // Error: // The safe-context of refLocal is narrower than p. For a non-ref reassignment // this would be allowed as its safe to assign wider lifetimes to narrower ones. // In the case of ref reassignment though this rule prevents it as the // safe-context values are different. refLocal = ref p; // If it were allowed this would be legal as the safe-context of refLocal // is *caller-context* and that is satisfied by stackalloc. At the same time // it would be assigning through p and escaping the stackalloc to the calling // method // // This is equivalent of saying p = stackalloc int[13]!!! refLocal = stackalloc int[13]; } ``` For a `ref` to non `ref struct` this rule is trivially satisfied as the values all have the same *safe-context*. This rule really only comes into play when the value is a `ref struct`. This behavior of `ref` will also be important in a future where we allow `ref` fields to `ref struct`. #### scoped locals The use of `scoped` on locals will be particularly helpful to code patterns which conditionally assign values with different *safe-context* to locals. It means code no longer needs to rely on initialization tricks like `= stackalloc byte[0]` to define a local *safe-context* but now can simply use `scoped`. ```c# // Old way // Span span = stackalloc byte[0]; // New way scoped Span span; int len = ...; if (len < MaxStackLen) { span = stackalloc byte[len]; } else { span = new byte[len]; } ``` This pattern comes up frequently in low level code. When the `ref struct` involved is `Span` the above trick can be used. It is not applicable to other `ref struct` types though and can result in low level code needing to resort to `unsafe` to work around the inability to properly specify the lifetime. #### scoped parameter values One source of repeated friction in low level code is the default escape for parameters is permissive. They are *safe-context* to the *caller-context*. This is a sensible default because it lines up with the coding patterns of .NET as a whole. In low level code though there is a larger use of `ref struct` and this default can cause friction with other parts of the ref safe context rules. The main friction point occurs because of the [method arguments must match](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md#method-arguments-must-match) rule. This rule most commonly comes into play with instance methods on `ref struct` where at least one parameter is also a `ref struct`. This is a common pattern in low level code where `ref struct` types commonly leverage `Span` parameters in their methods. For example it will occur on any writer style `ref struct` that uses `Span` to pass around buffers. This rule exists to prevent scenarios like the following: ```c# ref struct RS { Span _field; void Set(Span p) { _field = p; } static void DangerousCode(ref RS p) { Span span = stackalloc int[] { 42 }; // Error: if allowed this would let the method return a reference to // the stack p.Set(span); } } ``` Essentially this rule exists because the language must assume that all inputs to a method escape to their maximum allowed *safe-context*. When there are `ref` or `out` parameters, including the receivers, it's possible for the inputs to escape as fields of those `ref` values (as happens in `RS.Set` above). In practice though there are many such methods which pass `ref struct` as parameters that never intend to capture them in output. It is just a value that is used within the current method. For example: ```c# ref struct JsonReader { Span _buffer; int _position; internal bool TextEquals(ReadOnlySpan text) { var current = _buffer.Slice(_position, text.Length); return current == text; } } class C { static void M(ref JsonReader reader) { Span span = stackalloc char[4]; span[0] = 'd'; span[1] = 'o'; span[2] = 'g'; // Error: The safe-context of `span` is function-member // while `reader` is outside function-member hence this fails // by the above rule. if (reader.TextEquals(span)) { ... } } } ``` In order to work around this low level code will resort to `unsafe` tricks to lie to the compiler about the lifetime of their `ref struct`. This significantly reduces the value proposition of `ref struct` as they are meant to be a means to avoid `unsafe` while continuing to write high performance code. This is where `scoped` is an effective tool on `ref struct` parameters because it removes them from consideration as being returned from the method according to the updated [method arguments must match rule](#rules-method-arguments-must-match). A `ref struct` parameter which is consumed, but never returned, can be labeled as `scoped` to make call sites more flexible. ```c# ref struct JsonReader { Span _buffer; int _position; internal bool TextEquals(scoped ReadOnlySpan text) { var current = _buffer.Slice(_position, text.Length); return current == text; } } class C { static void M(ref JsonReader reader) { Span span = stackalloc char[4]; span[0] = 'd'; span[1] = 'o'; span[2] = 'g'; // Okay: the compiler never considers `span` as capturable here hence it doesn't // contribute to the method arguments must match rule if (reader.TextEquals(span)) { ... } } } ``` #### Preventing tricky ref assignment from readonly mutation When a `ref` is taken to a `readonly` field in a constructor or `init` member the type is `ref` not `ref readonly`. This is a long standing behavior that allows for code like the following: ```c# struct S { readonly int i; public S(string s) { M(ref i); } static void M(ref int i) { } } ``` That does pose a potential problem though if such a `ref` were able to be stored into a `ref` field on the same type. It would allow for direct mutation of a `readonly struct` from an instance member: ```c# readonly ref struct S { readonly int i; readonly ref int r; public S() { i = 0; // Error: `i` has a narrower scope than `r` r = ref i; } public void Oops() { r++; } } ``` The proposal prevents this though because it violates the ref safe context rules. Consider the following: - The *ref-safe-context* of `this` is *function-member* and *safe-context* is *caller-context*. These are both standard for `this` in a `struct` member. - The *ref-safe-context* of `i` is *function-member*. This falls out from the [field lifetimes rules](#rules-field-lifetimes). Specifically rule 4. At that point the line `r = ref i` is illegal by [ref reassignment rules](#rules-ref-reassignment). These rules were not intended to prevent this behavior but do so as a side effect. It's important to keep this in mind for any future rule update to evaluate the impact to scenarios like this. #### Silly cyclic assignment One aspect this design struggled with is how freely a `ref` can be returned from a method. Allowing all `ref` to be returned as freely as normal values is likely what most developers intuitively expect. However it allows for pathological scenarios that the compiler must consider when calculating ref safety. Consider the following: ```c# ref struct S { int field; ref int refField; static void SelfAssign(ref S s) { // Error: s.field can only escape the current method through a return statement s.refField = ref s.field; } } ``` This is not a code pattern that we expect any developers to use. Yet when a `ref` can be returned with the same lifetime as a value it is legal under the rules. The compiler must consider all legal cases when evaluating a method call and this leads to such APIs being effectively unusable. ```c# void M(ref S s) { ... } void Usage() { // safe-context to caller-context S local = default; // Error: compiler is forced to assume the worst and concludes a self assignment // is possible here and must issue an error. M(ref local); } ``` To make these APIs usable the compiler ensures that the `ref` lifetime for a `ref` parameter is smaller than lifetime of any references in the associated parameter value. This is the rationale for having *ref-safe-context* for `ref` to `ref struct` be *return-only* and `out` be *caller-context*. That prevents cyclic assignment because of the difference in lifetimes. Note that `[UnscopedRef]` [promotes](#rules-unscoped) the *ref-safe-context* of any `ref` to `ref struct` values to *caller-context* and hence it allows for cyclic assignment and forces a viral use of `[UnscopedRef]` up the call chain: ```c# S F() { S local = new(); // Error: self assignment possible inside `S.M`. S.M(ref local); return local; } ref struct S { int field; ref int refField; public static void M([UnscopedRef] ref S s) { // Allowed: s has both safe-context and ref-safe-context of caller-context s.refField = ref s.field; } } ``` Similarly `[UnscopedRef] out` allows a cyclic assignment because the parameter has both *safe-context* and *ref-safe-context* of *return-only*. Promoting `[UnscopedRef] ref` to *caller-context* is useful when the type is *not* a `ref struct` (note that we want to keep the rules simple so they don't distinguish between refs to ref vs non-ref structs): ```c# int x = 1; F(ref x).RefField = 2; Console.WriteLine(x); // prints 2 static S F([UnscopedRef] ref int x) { S local = new(); local.M(ref x); return local; } ref struct S { public ref int RefField; public void M([UnscopedRef] ref int data) { RefField = ref data; } } ``` In terms of advanced annotations the `[UnscopedRef]` design creates the following: ``` ref struct S { } // C# code S Create1(ref S p) S Create2([UnscopedRef] ref S p) // Annotation equivalent scoped<'b> S Create1(scoped<'a> ref scoped<'b> S) scoped<'a> S Create2(scoped<'a> ref scoped<'b> S) where 'b >= 'a ``` #### readonly cannot be deep through ref fields Consider the below code sample: ```c# ref struct S { ref int Field; readonly void Method() { // Legal or illegal? Field = 42; } } ``` When designing the rules for `ref` fields on `readonly` instances in a vacuum the rules can be validly designed such that the above is legal or illegal. Essentially `readonly` can validly be deep through a `ref` field or it can apply only to the `ref`. Applying only to the `ref` prevents ref reassignment but allows normal assignment which changes the referred to value. This design does not exist in a vacuum though, it is designing rules for types that already effectively have `ref` fields. The most prominent of which, `Span`, already has a strong dependency on `readonly` not being deep here. Its primary scenario is the ability to assign to the `ref` field through a `readonly` instance. ```c# readonly ref struct SpanOfOne { readonly ref int Field; public ref int this[int index] { get { if (index != 1) throw new Exception(); return ref Field; } } } ``` This means we must choose the shallow interpretation of `readonly`. #### Modeling constructors One subtle design question is: How are constructors bodies modeled for ref safety? Essentially how is the following constructor analyzed? ```c# ref struct S { ref int field; public S(ref int f) { field = ref f; } } ``` There are roughly two approaches: 1. Model as a `static` method where `this` is a local where its *safe-context* is *caller-context* 2. Model as a `static` method where `this` is an `out` parameter. Further a constructor must meet the following invariants: 1. Ensure that `ref` parameters can be captured as `ref` fields. 2. Ensure that `ref` to fields of `this` cannot be escaped through `ref` parameters. That would violate [tricky ref assignment](#tricky-ref-assignment). The intent is to pick the form that satisfies our invariants without introduction of any special rules for constructors. Given that the best model for constructors is viewing `this` as an `out` parameter. The *return only* nature of the `out` allows us to satisfy all the invariants above without any special casing: ```c# public static void ctor(out S @this, ref int f) { // The ref-safe-context of `ref f` is *return-only* which is also the // safe-context of `this.field` hence this assignment is allowed @this.field = ref f; } ``` #### Method arguments must match The method arguments must match rule is a common source of confusion for developers. It's a rule which has a number of special cases that are hard to understand unless you are familiar with the reasoning behind the rule. For the sake of better understanding the reasons for the rule we will simplify *ref-safe-context* and *safe-context* to simply *context*. Methods can pretty liberally return state passed to them as parameters. Essentially any reachable state which is unscoped can be returned (including returning by `ref`). This can be returned directly through a `return` statement or indirectly by assigning into a `ref` value. Direct returns don't pose much problems for ref safety. The compiler simply needs to look at all the returnable inputs to a method and then it effectively restricts the return value to be the minimum *context* of the input. That return value then goes through normal processing. Indirect returns pose a significant problem because all `ref` are both an input and output to the method. These outputs already have a known *context*. The compiler can't infer new ones, it has to consider them at their current level. That means the compiler has to look at every single `ref` which is assignable in the called method, evaluate it's *context*, and then verify no returnable input to the method has a smaller *context* than that `ref`. If any such case exists then the method call must be illegal because it could violate `ref` safety. Method arguments must match is the process by which the compiler asserts this safety check. A different way to evaluate this which is often easier for developers to consider is to do the following exercise: 1. Look at the method definition identify all places where state can be indirectly returned: a. Mutable `ref` parameters pointing to `ref struct` b. Mutable `ref` parameters with ref assignable `ref` fields c. Assignable `ref` params or `ref` fields pointing to `ref struct` (consider recursively) 2. Look at the call site a. Identify the contexts that line up with the locations identified above b. Identify the contexts of all inputs to the method that are returnable (don't line up with `scoped` parameters) If any value in 2.b is smaller than 2.a then the method call must be illegal. Let's look at a few examples to illustrate the rules: ```c# ref struct R { } class Program { static void F0(ref R a, scoped ref R b) => throw null; static void F1(ref R x, scoped R y) { F0(ref x, ref y); } } ``` Looking at the call to `F0` lets go through (1) and (2). The parameters with potential for indirect return are `a` and `b` as both can be directly assigned. The arguments which line up to those parameters are: - `a` which maps to `x` that has *context* of *caller-context* - `b` which maps to `y` that has with *context* of *function-member* The set of returnable input to the method are - `x` with *escape-scope* of *caller-context* - `ref x` with *escape-scope* of *caller-context* - `y` with *escape-scope* of *function-member* The value `ref y` is not returnable since it maps to a `scoped ref` hence it is not considered an input. But given that there is at least one input with a smaller *escape scope* (`y` argument) than one of the outputs (`x` argument) the method call is illegal. A different variation is the following: ```c# ref struct R { } class Program { static void F0(ref R a, ref int b) => throw null; static void F1(ref R x) { int y = 42; F0(ref x, ref y); } } ``` Again the parameters with potential for indirect return are `a` and `b` as both can be directly assigned. But `b` can be excluded because it does not point to a `ref struct` hence cannot be used to store `ref` state. Thus we have: - `a` which maps to `x` that has *context* of *caller-context* The set of returnable input to the method are: - `x` with *context* of *caller-context* - `ref x` with *context* of *caller-context* - `ref y` with *context* of *function-member* Given that there is at least one input with a smaller *escape scope* (`ref y` argument) than one of the outputs (`x` argument) the method call is illegal. This is the logic that the method arguments must match rule is trying to encompass. It goes further as it considers both `scoped` as a way to remove inputs from consideration and `readonly` as a way to remove `ref` as an output (can't assign into a `readonly ref` so it can't be a source of output). These special cases do add complexity to the rules but it's done so for the benefit of the developer. The compiler seeks to remove all inputs and outputs it knows can't contribute to the result to give developers maximum flexibility when calling a member. Much like overload resolution it's worth the effort to make our rules more complex when it creates more flexibility for consumers. #### Examples of inferred *safe-context* of declaration expressions Related to [Infer *safe-context* of declaration expressions](#infer-safe-to-escape-of-declaration-expressions). ```cs ref struct RS { public RS(ref int x) { } // assumed to be able to capture 'x' static void M0(RS input, out RS output) => output = input; static void M1() { var i = 0; var rs1 = new RS(ref i); // safe-context of 'rs1' is function-member M0(rs1, out var rs2); // safe-context of 'rs2' is function-member } static void M2(RS rs1) { M0(rs1, out var rs2); // safe-context of 'rs2' is function-member } static void M3(RS rs1) { M0(rs1, out scoped var rs2); // 'scoped' modifier forces safe-context of 'rs2' to the current local context (function-member or narrower). } } ``` Note that the local context which results from the `scoped` modifier is the narrowest which could possibly be used for the variable--to be any narrower would mean the expression refers to variables which are only declared in a narrower context than the expression. ================================================ FILE: proposals/csharp-11.0/new-line-in-interpolation.md ================================================ # Allow new-lines in all interpolations [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: * [x] Proposed * [x] Implementation: https://github.com/dotnet/roslyn/pull/56853 * [x] Specification: this file. ## Summary [summary]: #summary The language today treats non-verbatim and verbatim interpolated strings (`$""` and `$@""` respectively) differently. The primary *sensible* difference for these is that a non-verbatim interpolated string works like a normal string and cannot contain newlines in its text segments, and must instead use escapes (like `\r\n`). Conversely, a verbatim interpolated string can contain newlines in its text segments (like a verbatim string), and doesn't escape newlines or other character (except for `""` to escape a quote itself). This is all reasonable and will not change with this proposal. What is unreasonable today is that we extend the restriction on 'no newlines' in a non-verbatim interpolated string *beyond* its text segments into the *interpolations* themselves. This means, for example, that you cannot write the following: ```c# var v = $"Count is\t: { this.Is.A.Really(long(expr)) .That.I.Should( be + able)[ to.Wrap()] }."; ``` Ultimately, the 'interpolation must be on a single line itself' rule is just a restriction of the current implementation. That restriction really isn't necessary, and can be annoying, and would be fairly trivial to remove (see work https://github.com/dotnet/roslyn/pull/54875 to show how). In the end, all it does is force the dev to place things on a single line, or force them into a verbatim interpolated string (both of which may be unpalatable). The interpolation expressions themselves are not text, and shouldn't be beholden to any escaping/newline rules therin. ## Specification change ```diff single_regular_balanced_text_character - : '' - | '' + : + | comment ; ``` ## LDM Discussions https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-20.md ================================================ FILE: proposals/csharp-11.0/numeric-intptr.md ================================================ # Numeric IntPtr [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary This is a revision on the initial native integers feature ([spec](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/native-integers.md)), where the `nint`/`nuint` types were distinct from the underlying types `System.IntPtr`/`System.UIntPtr`. In short, we now treat `nint`/`nuint` as simple types aliasing `System.IntPtr`/`System.UIntPtr`, like we do for `int` in relation to `System.Int32`. The `System.Runtime.CompilerServices.RuntimeFeature.NumericIntPtr` runtime feature flag triggers this new behavior. ## Design [design]: #design ### 8.3.5 Simple types C# provides a set of predefined `struct` types called the simple types. The simple types are identified through keywords, but these keywords are simply aliases for predefined `struct` types in the `System` namespace, as described in the table below. **Keyword** | **Aliased type** ----------- | ------------------ `sbyte` | `System.SByte` `byte` | `System.Byte` `short` | `System.Int16` `ushort` | `System.UInt16` `int` | `System.Int32` `uint` | `System.UInt32` **`nint`** | **`System.IntPtr`** **`nuint`** | **`System.UIntPtr`** `long` | `System.Int64` `ulong` | `System.UInt64` `char` | `System.Char` `float` | `System.Single` `double` | `System.Double` `bool` | `System.Boolean` `decimal` | `System.Decimal` \[...] ### 8.3.6 Integral types C# supports **eleven** integral types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, and `char`. \[...] ## 8.8 Unmanaged types In other words, an *unmanaged_type* is one of the following: - `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, `float`, `double`, `decimal`, or `bool`. - Any *enum_type*. - Any user-defined *struct_type* that is not a constructed type and contains fields of *unmanaged_type*s only. - In unsafe code, any *pointer_type*. ### 10.2.3 Implicit numeric conversions The implicit numeric conversions are: - From `sbyte` to `short`, `int`, **`nint`**, `long`, `float`, `double`, or `decimal`. - From `byte` to `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `float`, `double`, or `decimal`. - From `short` to `int`, **`nint`**, `long`, `float`, `double`, or `decimal`. - From `ushort` to `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `float`, `double`, or `decimal`. - From `int` to **`nint`**, `long`, `float`, `double`, or `decimal`. - From `uint` to **`nuint`**, `long`, `ulong`, `float`, `double`, or `decimal`. - **From `nint` to `long`, `float`, `double`, or `decimal`.** - **From `nuint` to `ulong`, `float`, `double`, or `decimal`.** - From `long` to `float`, `double`, or `decimal`. - From `ulong` to `float`, `double`, or `decimal`. - From `char` to `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `float`, `double`, or `decimal`. - From `float` to `double`. \[...] ### 10.2.11 Implicit constant expression conversions An implicit constant expression conversion permits the following conversions: - A *constant_expression* of type `int` can be converted to type `sbyte`, `byte`, `short`, `ushort`, `uint`, **`nint`, `nuint`**, or `ulong`, provided the value of the *constant_expression* is within the range of the destination type. \[...] ### 10.3.2 Explicit numeric conversions The explicit numeric conversions are the conversions from a *numeric_type* to another *numeric_type* for which an implicit numeric conversion does not already exist: - From `sbyte` to `byte`, `ushort`, `uint`, **`nuint`**, `ulong`, or `char`. - From `byte` to `sbyte` or `char`. - From `short` to `sbyte`, `byte`, `ushort`, `uint`, **`nuint`**, `ulong`, or `char`. - From `ushort` to `sbyte`, `byte`, `short`, or `char`. - From `int` to `sbyte`, `byte`, `short`, `ushort`, `uint`, **`nuint`**, `ulong`, or `char`. - From `uint` to `sbyte`, `byte`, `short`, `ushort`, `int`, **`nint`**, or `char`. - From `long` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `ulong`, or `char`. - **From `nint` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nuint`, `ulong`, or `char`.** - **From `nuint` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `long`, or `char`.** - From `ulong` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, or `char`. - From `char` to `sbyte`, `byte`, or `short`. - From `float` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, or `decimal`. - From `double` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, `float`, or `decimal`. - From `decimal` to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, `float`, or `double`. \[...] ### 10.3.3 Explicit enumeration conversions The explicit enumeration conversions are: - From `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, `float`, `double`, or `decimal` to any *enum_type*. - From any *enum_type* to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, `float`, `double`, or `decimal`. - From any *enum_type* to any other *enum_type*. #### 12.6.4.7 Better conversion target Given two types `T₁` and `T₂`, `T₁` is a ***better conversion target*** than `T₂` if one of the following holds: - An implicit conversion from `T₁` to `T₂` exists and no implicit conversion from `T₂` to `T₁` exists - `T₁` is `Task`, `T₂` is `Task`, and `S₁` is a better conversion target than `S₂` - `T₁` is `S₁` or `S₁?` where `S₁` is a signed integral type, and `T₂` is `S₂` or `S₂?` where `S₂` is an unsigned integral type. Specifically: \[...] ### 12.8.12 Element access \[...] The number of expressions in the *argument_list* shall be the same as the rank of the *array_type*, and each expression shall be of type `int`, `uint`, **`nint`, `nuint`**, `long`, or `ulong,` or shall be implicitly convertible to one or more of these types. #### 11.8.12.2 Array access \[...] The number of expressions in the *argument_list* shall be the same as the rank of the *array_type*, and each expression shall be of type `int`, `uint`, **`nint`, `nuint`**, `long`, or `ulong,` or shall be implicitly convertible to one or more of these types. \[...] The run-time processing of an array access of the form `P[A]`, where `P` is a *primary_no_array_creation_expression* of an *array_type* and `A` is an *argument_list*, consists of the following steps: \[...] - The index expressions of the *argument_list* are evaluated in order, from left to right. Following evaluation of each index expression, an implicit conversion to one of the following types is performed: `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`. The first type in this list for which an implicit conversion exists is chosen. \[...] ### 12.8.16 Postfix increment and decrement operators Unary operator overload resolution is applied to select a specific operator implementation. Predefined `++` and `--` operators exist for the following types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`,** `long`, `ulong`, `char`, `float`, `double`, `decimal`, and any enum type. ### 12.9.2 Unary plus operator The predefined unary plus operators are: ```csharp ... nint operator +(nint x); nuint operator +(nuint x); ``` ### 12.9.3 Unary minus operator The predefined unary minus operators are: - Integer negation: ```csharp ... nint operator –(nint x); ``` ### 12.8.16 Postfix increment and decrement operators Predefined `++` and `--` operators exist for the following types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, `float`, `double`, `decimal`, and any enum type. ### 11.7.19 Default value expressions In addition, a *default_value_expression* is a constant expression if the type is one of the following value types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool,` or any enumeration type. ### 12.9.5 Bitwise complement operator The predefined bitwise complement operators are: ```csharp ... nint operator ~(nint x); nuint operator ~(nuint x); ``` ### 12.9.6 Prefix increment and decrement operators Predefined `++` and `--` operators exist for the following types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, `float`, `double`, `decimal`, and any enum type. ## 12.10 Arithmetic operators ### 12.10.2 Multiplication operator The predefined multiplication operators are listed below. The operators all compute the product of `x` and `y`. - Integer multiplication: ```csharp ... nint operator *(nint x, nint y); nuint operator *(nuint x, nuint y); ``` ### 12.10.3 Division operator The predefined division operators are listed below. The operators all compute the quotient of `x` and `y`. - Integer division: ```csharp ... nint operator /(nint x, nint y); nuint operator /(nuint x, nuint y); ``` ### 12.10.4 Remainder operator The predefined remainder operators are listed below. The operators all compute the remainder of the division between `x` and `y`. - Integer remainder: ```csharp ... nint operator %(nint x, nint y); nuint operator %(nuint x, nuint y); ``` ### 12.10.5 Addition operator - Integer addition: ```csharp ... nint operator +(nint x, nint y); nuint operator +(nuint x, nuint y); ``` ### 12.10.6 Subtraction operator - Integer subtraction: ```csharp ... nint operator –(nint x, nint y); nuint operator –(nuint x, nuint y); ``` ## 12.11 Shift operators The predefined shift operators are listed below. - Shift left: ```csharp ... nint operator <<(nint x, int count); nuint operator <<(nuint x, int count); ``` - Shift right: ```csharp ... nint operator >>(nint x, int count); nuint operator >>(nuint x, int count); ``` The `>>` operator shifts `x` right by a number of bits computed as described below. When `x` is of type `int`, **`nint`** or `long`, the low-order bits of `x` are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero if `x` is non-negative and set to one if `x` is negative. When `x` is of type `uint`, **`nuint`** or `ulong`, the low-order bits of `x` are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero. - Unsigned shift right: ```csharp ... nint operator >>>(nint x, int count); nuint operator >>>(nuint x, int count); ``` For the predefined operators, the number of bits to shift is computed as follows: \[...] - When the type of `x` is `nint` or `nuint`, the shift count is given by the low-order five bits of `count` on a 32 bit platform, or the lower-order six bits of `count` on a 64 bit platform. ## 12.12 Relational and type-testing operators ### 12.12.2 Integer comparison operators The predefined integer comparison operators are: ```csharp ... bool operator ==(nint x, nint y); bool operator ==(nuint x, nuint y); bool operator !=(nint x, nint y); bool operator !=(nuint x, nuint y); bool operator <(nint x, nint y); bool operator <(nuint x, nuint y); bool operator >(nint x, nint y); bool operator >(nuint x, nuint y); bool operator <=(nint x, nint y); bool operator <=(nuint x, nuint y); bool operator >=(nint x, nint y); bool operator >=(nuint x, nuint y); ``` ## 12.12 Logical operators ### 12.12.2 Integer logical operators The predefined integer logical operators are: ```csharp ... nint operator &(nint x, nint y); nuint operator &(nuint x, nuint y); nint operator |(nint x, nint y); nuint operator |(nuint x, nuint y); nint operator ^(nint x, nint y); nuint operator ^(nuint x, nuint y); ``` ## 12.22 Constant expressions A constant expression may be either a value type or a reference type. If a constant expression is a value type, it must be one of the following types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`**, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool,` or any enumeration type. \[...] An implicit constant expression conversion permits a constant expression of type `int` to be converted to `sbyte`, `byte`, `short`, `ushort`, `uint`, **`nint`, `nuint`,** or `ulong`, provided the value of the constant expression is within the range of the destination type. ## 17.4 Array element access Array elements are accessed using *element_access* expressions of the form `A[I₁, I₂, ..., Iₓ]`, where `A` is an expression of an array type and each `Iₑ` is an expression of type `int`, `uint`, **`nint`, `nuint`,** `long`, `ulong`, or can be implicitly converted to one or more of these types. The result of an array element access is a variable, namely the array element selected by the indices. ## 23.5 Pointer conversions ### 23.5.1 General \[...] Additionally, in an unsafe context, the set of available explicit conversions is extended to include the following explicit pointer conversions: - From any *pointer_type* to any other *pointer_type*. - From `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`,** `long`, or `ulong` to any *pointer_type*. - From any *pointer_type* to `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, **`nint`, `nuint`,** `long`, or `ulong`. ### 23.6.4 Pointer element access \[...] In a pointer element access of the form `P[E]`, `P` shall be an expression of a pointer type other than `void*`, and `E` shall be an expression that can be implicitly converted to `int`, `uint`, **`nint`, `nuint`,** `long`, or `ulong`. ### 23.6.7 Pointer arithmetic In an unsafe context, the `+` operator and `–` operator can be applied to values of all pointer types except `void*`. Thus, for every pointer type `T*`, the following operators are implicitly defined: ```csharp [...] T* operator +(T* x, nint y); T* operator +(T* x, nuint y); T* operator +(nint x, T* y); T* operator +(nuint x, T* y); T* operator -(T* x, nint y); T* operator -(T* x, nuint y); ``` Given an expression `P` of a pointer type `T*` and an expression `N` of type `int`, `uint`, **`nint`, `nuint`,** `long`, or `ulong`, the expressions `P + N` and `N + P` compute the pointer value of type `T*` that results from adding `N * sizeof(T)` to the address given by `P`. Likewise, the expression `P – N` computes the pointer value of type `T*` that results from subtracting `N * sizeof(T)` from the address given by `P`. ## Various considerations ### Breaking changes One of the main impacts of this design is that `System.IntPtr` and `System.UIntPtr` gain some built-in operators (conversions, unary and binary). Those include `checked` operators, which means that the following operators on those types will now throw when overflowing: - `IntPtr + int` - `IntPtr - int` - `IntPtr -> int` - `long -> IntPtr` - `void* -> IntPtr` ### Metadata encoding This design means that `nint` and `nuint` can simply be emitted as `System.IntPtr` and `System.UIntPtr`, without the use of `System.Runtime.CompilerServices.NativeIntegerAttribute`. Similarly, when loading metadata `NativeIntegerAttribute` can be ignored. ================================================ FILE: proposals/csharp-11.0/pattern-match-span-of-char-on-string.md ================================================ # Pattern match `Span` on a constant string [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Permit pattern matching a `Span` and a `ReadOnlySpan` on a constant string. ## Motivation [motivation]: #motivation For perfomance, usage of `Span` and `ReadOnlySpan` is preferred over string in many scenarios. The framework has added many new APIs to allow you to use `ReadOnlySpan` in place of a `string`. A common operation on strings is to use a switch to test if it is a particular value, and the compiler optimizes such a switch. However there is currently no way to do the same on a `ReadOnlySpan` efficiently, other than implementing the switch and the optimization manually. In order to encourage adoption of `ReadOnlySpan` we allow pattern matching a `ReadOnlySpan`, on a constant `string`, thus also allowing it to be used in a switch. ```csharp static bool Is123(ReadOnlySpan s) { return s is "123"; } static bool IsABC(Span s) { return s switch { "ABC" => true, _ => false }; } ``` ## Detailed design [design]: #detailed-design We alter the [spec](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/patterns.md#1123-constant-pattern) for constant patterns as follows (the proposed addition is shown in bold): > Given a pattern input value `e` and a constant pattern `P` with converted value `v`, > > - if *e* has integral type or enum type, or a nullable form of one of those, and *v* has integral type, the pattern `P` *matches* the value *e* if result of the expression `e == v` is `true`; otherwise > - **If *e* is of type `System.Span` or `System.ReadOnlySpan`, and *c* is a constant string, and *c* does not have a constant value of `null`, then the pattern is considered matching if `System.MemoryExtensions.SequenceEqual(e, System.MemoryExtensions.AsSpan(c))` returns `true`.** > - the pattern `P` *matches* the value *e* if `object.Equals(e, v)` returns `true`. ### Well-known members `System.Span` and `System.ReadOnlySpan` are matched by name, must be `ref struct`s, and can be defined outside corlib. `System.MemoryExtensions` is matched by name and can be defined outside corlib. The signature of `System.MemoryExtensions.SequenceEqual` overloads must match: - `public static bool SequenceEqual(System.Span, System.ReadOnlySpan)` - `public static bool SequenceEqual(System.ReadOnlySpan, System.ReadOnlySpan)` The signature of `System.MemoryExtensions.AsSpan` must match: - `public static System.ReadOnlySpan AsSpan(string)` Methods with optional parameters are excluded from consideration. ## Drawbacks [drawbacks]: #drawbacks None ## Alternatives [alternatives]: #alternatives None ## Unresolved questions [unresolved]: #unresolved-questions 1. Should matching be defined independently from `MemoryExtensions.SequenceEqual()` etc.? > ... the pattern is considered matching if `e.Length == c.Length` and `e[i] == c[i]` for all characters in `e`. _Recommendation: Define in terms of `MemoryExtensions.SequenceEqual()` for performance. If `MemoryExtensions` is missing, report compile error._ 2. Should matching against `(string)null` be allowed? If so, should `(string)null` subsume `""` since `MemoryExtensions.AsSpan(null) == MemoryExtensions.AsSpan("")`? ```csharp static bool IsEmpty(ReadOnlySpan span) { return span switch { (string)null => true, // ok? "" => true, // error: unreachable? _ => false, }; } ``` _Recommendation: Constant pattern `(string)null` should be reported as an error._ 3. Should the constant pattern match include a runtime type test of the expression value for `Span` or `ReadOnlySpan`? ```csharp static bool Is123(Span s) { return s is "123"; // test for Span? } static bool IsABC(Span s) { return s is Span and "ABC"; // ok? } static bool IsEmptyString(T t) where T : ref struct { return t is ""; // test for ReadOnlySpan, Span, string? } ``` _Recommendation: No implicit runtime type test for constant pattern. (`IsABC()` example is allowed because the type test is explicit.)_ **This recommendation was not implemented. All of the preceding samples produce a compiler error.** 4. Should subsumption consider constant string patterns, list patterns, and `Length` property pattern? ```csharp static int ToNum(ReadOnlySpan s) { return s switch { { Length: 0 } => 0, "" => 1, // error: unreachable? ['A',..] => 2, "ABC" => 3, // error: unreachable? _ => 4, }; } ``` _Recommendation: Same subsumption behavior as used when the expression value is `string`. (Does that mean no subsumption between constant strings, list patterns, and `Length`, other than treating `[..]` as matching any?)_ ## Design meetings https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-07.md#readonlyspanchar-patterns ================================================ FILE: proposals/csharp-11.0/raw-string-literal.md ================================================ # Raw string literal [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Allow a new form of string literal that starts with a minimum of three `"""` characters (but no maximum), optionally followed by a `new_line`, the content of the string, and then ends with the same number of quotes that the literal started with. For example: ``` var xml = """ """; ``` Because the nested contents might itself want to use `"""` then the starting/ending delimiters can be longer like so: ``` var xml = """" Ok to use """ here """"; ``` To make the text easy to read and allow for indentation that developers like in code, these string literals will naturally remove the indentation specified on the last line when producing the final literal value. For example, a literal of the form: ``` var xml = """ """; ``` Will have the contents: ``` ``` This allows code to look natural, while still producing literals that are desired, and avoiding runtime costs if this required the use of specialized string manipulation routines. If the indentation behavior is not desired, it is also trivial to disable like so: ``` var xml = """ """; ``` A single line form is also supported. It starts with a minimum of three `"""` characters (but no maximum), the content of the string (which cannot contain any `new_line` characters), and then ends with the same number of quotes that the literal started with. For example: ``` var xml = """"""; ``` Interpolated raw strings are also supported. In this case, the string specifies the number of braces needed to start an interpolation (determined by the number of dollar signs present at the start of the literal). Any brace sequence with fewer braces than that is just treated as content. For example: ``` var json = $$""" { "summary": "text", "length" : {{value.Length}}, }; """; ``` ## Motivation C# lacks a general way to create simple string literals that can contain effectively any arbitrary text. All C# string literal forms today need some form of escaping in case the contents use some special character (always if a delimiter is used). This prevents easily having literals containing other languages in them (for example, an XML, HTML or JSON literal). All current approaches to form these literals in C# today always force the user to manually escape the contents. Editing at that point can be highly annoying as the escaping cannot be avoided and must be dealt with whenever it arises in the contents. This is particularly painful for regexes, especially when they contain quotes or backslashes. Even with a verbatim (`@""`) string, quotes themselves must be escaped leading to a mix of C# and regex interspersed. `{` and `}` are similarly frustrating in interpolated (`$""`) strings. The crux of the problem is that all our strings have a fixed start/end delimiter. As long as that is the case, we will always have to have an escaping mechanism as the string contents may need to specify that end delimiter in their contents. This is particularly problematic as that delimiter `"` is exceedingly common in many languages. To address this, this proposal allows for flexible start and end delimiters so that they can always be made in a way that will not conflict with the content of the string. ## Goals 1. Provide a mechanism that will allow *all* string values to be provided by the user without the need for *any* escape-sequences whatsoever. Because all strings must be representable without escape-sequences, it must always be possible for the user to specify delimiters that will be guaranteed to not collide with any text contents. 2. Support interpolations in the same fashion. As above, because *all* strings must be representable without escapes, it must always be possible for the user to specify an `interpolation` delimiter that will be guaranteed to not collide with any text contents. Importantly, languages that use our *interpolation* delimiter characters (`{` and `}`) should feel first-class and not painful to use. 3. Multiline string literals should look pleasant in code and should not make indentation within the compilation unit look strange. Importantly, literal values that themselves have no indentation should not be forced to occupy the first column of the file as that can break up the flow of code and will look unaligned with the rest of the code that surrounds it. * This behavior should be easy to override while keeping literals clear and easy to read. 4. For all strings that do not themselves contain a `new_line` or start or end with a quote (`"`) character, it should be possible to represent the string literal itself on a single line. - Optionally, with extra complexity, we could refine this to state that: For all strings that do not themselves contain a `new_line` (but can start or end with a quote `"` character), it should be possible to represent the string literal itself on a single line. For more details see the expanded proposal in the `Drawbacks` section. ## Detailed design (non-interpolation case) We will add a new `string_literal` production with the following form: ``` string_literal : regular_string_literal | verbatim_string_literal | raw_string_literal ; raw_string_literal : single_line_raw_string_literal | multi_line_raw_string_literal ; raw_string_literal_delimiter : """ | """" | """"" | etc. ; raw_content : not_new_line+ ; single_line_raw_string_literal : raw_string_literal_delimiter raw_content raw_string_literal_delimiter ; multi_line_raw_string_literal : raw_string_literal_delimiter whitespace* new_line (raw_content | new_line)* new_line whitespace* raw_string_literal_delimiter ; not_new_line : ; ``` The ending delimiter to a `raw_string_literal` must match the starting delimiter. So if the starting delimiter is `"""""` the ending delimiter must be that as well. The above grammar for a `raw_string_literal` should be interpreted as: 1. It starts with at least three quotes (but no upper bound on quotes). 2. It then continues with contents on the same line as the starting quotes. These contents on the same line can be blank, or non-blank. 'blank' is synonymous with 'entirely whitespace'. 3. If the contents on that same line is non-blank no further content can follow. In other words the literal is required to end with the same number of quotes on that same line. 4. If the contents on the same line is blank, then the literal can continue with a `new_line` and some number of subsequent content lines and `new_line`s. - A content line is any text except a `new_line`. - It then ends with a `new_line` some number (possibly zero) of `whitespace` and the same number of quotes that the literal started with. ## Raw string literal value The portions between the starting and ending `raw_string_literal_delimiter` are used to form the value of the `raw_string_literal` in the following fashion: * In the case of `single_line_raw_string_literal` the value of the literal will exactly be the contents between the starting and ending `raw_string_literal_delimiter`. * In the case of `multi_line_raw_string_literal` the initial `whitespace* new_line` and the final `new_line whitespace*` is not part of the value of the string. However, the final `whitespace*` portion preceding the `raw_string_literal_delimiter` terminal is considered the 'indentation whitespace' and will affect how the other lines are interpreted. * To get the final value the sequence of `(raw_content | new_line)*` is walked and the following is performed: * If it a `new_line` the content of the `new_line` is added to the final string value. * If it is not a 'blank' `raw_content` (i.e. `not_new_line+` contains a non-`whitespace` character): * the 'indentation whitespace' must be a prefix of the `raw_content`. It is an error otherwise. * the 'indentation whitespace' is stripped from the start of `raw_content` and the remainder is added to the final string value. * If it is a 'blank' `raw_content` (i.e. `not_new_line+` is entirely `whitespace`): * the 'indentation whitespace' must be a prefix of the `raw_content` or the `raw_content` must be a prefix of of the 'indentation whitespace'. It is an error otherwise. * as much of the 'indentation whitespace' is stripped from the start of `raw_content` and any remainder is added to the final string value. ## Clarifications: 1. A `single_line_raw_string_literal` is not capable of representing a string with a `new_line` value in it. A `single_line_raw_string_literal` does not participate in the 'indentation whitespace' trimming. Its value is always the exact characters between the starting and ending delimiters. 2. Because a `multi_line_raw_string_literal` ignores the final `new_line` of the last content line, the following represents a string with no starting `new_line` and no terminating `new_line` ``` var v1 = """ This is the entire content of the string. """; ``` This maintains symmetry with how the starting `new_line` is ignored, and it also provides a uniform way to ensure the 'indentation whitespace' can always be adjusted. To represent a string with a terminal `new_line` an extra line must be provided like so: ``` var v1 = """ This string ends with a new line. """; ``` 3. A `single_line_raw_string_literal` cannot represent a string value that starts or ends with a quote (`"`) though an augmentation to this proposal is provided in the `Drawbacks` section that shows how that could be supported. 4. A `multi_line_raw_string_literal` starts with `whitespace* new_line` following the initial `raw_string_literal_delimiter`. This content after the delimiter is entirely ignored and is not used in any way when determining the value of the string. This allows for a mechanism to specify a `raw_string_literal` whose content starts with a `"` character itself. For example: ``` var v1 = """ "The content of this string starts with a quote """; ``` 5. A `raw_string_literal` can also represent content that end with a quote (`"`). This is supported as the terminating delimiter must be on its own line. For example: ``` var v1 = """ "The content of this string starts and ends with a quote" """; ``` ``` var v1 = """ ""The content of this string starts and ends with two quotes"" """; ``` 5. The requirement that a 'blank' `raw_content` be either a prefix of the 'indentation whitespace' or the 'indentation whitespace' must be a prefix of it helps ensure confusing scenarios with mixed whitespace do not occur, especially as it would be unclear what should happen with that line. For example, the following case is illegal: ``` var v1 = """ Start End """; ``` 6. Here the 'indentation whitespace' is nine space characters, but the 'blank' `raw_content` does not start with a prefix of that. There is no clear answer as to how that `` line should be treated at all. Should it be ignored? Should it be the same as `.........`? As such, making it illegal seems the clearest for avoiding confusion. 7. The following cases are legal though and represent the same string: ``` var v1 = """ Start End """; ``` ``` var v1 = """ Start End """; ``` In both these cases, the 'indentation whitespace' will be nine spaces. And in both cases, we will remove as much of that prefix as possible, leading the 'blank' `raw_content` in each case to be empty (not counting every `new_line`). This allows users to not have to see and potentially fret about whitespace on these lines when they copy/paste or edit these lines. 8. In the case though of: ``` var v1 = """ Start End """; ``` The 'indentation whitespace' will still be nine spaces. Here though, we will remove as much of the 'indentation whitespace' as possible, and the 'blank' `raw_content` will contribute a single space to the final content. This allows for cases where the content does need whitespace on these lines that should be preserved. 9. The following is technically not legal: ``` var v1 = """ """; ``` This is because the start of the raw string must have a `new_line` (which it does) but the end must have a `new_line` as well (which it does not). The minimal legal `raw_string_literal` is: ``` var v1 = """ """; ``` However, this string is decidedly uninteresting as it is equivalent to `""`. ## Indentation examples The 'indentation whitespace' algorithm can be visualized on several inputs like so. The following examples use the vertical bar character `|` to illustrate the first column in the resultant raw string: ### Example 1 - Standard case ``` var xml = """ """; ``` is interpreted as ``` var xml = """ | | | | """; ``` ### Example 2 - End delimiter on same line as content. ``` var xml = """ """; ``` This is illegal. The last content line must end with a `new_line`. ### Example 3 - End delimiter before start delimiter ``` var xml = """ """; ``` is interpreted as ``` var xml = """ | | | | """; ``` ### Example 4 - End delimiter after start delimiter ``` var xml = """ """; ``` This is illegal. The lines of content must start with the 'indentation whitespace' ### Example 5 - Empty blank line ``` var xml = """ """; ``` is interpreted as ``` var xml = """ | | | | | """; ``` ### Example 6 - Blank line with less whitespace than prefix (dots represent spaces) ``` var xml = """ .... """; ``` is interpreted as ``` var xml = """ | | | | | """; ``` ### Example 7 - Blank line with more whitespace than prefix (dots represent spaces) ``` var xml = """ .............. """; ``` is interpreted as ``` var xml = """ | | | |.... | """; ``` ## Detailed design (interpolation case) Interpolations in normal interpolated strings (e.g. `$"..."`) are supported today through the use of the `{` character to start an `interpolation` and the use of an `{{` escape-sequence to insert an actual open brace character. Using this same mechanism would violate goals '1' and '2' of this proposal. Languages that have `{` as a core character (examples being JavaScript, JSON, Regex, and even embedded C#) would now need escaping, undoing the purpose of raw string literals. To support interpolations we introduce them in a different fashion than normal `$"` interpolated strings. Specifically, an `interpolated_raw_string_literal` will start with some number of `$` characters. The count of these indicates how many `{` (and `}`) characters are needed in the content of the literal to delimit the `interpolation`. Importantly, there continues to be no escaping mechanism for curly braces. Rather, just as with quotes (`"`) the literal itself can always ensure it specifies delimiters for the interpolations that are certain to not collide with any of the rest of the content of the string. For example a JSON literal containing interpolation holes can be written like so: ```c# var v1 = $$""" { "orders": [ { "number": {{order_number}} } ] } """ ``` Here, the `{{...}}` matches the requisite count of two braces specified by the `$$` delimiter prefix. In the case of a single `$` that means the interpolation is specified just as `{...}` as in normal interpolated string literals. Importantly, this means that an interpolated literal with `N` `$` characters can have a sequence of `2*N-1` braces (of the same type in a row). The last `N` braces will start (or end) an interpolation, and the remaining `N-1` braces will just be content. For example: ```c# var v1 = $$"""X{{{1+1}}}Z"""; ``` In this case the inner two `{{` and `}}` braces belong to the interpolation, and the outer singular braces are just content. So the above string is equivalent to the content `X{2}Z`. Having `2*N` (or more) braces is always an error. To have longer sequences of braces as content, the number of `$` characters must be increased accordingly. Interpolated raw string literals are defined as: ``` interpolated_raw_string_literal : single_line_interpolated_raw_string_literal | multi_line_interpolated_raw_string_literal ; interpolated_raw_string_start : $ | $$ | $$$ | etc. ; interpolated_raw_string_literal_delimiter : interpolated_raw_string_start raw_string_literal_delimiter ; single_line_interpolated_raw_string_literal : interpolated_raw_string_literal_delimiter interpolated_raw_content raw_string_literal_delimiter ; multi_line_interpolated_raw_string_literal : interpolated_raw_string_literal_delimiter whitespace* new_line (interpolated_raw_content | new_line)* new_line whitespace* raw_string_literal_delimiter ; interpolated_raw_content : (not_new_line | raw_interpolation)+ ; raw_interpolation : raw_interpolation_start interpolation raw_interpolation_end ; raw_interpolation_start : { | {{ | {{{ | etc. ; raw_interpolation_end : } | }} | }}} | etc. ; ``` The above is similar to the definition of `raw_string_literal` but with some important differences. A `interpolated_raw_string_literal` should be interpreted as: 1. It starts with at least one dollar sign (but no upper bound) and then three quotes (also with no upper bound). 2. It then continues with content on the same line as the starting quotes. This content on the same line can be blank, or non-blank. 'blank' is synonymous with 'entirely whitespace'. 3. If the content on that same line is non-blank no further content can follow. In other words the literal is required to end with the same number of quotes on that same line. 4. If the contents on the same line is blank, then the literal can continue with a `new_line` and some number of subsequent content lines and `new_line`s. - A content line is any text except a `new_line`. - A content line can contain multiple `raw_interpolation` occurrences at any position. The `raw_interpolation` must start with an equal number of open braces (`{`) as the number of dollar signs at the start of the literal. - If 'indentation whitespace' is not-empty, a `raw_interpolation` cannot immediately follow a `new_line`. - The `raw_interpolation` will following the normal rules specified at [§12.8.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1283-interpolated-string-expressions). Any `raw_interpolation` must end with the same number of close braces (`}`) as dollar signs and open braces. - Any `interpolation` can itself contain new-lines within in the same manner as an `interpolation` in a normal `verbatim_string_literal` (`@""`). - It then ends with a `new_line` some number (possibly zero) of `whitespace` and the same number of quotes that the literal started with. Computation of the interpolated string value follows the same rules as a normal `raw_string_literal` except updated to handle lines containing `raw_interpolation`s. Building the string value happens in the same fashion, just with the interpolation holes replaced with whatever values those expressions produce at runtime. If the `interpolated_raw_string_literal` is converted to a `FormattableString` then the values of the interpolations are passed in their respective order to the `arguments` array to `FormattableString.Create`. The rest of the content of the `interpolated_raw_string_literal` *after* the 'indentation whitespace' has been stripped from all lines will be used to generate the `format` string passed to `FormattableString.Create`, except with appropriately numbered `{N}` contents in each location where a `raw_interpolation` occurred (or `{N,constant}` in the case if its `interpolation` is of the form `expression ',' constant_expression`). There is an ambiguity in the above specification. Specifically when a section of `{` in text and `{` of an interpolation abut. For example: ``` var v1 = $$""" {{{order_number}}} """ ``` This could be interpreted as: `{{ {order_number } }}` or `{ {{order_number}} }`. However, as the former is illegal (no C# expression could start with `{`) it would be pointless to interpret that way. So we interpret in the latter fashion, where the innermost `{` and `}` braces form the interpolation, and any outermost ones form the text. In the future this might be an issue if the language ever supports any expressions that are surrounded by braces. However, in that case, the recommendation would be to write such a case like so: `{{({some_new_expression_form})}}`. Here, parentheses would help designate the expression portion from the rest of the literal/interpolation. This has precedence already with how ternary conditional expressions need to be wrapped to not conflict with the formatting/alignment specifier of an interpolation (e.g. `{(x ? y : z)}`). ## Drawbacks Raw string literals add more complexity to the language. We already have many string literal forms already for numerous purposes. `""` strings, `@""` strings, and `$""` strings already have a lot of power and flexibility. But they all lack a way to provide raw contents that never need escaping. The above rules do not support the case of [4.a](#goals): 4. ... - Optionally, with extra complexity, we could refine this to state that: For all strings that do not themselves contain a `new_line` (but can start or end with a quote `"` character), it should be possible to represent the string literal itself on a single line. That's because we have no means to know that a starting or ending quote (`"`) should belong to the contents and not the delimiter itself. If this is an important scenario we want to support though, we can add a parallel `'''` construct to go along with the `"""` form. With that parallel construct, a single line string that start and ends with `"` can be written easily as `'''"This string starts and ends with quotes"'''` along with the parallel construct `"""'This string starts and ends with apostrophes'"""`. This may also be desirable to support to help visually separate out quote characters, which may help when embedding languages that primarily use one quote character much more than then other. ## Alternatives https://github.com/dotnet/csharplang/discussions/89 covers many options here. Alternatives are numerous, but i feel stray too far into complexity and poor ergonomics. This approach opts for simplicity where you just keep increasing the start/end quote length until there is no concern about a conflict with the string contents. It also allows the code you write to look well indented, while still producing a dedented literal that is what most code wants. One of the most interesting potential variations though is the use of `` ` `` (or ` ``` `) fences for these raw string literals. This would have several benefits: 1. It would avoid all the issues with strings starting er ending with quotes. 2. It would look familiar to markdown. Though that in itself is potentially not a good thing as users might expect markdown interpretation. 3. A raw string literal would only have to start and end with a single character in most cases, and would only need multiple in the much rarer case of contents that contain back-ticks themselves. 4. It would feel natural to extend this in the future with ` ```xml `, again akin to markdown. Though, of course, that is also true of the `"""` form. Overall though, the net benefit here seems small. In keeping with C# history, i think `"` should continue to be the `string literal` delimiter, just as it is for `@""` and `$""`. ## Design meetings ### ~~Open issues to discuss~~ Resolved issues: - [x] should we have a single line form? We technically could do without it. But it would mean simple strings not containing a newline would always take at least three lines. I think we should It's very heavyweight to force single line constructs to be three lines just to avoid escaping. Design decision: Yes, we will have a single line form. - [x] should we require that multiline *must* start with a newline? I think we should. It also gives us the ability to support things like `"""xml` in the future. Design decision: Yes, we will require that multiline must start with a newline - [x] should the automatic dedenting be done at all? I think we should. It makes code look so much more pleasant. Design decision: Yes, automatic dedenting will be done. - [x] should we restrict common-whitespace from mixing whitespace types? I don't think we should. Indeed, there is a common indentation strategy called "tab for indentation, space for alignment". It would be very natural to use this to align the end delimiter with the start delimiter in a case where the start delimiter doesn't start on a tab stop. Design decision: We will not have any restrictions on mixing whitespace. - [x] should we use something else for the fences? `` ` `` would match markdown syntax, and would mean we didn't need to always start these strings with three quotes. Just one would suffice for the common case. Design decision: We will use `"""` - [x] should we have a requirement that the delimiter have more quotes than the longest sequence of quotes in the string value? Technically it's not required. for example: ``` var v = """ contents""""" """ ``` This is a string with `"""` as the delimiter. Several community members have stated this is confusing and we should require in a case like this that the delimiter always have more characters. That would then be: ``` var v = """""" contents""""" """""" ``` Design decision: Yes, the delimiter must be longer than any sequence of quotes in the string itself. ================================================ FILE: proposals/csharp-11.0/relaxing_shift_operator_requirements.md ================================================ # Relaxing shift operator requirements [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary The shift operator requirements will be relaxed so that the right-hand side operand is no longer restricted to only be `int`. ## Motivation [motivation]: #motivation When working with types other than `int`, it is not uncommon that you shift using the result of another computation, such as shifting based on the `leading zero count`. The natural type of something like a `leading zero count` is the same as the input type (`TSelf`) and so in many cases, this requires you to convert that result to `int` before shifting, even if that result is already within range. Within the context of the generic math interfaces the libraries are planning to expose, this is potentially problematic as the type is not well known and so the conversion to `int` may not be possible or even well-defined. ## Detailed design [design]: #detailed-design ### Shift operators [§12.11](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1211-shift-operators) should be reworded as follows: ```diff - When declaring an overloaded shift operator, the type of the first operand must always be the class or struct containing the operator declaration, and the type of the second operand must always be int. + When declaring an overloaded shift operator, the type of the first operand must always be the class or struct containing the operator declaration. ``` That is, the restriction that the first operand be the class or struct containing the operator declaration remains. While the restriction that the second operand must be `int` is removed. ### Binary operators [§14.10.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15103-binary-operators) should be reworded as follows: ```diff -* A binary `<<` or `>>` operator must take two parameters, the first of which must have type `T` or `T?` and the second of which must have type `int` or `int?`, and can return any type. +* A binary `<<` or `>>` operator must take two parameters, the first of which must have type `T` or `T?`, and can return any type. ``` That is, the restriction that the first parameter be `T` or `T?` remains. While the restriction that the second operand must be `int` or `int?` is removed. ### Binary operator overload resolution The first bullet point at [§11.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1245-binary-operator-overload-resolution) should be reworded as follows: * The set of candidate user-defined operators provided by `X` and `Y` for the operation `operator op(x,y)` is determined. The set consists of the union of the candidate operators provided by `X` and **, unless the operator is a shift operator,** the candidate operators provided by `Y`, each determined using the rules of Candidate user-defined operators [§11.4.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1246-candidate-user-defined-operators). If `X` and `Y` are the same type, or if `X` and `Y` are derived from a common base type, then shared candidate operators only occur in the combined set once. That is, for shift operators, candidate operators are only those provided by type `X`. ## Drawbacks [drawbacks]: #drawbacks Users will be able to define operators that do not follow the recommended guidelines, such as implementing `cout << "string"` in C#. ## Alternatives [alternatives]: #alternatives The generic math interfaces being exposed by the libraries could expose explicitly named methods instead. This may make code more difficult to read/maintain. The generic math interfaces could require the shift take `int` and that a conversion be performed. This conversion may be expensive or may be not possible depending on the type in question. ## Unresolved questions [unresolved]: #unresolved-questions Is there concern around preserving the "intent" around why the second operand was restricted to `int`? ## Design meetings https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md ================================================ FILE: proposals/csharp-11.0/required-members.md ================================================ # Required Members [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary This proposal adds a way of specifying that a property or field is required to be set during object initialization, forcing the instance creator to provide an initial value for the member in an object initializer at the creation site. ## Motivation Object hierarchies today require a lot of boilerplate to carry data across all levels of the hierarchy. Let's look at a simple hierarchy involving a `Person` as might be defined in C# 8: ```cs class Person { public string FirstName { get; } public string MiddleName { get; } public string LastName { get; } public Person(string firstName, string lastName, string? middleName = null) { FirstName = firstName; LastName = lastName; MiddleName = middleName ?? string.Empty; } } class Student : Person { public int ID { get; } public Student(int id, string firstName, string lastName, string? middleName = null) : base(firstName, lastName, middleName) { ID = id; } } ``` There's lots of repetition going on here: 1. At the root of the hierarchy, the type of each property had to be repeated twice, and the name had to be repeated four times. 2. At the derived level, the type of each inherited property had to be repeated once, and the name had to be repeated twice. This is a simple hierarchy with 3 properties and 1 level of inheritance, but many real-world examples of these types of hierarchies go many levels deeper, accumulating larger and larger numbers of properties to pass along as they do so. Roslyn is one such codebase, for example, in the various tree types that make our CSTs and ASTs. This nesting is tedious enough that we have code generators to generate the constructors and definitions of these types, and many customers take similar approaches to the problem. C# 9 introduces records, which for some scenarios can make this better: ```cs record Person(string FirstName, string LastName, string MiddleName = ""); record Student(int ID, string FirstName, string LastName, string MiddleName = "") : Person(FirstName, LastName, MiddleName); ``` `record`s eliminate the first source of duplication, but the second source of duplication remains unchanged: unfortunately, this is the source of duplication that grows as the hierarchy grows, and is the most painful part of the duplication to fix up after making a change in the hierarchy as it required chasing the hierarchy through all of its locations, possibly even across projects and potentially breaking consumers. As a workaround to avoid this duplication, we have long seen consumers embracing object initializers as a way of avoiding writing constructors. Prior to C# 9, however, this had 2 major downsides: 1. The object hierarchy has to be fully mutable, with `set` accessors on every property. 2. There is no way to ensure that every instantiation of an object from the graph sets every member. C# 9 again addressed the first issue here, by introducing the `init` accessor: with it, these properties can be set on object creation/initialization, but not subsequently. However, we again still have the second issue: properties in C# have been optional since C# 1.0. Nullable reference types, introduced in C# 8.0, addressed part of this issue: if a constructor does not initialize a non-nullable reference-type property, then the user is warned about it. However, this doesn't solve the problem: the user here wants to not repeat large parts of their type in the constructor, they want to pass the _requirement_ to set properties on to their consumers. It also doesn't provide any warnings about `ID` from `Student`, as that is a value type. These scenarios are extremely common in database model ORMs, such as EF Core, which need to have a public parameterless constructor but then drive nullability of the rows based on the nullability of the properties. This proposal seeks to address these concerns by introducing a new feature to C#: required members. Required members will be required to be initialized by consumers, rather than by the type author, with various customizations to allow flexibility for multiple constructors and other scenarios. ## Detailed Design `class`, `struct`, and `record` types gain the ability to declare a _required\_member\_list_. This list is the list of all the properties and fields of a type that are considered _required_, and must be initialized during the construction and initialization of an instance of the type. Types inherit these lists from their base types automatically, providing a seamless experience that removes boilerplate and repetitive code. ### `required` modifier We add `'required'` to the list of modifiers in _field\_modifier_ and _property\_modifier_. The _required\_member\_list_ of a type is composed of all the members that have had `required` applied to them. Thus, the `Person` type from earlier now looks like this: ```cs public class Person { // The default constructor requires that FirstName and LastName be set at construction time public required string FirstName { get; init; } public string MiddleName { get; init; } = ""; public required string LastName { get; init; } } ``` All constructors on a type that has a _required\_member\_list_ automatically advertise a _contract_ that consumers of the type must initialize all of the properties in the list. It is an error for a constructor to advertise a contract that requires a member that is not at least as accessible as the constructor itself. For example: ```cs public class C { public required int Prop { get; protected init; } // Advertises that Prop is required. This is fine, because the constructor is just as accessible as the property initer. protected C() {} // Error: ctor C(object) is more accessible than required property Prop.init. public C(object otherArg) {} } ``` `required` is only valid in `class`, `struct`, and `record` types. It is not valid in `interface` types. `required` cannot be combined with the following modifiers: * `fixed` * `ref readonly` * `ref` * `const` * `static` `required` is not allowed to be applied to indexers. The compiler will issue a warning when `Obsolete` is applied to a required member of a type and: 1. The type is not marked `Obsolete`, or 2. Any constructor not attributed with `SetsRequiredMembersAttribute` is not marked `Obsolete`. ### `SetsRequiredMembersAttribute` All constructors in a type with required members, or whose base type specifies required members, must have those members set by a consumer when that constructor is called. In order to exempt constructors from this requirement, a constructor can be attributed with `SetsRequiredMembersAttribute`, which removes these requirements. The constructor body is not validated to ensure that it definitely sets the required members of the type. `SetsRequiredMembersAttribute` removes _all_ requirements from a constructor, and those requirements are not checked for validity in any way. NB: this is the escape hatch if inheriting from a type with an invalid required members list is necessary: mark the constructor of that type with `SetsRequiredMembersAttribute`, and no errors will be reported. If a constructor `C` chains to a `base` or `this` constructor that is attributed with `SetsRequiredMembersAttribute`, `C` must also be attributed with `SetsRequiredMembersAttribute`. For record types, we will emit `SetsRequiredMembersAttribute` on the synthesized copy constructor of a record if the record type or any of its base types have required members. NB: An earlier version of this proposal had a larger metalanguage around initialization, allowing adding and removing individual required members from a constructor, as well as validation that the constructor was setting all required members. This was deemed too complex for the initial release, and removed. We can look at adding more complex contracts and modifications as a later feature. ### Enforcement For every constructor `Ci` in type `T` with required members `R`, consumers calling `Ci` must do one of: * Set all members of `R` in an _object\_initializer_ on the _object\_creation\_expression_, * Or set all members of `R` via the _named\_argument\_list_ section of an _attribute\_target_. unless `Ci` is attributed with `SetsRequiredMembers`. If the current context does not permit an _object\_initializer_ or is not an _attribute\_target_, and `Ci` is not attributed with `SetsRequiredMembers`, then it is an error to call `Ci`. ### `new()` constraint A type with a parameterless constructor that advertises a _contract_ is not allowed to be substituted for a type parameter constrained to `new()`, as there is no way for the generic instantiation to ensure that the requirements are satisfied. ### `struct` `default`s Required members are not enforced on instances of `struct` types created with `default` or `default(StructType)`. They are enforced for `struct` instances created with `new StructType()`, even when `StructType` has no parameterless constructor and the default struct constructor is used. ### Accessibility It is an error to mark a member required if the member cannot be set in any context where the containing type is visible. * If the member is a field, it cannot be `readonly`. * If the member is a property, it must have a setter or initer at least as accessible as the member's containing type. This means the following cases are not allowed: ```cs interface I { int Prop1 { get; } } public class Base { public virtual int Prop2 { get; set; } protected required int _field; // Error: _field is not at least as visible as Base. Open question below about the protected constructor scenario public required readonly int _field2; // Error: required fields cannot be readonly protected Base() { } protected class Inner { protected required int PropInner { get; set; } // Error: PropInner cannot be set inside Base or Derived } } public class Derived : Base, I { required int I.Prop1 { get; } // Error: explicit interface implementions cannot be required as they cannot be set in an object initializer public required override int Prop2 { get; set; } // Error: this property is hidden by Derived.Prop2 and cannot be set in an object initializer public new int Prop2 { get; } public required int Prop3 { get; } // Error: Required member must have a setter or initer public required int Prop4 { get; internal set; } // Error: Required member setter must be at least as visible as the constructor of Derived } ``` It is an error to hide a `required` member, as that member can no longer be set by a consumer. When overriding a `required` member, the `required` keyword must be included on the method signature. This is done so that if we ever want to allow unrequiring a property with an override in the future, we have design space to do so. Overrides are allowed to mark a member `required` where it was not `required` in the base type. A member so-marked is added to the required members list of the derived type. Types are allowed to override required virtual properties. This means that if the base virtual property has storage, and the derived type tries to access the base implementation of that property, they could observe uninitialized storage. NB: This is a general C# anti-pattern, and we don't think that this proposal should attempt to address it. ### Effect on nullable analysis Members that are marked `required` are not required to be initialized to a valid nullable state at the end of a constructor. All `required` members from this type and any base types are considered by nullable analysis to be default at the beginning of any constructor in that type, unless chaining to a `this` or `base` constructor that is attributed with `SetsRequiredMembersAttribute`. Nullable analysis will warn about all `required` members from the current and base types that do not have a valid nullable state at the end of a constructor attributed with `SetsRequiredMembersAttribute`. ```cs #nullable enable public class Base { public required string Prop1 { get; set; } public Base() {} [SetsRequiredMembers] public Base(int unused) { Prop1 = ""; } } public class Derived : Base { public required string Prop2 { get; set; } [SetsRequiredMembers] public Derived() : base() { } // Warning: Prop1 and Prop2 are possibly null. [SetsRequiredMembers] public Derived(int unused) : base() { Prop1.ToString(); // Warning: possibly null dereference Prop2.ToString(); // Warning: possibly null dereference } [SetsRequiredMembers] public Derived(int unused, int unused2) : this() { Prop1.ToString(); // Ok Prop2.ToString(); // Ok } [SetsRequiredMembers] public Derived(int unused1, int unused2, int unused3) : base(unused1) { Prop1.ToString(); // Ok Prop2.ToString(); // Warning: possibly null dereference } } ``` ### Metadata Representation The following 2 attributes are known to the C# compiler and required for this feature to function: ```cs namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public sealed class RequiredMemberAttribute : Attribute { public RequiredMemberAttribute() {} } } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] public sealed class SetsRequiredMembersAttribute : Attribute { public SetsRequiredMembersAttribute() {} } } ``` It is an error to manually apply `RequiredMemberAttribute` to a type. Any member that is marked `required` has a `RequiredMemberAttribute` applied to it. In addition, any type that defines such members is marked with `RequiredMemberAttribute`, as a marker to indicate that there are required members in this type. Note that if type `B` derives from `A`, and `A` defines `required` members but `B` does not add any new or override any existing `required` members, `B` will not be marked with a `RequiredMemberAttribute`. To fully determine whether there are any required members in `B`, checking the full inheritance hierarchy is necessary. Any constructor in a type with `required` members that does not have `SetsRequiredMembersAttribute` applied to it is marked with two attributes: 1. `System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute` with the feature name `"RequiredMembers"`. 2. `System.ObsoleteAttribute` with the string `"Types with required members are not supported in this version of your compiler"`, and the attribute is marked as an error, to prevent any older compilers from using these constructors. We don't use a `modreq` here because it is a goal to maintain binary compat: if the last `required` property was removed from a type, the compiler would no longer synthesize this `modreq`, which is a binary-breaking change and all consumers would need to be recompiled. A compiler that understands `required` members will ignore this obsolete attribute. Note that members can come from base types as well: even if there are no new `required` members in the current type, if any base type has `required` members, this `Obsolete` attribute will be generated. If the constructor already has an `Obsolete` attribute, no additional `Obsolete` attribute will be generated. We use both `ObsoleteAttribute` and `CompilerFeatureRequiredAttribute` because the latter is new this release, and older compilers don't understand it. In the future, we may be able to drop the `ObsoleteAttribute` and/or not use it to protect new features, but for now we need both for full protection. To build the full list of `required` members `R` for a given type `T`, including all base types, the following algorithm is run: 1. For every `Tb`, starting with `T` and working through the base type chain until `object` is reached. 2. If `Tb` is marked with `RequiredMemberAttribute`, then all members of `Tb` marked with `RequiredMemberAttribute` are gathered into `Rb` 1. For every `Ri` in `Rb`, if `Ri` is overridden by any member of `R`, it is skipped. 2. Otherwise, if any `Ri` is hidden by a member of `R`, then the lookup of required members fails and no further steps are taken. Calling any constructor of `T` not attributed with `SetsRequiredMembers` issues an error. 3. Otherwise, `Ri` is added to `R`. ## Open Questions ### Nested member initializers What will the enforcement mechanisms for nested member initializers be? Will they be disallowed entirely? ```cs class Range { public required Location Start { get; init; } public required Location End { get; init; } } class Location { public required int Column { get; init; } public required int Line { get; init; } } _ = new Range { Start = { Column = 0, Line = 0 }, End = { Column = 1, Line = 0 } } // Would this be allowed if Location is a struct type? _ = new Range { Start = new Location { Column = 0, Line = 0 }, End = new Location { Column = 1, Line = 0 } } // Or would this form be necessary instead? ``` ## Discussed Questions ### Level of enforcement for `init` clauses The `init` clause feature wasn't implemented in C# 11. It remains an active proposal.
Do we strictly enforce that members specified in an `init` clause without an initializer must initialize all members? It seems likely that we do, otherwise we create an easy pit-of-failure. However, we also run the risk of reintroducing the same problems we solved with `MemberNotNull` in C# 9. If we want to strictly enforce this, we will likely need a way for a helper method to indicate that it sets a member. Some possible syntaxes we've discussed for this: * Allow `init` methods. These methods are only allowed to be called from a constructor or from another `init` method, and can access `this` as if it's in the constructor (ie, set `readonly` and `init` fields/properties). This can be combined with `init` clauses on such methods. A `init` clause would be considered satisfied if the member in the clause is definitely assigned in the body of the method/constructor. Calling a method with a `init` clause that includes a member counts as assigning to that member. If we do decided that this is a route we want to pursue, now or in the future, it seems likely that we should not use `init` as the keyword for the init clause on a constructor, as that would be confusing. * Allow the `!` operator to suppress the warning/error explicitly. If initializing a member in a complicated way (such as in a shared method), the user can add a `!` to the init clause to indicate the compiler should not check for initialization. **Conclusion**: After discussion we like the idea of the `!` operator. It allows the user to be intentional about more complicated scenarios while also not creating a large design hole around init methods and annotating every method as setting members X or Y. `!` was chosen because we already use it for suppressing nullable warnings, and using it to tell the compiler "I'm smarter than you" in another place is a natural extension of the syntax form.
### Required interface members This proposal does not allow interfaces to mark members as required. This protects us from having to figure out complex scenarios around `new()` and interface constraints in generics right now, and is directly related to both factories and generic construction. In order to ensure that we have design space in this area, we forbid `required` in interfaces, and forbid types with _required\_member\_lists_ from being substituted for type parameters constrained to `new()`. When we want to take a broader look at generic construction scenarios with factories, we can revisit this issue. ### Syntax questions The `init` clause feature wasn't implemented in C# 11. It remains an active proposal.
* Is `init` the right word? `init` as a postfix modifier on the constructor might interfere if we ever want to reuse it for factories and also enable `init` methods with a prefix modifier. Other possibilities: * `set` * Is `required` the right modifier for specifying that all members are initialized? Others suggested: * `default` * `all` * With a ! to indicate complex logic * Should we require a separator between the `base`/`this` and the `init`? * `:` separator * ',' separator * Is `required` the right modifier? Other alternatives that have been suggested: * `req` * `require` * `mustinit` * `must` * `explicit` **Conclusion**: We have removed the `init` constructor clause for now, and are proceeding with `required` as the property modifier.
### Init clause restrictions The `init` clause feature wasn't implemented in C# 11. It remains an active proposal.
Should we allow access to `this` in the init clause? If we want the assignment in `init` to be a shorthand for assigning the member in the constructor itself, it seems like we should. Additionally, does it create a new scope, like `base()` does, or does it share the same scope as the method body? This is particularly important for things like local functions, which the init clause may want to access, or for name shadowing, if an init expression introduces a variable via `out` parameter. **Conclusion**: `init` clause has been removed.
### Accessibility requirements and `init` The `init` clause feature wasn't implemented in C# 11. It remains an active proposal.
In versions of this proposal with the `init` clause, we talked about being able to have the following scenario: ```cs public class Base { protected required int _field; protected Base() {} // Contract required that _field is set } public class Derived : Base { public Derived() : init(_field = 1) // Contract is fulfilled and _field is removed from the required members list { } } ``` However, we have removed the `init` clause from the proposal at this point, so we need to decide whether to allow this scenario in a limited fashion. The options we have are: 1. Disallow the scenario. This is the most conservative approach, and the rules in the [Accessibility](#accessibility) are currently written with this assumption in mind. The rule is that any member that is required must be at least as visible as its containing type. 2. Require that all constructors are either: 1. No more visible than the least-visible required member. 2. Have the `SetsRequiredMembersAttribute` applied to the constructor. These would ensure that anyone who can see a constructor can either set all the things it exports, or there is nothing to set. This could be useful for types that are only ever created via static `Create` methods or similar builders, but the utility seems overall limited. 3. Readd a way to remove specific parts of the contract to the proposal, as discussed in [LDM](https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-10-25.md) previously. **Conclusion**: Option 1, all required members must be at least as visible as their containing type.
### Override rules The current spec says that the `required` keyword needs to be copied over and that overrides can make a member _more_ required, but not less. Is that what we want to do? Allowing removal of requirements needs more contract modification abilities than we are currently proposing. **Conclusion**: Adding `required` on override is allowed. If the overridden member is `required`, the overridding member must also be `required`. ### Alternative metadata representation We could also take a different approach to metadata representation, taking a page from extension methods. We could put a `RequiredMemberAttribute` on the type to indicate that the type contains required members, and then put a `RequiredMemberAttribute` on each member that is required. This would simplify the lookup sequence (no need to do member lookup, just look for members with the attribute). **Conclusion**: Alternative approved. ### Metadata Representation The [Metadata Representation](#metadata-representation) needs to be approved. We additionally need to decide whether these attributes should be included in the BCL. 1. For `RequiredMemberAttribute`, this attribute is more akin to the general embedded attributes we use for nullable/nint/tuple member names, and will not be manually applied by the user in C#. It's possible that other languages might want to manually apply this attribute, however. 2. `SetsRequiredMembersAttribute`, on the other hand, is directly used by consumers, and thus should likely be in the BCL. If we go with the alternative representation in the previous section, that might change the calculus on `RequiredMemberAttribute`: instead of being similar to the general embedded attributes for `nint`/nullable/tuple member names, it's closer to `System.Runtime.CompilerServices.ExtensionAttribute`, which has been in the framework since extension methods shipped. **Conclusion**: We will put both attributes in the BCL. ### Warning vs Error Should not setting a required member be a warning or an error? It is certainly possible to trick the system, via `Activator.CreateInstance(typeof(C))` or similar, which means we may not be able to fully guarantee all properties are always set. We also allow suppression of the diagnostics at the constructor-site by using the `!`, which we generally do not allow for errors. However, the feature is similar to readonly fields or init properties, in that we hard error if users attempt to set such a member after initialization, but they can be circumvented by reflection. **Conclusion**: Errors. ================================================ FILE: proposals/csharp-11.0/static-abstracts-in-interfaces.md ================================================ # Static abstract members in interfaces [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary An interface is allowed to specify abstract static members that implementing classes and structs are then required to provide an explicit or implicit implementation of. The members can be accessed off of type parameters that are constrained by the interface. ## Motivation [motivation]: #motivation There is currently no way to abstract over static members and write generalized code that applies across types that define those static members. This is particularly problematic for member kinds that *only* exist in a static form, notably operators. This feature allows generic algorithms over numeric types, represented by interface constraints that specify the presence of given operators. The algorithms can therefore be expressed in terms of such operators: ``` c# // Interface specifies static properties and operators interface IAddable where T : IAddable { static abstract T Zero { get; } static abstract T operator +(T t1, T t2); } // Classes and structs (including built-ins) can implement interface struct Int32 : …, IAddable { static Int32 IAddable.operator +(Int32 x, Int32 y) => x + y; // Explicit public static int Zero => 0; // Implicit } // Generic algorithms can use static members on T public static T AddAll(T[] ts) where T : IAddable { T result = T.Zero; // Call static operator foreach (T t in ts) { result += t; } // Use `+` return result; } // Generic method can be applied to built-in and user-defined types int sixtyThree = AddAll(new [] { 1, 2, 4, 8, 16, 32 }); ``` ## Syntax ### Interface members The feature would allow static interface members to be declared virtual. #### Rules before C# 11 Before C# 11, instance members in interfaces are implicitly abstract (or virtual if they have a default implementation), but can optionally have an `abstract` (or `virtual`) modifier. Non-virtual instance members must be explicitly marked as `sealed`. Static interface members today are implicitly non-virtual, and do not allow `abstract`, `virtual` or `sealed` modifiers. #### Proposal ##### Abstract static members Static interface members other than fields are allowed to also have the `abstract` modifier. Abstract static members are not allowed to have a body (or in the case of properties, the accessors are not allowed to have a body). ``` c# interface I where T : I { static abstract void M(); static abstract T P { get; set; } static abstract event Action E; static abstract T operator +(T l, T r); static abstract bool operator ==(T l, T r); static abstract bool operator !=(T l, T r); static abstract implicit operator T(string s); static abstract explicit operator string(T t); } ``` ##### Virtual static members Static interface members other than fields are allowed to also have the `virtual` modifier. Virtual static members are required to have a body. ``` c# interface I where T : I { static virtual void M() {} static virtual T P { get; set; } static virtual event Action E; static virtual T operator +(T l, T r) { throw new NotImplementedException(); } } ``` ##### Explicitly non-virtual static members For symmetry with non-virtual instance members, static members (except fields) should be allowed an optional `sealed` modifier, even though they are non-virtual by default: ``` c# interface I0 { static sealed void M() => Console.WriteLine("Default behavior"); static sealed int f = 0; static sealed int P1 { get; set; } static sealed int P2 { get => f; set => f = value; } static sealed event Action E1; static sealed event Action E2 { add => E1 += value; remove => E1 -= value; } static sealed I0 operator +(I0 l, I0 r) => l; } ``` ### Implementation of interface members #### Today's rules Classes and structs can implement abstract instance members of interfaces either implicitly or explicitly. An implicitly implemented interface member is a normal (virtual or non-virtual) member declaration of the class or struct that just "happens" to also implement the interface member. The member can even be inherited from a base class and thus not even be present in the class declaration. An explicitly implemented interface member uses a qualified name to identify the interface member in question. The implementation is not directly accessible as a member on the class or struct, but only through the interface. #### Proposal No new syntax is needed in classes and structs to facilitate implicit implementation of static abstract interface members. Existing static member declarations serve that purpose. Explicit implementations of static abstract interface members use a qualified name along with the `static` modifier. ``` c# class C : I { string _s; public C(string s) => _s = s; static void I.M() => Console.WriteLine("Implementation"); static C I.P { get; set; } static event Action I.E // event declaration must use field accessor syntax { add { ... } remove { ... } } static C I.operator +(C l, C r) => new C($"{l._s} {r._s}"); static bool I.operator ==(C l, C r) => l._s == r._s; static bool I.operator !=(C l, C r) => l._s != r._s; static implicit I.operator C(string s) => new C(s); static explicit I.operator string(C c) => c._s; } ``` ## Semantics ### Operator restrictions Today all unary and binary operator declarations have some requirement involving at least one of their operands to be of type `T` or `T?`, where `T` is the instance type of the enclosing type. These requirements need to be relaxed so that a restricted operand is allowed to be of a type parameter that counts as "the instance type of the enclosing type". In order for a type parameter `T` to count as "the instance type of the enclosing type", it must meet the following requirements: - `T` is a direct type parameter on the interface in which the operator declaration occurs, and - `T` is *directly* constrained by what the spec calls the "instance type" - i.e. the surrounding interface with its own type parameters used as type arguments. ### Equality operators and conversions Abstract/virtual declarations of `==` and `!=` operators, as well as abstract/virtual declarations of implicit and explicit conversion operators will be allowed in interfaces. Derived interfaces will be allowed to implement them too. For `==` and `!=` operators, at least one parameter type must be a type parameter that counts as "the instance type of the enclosing type", as defined in the previous section. ### Implementing static abstract members The rules for when a static member declaration in a class or struct is considered to implement a static abstract interface member, and for what requirements apply when it does, are the same as for instance members. ***TBD:** There may be additional or different rules necessary here that we haven't yet thought of.* ### Interfaces as type arguments We discussed the issue raised by https://github.com/dotnet/csharplang/issues/5955 and decided to add a restriction around usage of an interface as a type argument (https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md#type-hole-in-static-abstracts). Here is the restriction as it was proposed by https://github.com/dotnet/csharplang/issues/5955 and approved by the LDM. An interface containing or inheriting a static abstract/virtual member that does not have most specific implementation in the interface cannot be used as a type argument. If all static abstract/virtual members have most specific implementation, the interface can be used as a type argument. ### Accessing static abstract interface members A static abstract interface member `M` may be accessed on a type parameter `T` using the expression `T.M` when `T` is constrained by an interface `I` and `M` is an accessible static abstract member of `I`. ``` c# T M() where T : I { T.M(); T t = T.P; T.E += () => { }; return t + T.P; } ``` At runtime, the actual member implementation used is the one that exists on the actual type provided as a type argument. ``` c# C c = M(); // The static members of C get called ``` Since query expressions are spec'ed as a syntactic rewrite, C# actually lets you use a *type* as the query source, as long as it has static members for the query operators you use! In other words, if the *syntax* fits, we allow it! We think this behavior was not intentional or important in the original LINQ, and we don't want to do the work to support it on type parameters. If there are scenarios out there we will hear about them, and can choose to embrace this later. ### Variance safety [§18.2.3.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/interfaces.md#18232-variance-safety) Variance safety rules should apply to signatures of static abstract members. The addition proposed in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/variance-safety-for-static-interface-members.md#variance-safety should be adjusted from *These restrictions do not apply to occurrences of types within declarations of static members.* to *These restrictions do not apply to occurrences of types within declarations of **non-virtual, non-abstract** static members.* ### [§10.5.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1054-user-defined-implicit-conversions) User defined implicit conversions The following bullet points - Determine the types `S`, `S₀` and `T₀`. - If `E` has a type, let `S` be that type. - If `S` or `T` are nullable value types, let `Sᵢ` and `Tᵢ` be their underlying types, otherwise let `Sᵢ` and `Tᵢ` be `S` and `T`, respectively. - If `Sᵢ` or `Tᵢ` are type parameters, let `S₀` and `T₀` be their effective base classes, otherwise let `S₀` and `T₀` be `Sₓ` and `Tᵢ`, respectively. - Find the set of types, `D`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), and `T0` (if `T0` is a class or struct). - Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing `S` to a type encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. are adjusted as follows: - Determine the types `S`, `S₀` and `T₀`. - If `E` has a type, let `S` be that type. - If `S` or `T` are nullable value types, let `Sᵢ` and `Tᵢ` be their underlying types, otherwise let `Sᵢ` and `Tᵢ` be `S` and `T`, respectively. - If `Sᵢ` or `Tᵢ` are type parameters, let `S₀` and `T₀` be their effective base classes, otherwise let `S₀` and `T₀` be `Sₓ` and `Tᵢ`, respectively. - Find the set of applicable user-defined and lifted conversion operators, `U`. - Find the set of types, `D1`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), and `T0` (if `T0` is a class or struct). - Find the set of applicable user-defined and lifted conversion operators, `U1`. This set consists of the user-defined and lifted implicit conversion operators declared by the classes or structs in `D1` that convert from a type encompassing `S` to a type encompassed by `T`. - If `U1` is not empty, then `U` is `U1`. Otherwise, - Find the set of types, `D2`, from which user-defined conversion operators will be considered. This set consists of `Sᵢ` *effective interface set* and their base interfaces (if `Sᵢ` is a type parameter), and `Tᵢ` *effective interface set* (if `Tᵢ` is a type parameter). - Find the set of applicable user-defined and lifted conversion operators, `U2`. This set consists of the user-defined and lifted implicit conversion operators declared by the interfaces in `D2` that convert from a type encompassing `S` to a type encompassed by `T`. - If `U2` is not empty, then `U` is `U2` - If `U` is empty, the conversion is undefined and a compile-time error occurs. ### [§10.3.9](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1039-user-defined-explicit-conversions) User-defined explicit conversions The following bullet points - Determine the types `S`, `S₀` and `T₀`. - If `E` has a type, let `S` be that type. - If `S` or `T` are nullable value types, let `Sᵢ` and `Tᵢ` be their underlying types, otherwise let `Sᵢ` and `Tᵢ` be `S` and `T`, respectively. - If `Sᵢ` or `Tᵢ` are type parameters, let `S₀` and `T₀` be their effective base classes, otherwise let `S₀` and `T₀` be `Sᵢ` and `Tᵢ`, respectively. - Find the set of types, `D`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), `T0` (if `T0` is a class or struct), and the base classes of `T0` (if `T0` is a class). - Find the set of applicable user-defined and lifted conversion operators, `U`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in `D` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. If `U` is empty, the conversion is undefined and a compile-time error occurs. are adjusted as follows: - Determine the types `S`, `S₀` and `T₀`. - If `E` has a type, let `S` be that type. - If `S` or `T` are nullable value types, let `Sᵢ` and `Tᵢ` be their underlying types, otherwise let `Sᵢ` and `Tᵢ` be `S` and `T`, respectively. - If `Sᵢ` or `Tᵢ` are type parameters, let `S₀` and `T₀` be their effective base classes, otherwise let `S₀` and `T₀` be `Sᵢ` and `Tᵢ`, respectively. - Find the set of applicable user-defined and lifted conversion operators, `U`. - Find the set of types, `D1`, from which user-defined conversion operators will be considered. This set consists of `S0` (if `S0` is a class or struct), the base classes of `S0` (if `S0` is a class), `T0` (if `T0` is a class or struct), and the base classes of `T0` (if `T0` is a class). - Find the set of applicable user-defined and lifted conversion operators, `U1`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in `D1` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. - If `U1` is not empty, then `U` is `U1`. Otherwise, - Find the set of types, `D2`, from which user-defined conversion operators will be considered. This set consists of `Sᵢ` *effective interface set* and their base interfaces (if `Sᵢ` is a type parameter), and `Tᵢ` *effective interface set* and their base interfaces (if `Tᵢ` is a type parameter). - Find the set of applicable user-defined and lifted conversion operators, `U2`. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the interfaces in `D2` that convert from a type encompassing or encompassed by `S` to a type encompassing or encompassed by `T`. - If `U2` is not empty, then `U` is `U2` - If `U` is empty, the conversion is undefined and a compile-time error occurs. ### Default implementations An *additional* feature to this proposal is to allow static virtual members in interfaces to have default implementations, just as instance virtual/abstract members do. One complication here is that default implementations would want to call other static virtual members "virtually". Allowing static virtual members to be called directly on the interface would require flowing a hidden type parameter representing the "self" type that the current static method really got invoked on. This seems complicated, expensive and potentially confusing. We discussed a simpler version which maintains the limitations of the current proposal that static virtual members can *only* be invoked on type parameters. Since interfaces with static virtual members will often have an explicit type parameter representing a "self" type, this wouldn't be a big loss: other static virtual members could just be called on that self type. This version is a lot simpler, and seems quite doable. At https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md#default-implementations-of-abstract-statics we decided to support Default Implementations of static members following/expanding the rules established in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/default-interface-methods.md accordingly. ### Pattern matching Given the following code, a user might reasonably expect it to print "True" (as it would if the constant pattern was written inline): ```cs M(1.0); static void M(T t) where T : INumberBase { Console.WriteLine(t is 1); // Error. Cannot use a numeric constant Console.WriteLine((t is int i) && (i is 1)); } ``` However, because the input type of the pattern is not `double`, the constant `1` pattern will first type check the incoming `T` against `int`. This is unintuitive, so it is blocked until a future C# version adds better handling for numeric matching against types derived from `INumberBase`. To do so, we will say that, we will explicitly recognize `INumberBase` as the type that all "numbers" will derive from, and block the pattern if we're trying to match a numeric constant pattern against a number type that we can't represent the pattern in (ie, a type parameter constrained to `INumberBase`, or a user-defined number type that inherits from `INumberBase`). Formally, we add an exception to the definition of *pattern-compatible* for constant patterns: > A constant pattern tests the value of an expression against a constant value. The constant may be any constant expression, such as a literal, the name of a declared `const` variable, or an enumeration constant. When the input value is not an open type, the constant expression is implicitly converted to the type of the matched expression; if the type of the input value is not *pattern-compatible* with the type of the constant expression, the pattern-matching operation is an error. **If the constant expression being matched against is a numeric value, the input value is a type that inherits from `System.Numerics.INumberBase`, and there is no constant conversion from the constant expression to the type of the input value, the pattern-matching operation is an error.** We also add a similar exception for relational patterns: > When the input is a type for which a suitable built-in binary relational operator is defined that is applicable with the input as its left operand and the given constant as its right operand, the evaluation of that operator is taken as the meaning of the relational pattern. Otherwise we convert the input to the type of the expression using an explicit nullable or unboxing conversion. It is a compile-time error if no such conversion exists. **It is a compile-time error if the input type is a type parameter constrained to or a type inheriting from `System.Numerics.INumberBase` and the input type has no suitable built-in binary relational operator defined.** The pattern is considered not to match if the conversion fails. If the conversion succeeds then the result of the pattern-matching operation is the result of evaluating the expression e OP v where e is the converted input, OP is the relational operator, and v is the constant expression. ## Drawbacks [drawbacks]: #drawbacks - "static abstract" is a new concept and will meaningfully add to the conceptual load of C#. - It's not a cheap feature to build. We should make sure it's worth it. ## Alternatives [alternatives]: #alternatives ### Structural constraints An alternative approach would be to have "structural constraints" directly and explicitly requiring the presence of specific operators on a type parameter. The drawbacks of that are: - This would have to be written out every time. Having a named constraint seems better. - This is a whole new kind of constraint, whereas the proposed feature utilizes the existing concept of interface constraints. - It would only work for operators, not (easily) other kinds of static members. ## Unresolved questions [unresolved]: #unresolved-questions ### Static abstract interfaces and static classes See https://github.com/dotnet/csharplang/issues/5783 and https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md#static-abstract-interfaces-and-static-classes for more information. ## Design meetings - https://github.com/dotnet/csharplang/blob/master/meetings/2021/LDM-2021-02-08.md - https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-05.md - https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-29.md - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-16.md - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-28.md - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-06.md - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-06-06.md ================================================ FILE: proposals/csharp-11.0/unsigned-right-shift-operator.md ================================================ # Unsigned right shift operator [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary An unsigned right shift operator will be supported by C# as a built-in operator (for primitive integral types) and as a user-defined operator. ## Motivation [motivation]: #motivation When working with signed integral value, it is not uncommon that you need to shift bits right without replicating the high order bit on each shift. While this can be achieved for primitive integral types with a regular shift operator, a cast to an unsigned type before the shift operation and a cast back after it is required. Within the context of the generic math interfaces the libraries are planning to expose, this is potentially more problematic as the type might not necessary have an unsigned counterpart defined or known upfront by the generic math code, yet an algorithm might rely on ability to perform an unsigned right shift operation. ## Detailed design [design]: #detailed-design ### Operators and punctuators Section [§6.4.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/lexical-structure.md#646-operators-and-punctuators) will be adjusted to include `>>>` operator - the unsigned right shift operator: ```antlr unsigned_right_shift : '>>>' ; unsigned_right_shift_assignment : '>>>=' ; ``` No characters of any kind (not even whitespace) are allowed between the tokens in *unsigned_right_shift* and *unsigned_right_shift_assignment* productions. These productions are treated specially in order to enable the correct handling of *type_parameter_list*s. ### Shift operators Section [§12.11](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1211-shift-operators) will be adjusted to include `>>>` operator - the unsigned right shift operator: The `<<`, `>>` and `>>>` operators are used to perform bit shifting operations. ```antlr shift_expression : additive_expression | shift_expression '<<' additive_expression | shift_expression right_shift additive_expression | shift_expression unsigned_right_shift additive_expression ; ``` For an operation of the form `x << count` or `x >> count` or `x >>> count`, binary operator overload resolution ([§12.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1245-binary-operator-overload-resolution)) is applied to select a specific operator implementation. The operands are converted to the parameter types of the selected operator, and the type of the result is the return type of the operator. The predefined unsigned shift operators will support the same set of signatures that predefined signed shift operators support today in the current implementation. * Shift right: ```csharp int operator >>>(int x, int count); uint operator >>>(uint x, int count); long operator >>>(long x, int count); ulong operator >>>(ulong x, int count); nint operator >>>(nint x, int count); nuint operator >>>(nuint x, int count); ``` The `>>>` operator shifts `x` right by a number of bits computed as described below. The low-order bits of `x` are discarded, the remaining bits are shifted right, and the high-order empty bit positions are set to zero. For the predefined operators, the number of bits to shift is computed as follows: * When the type of `x` is `int` or `uint`, the shift count is given by the low-order five bits of `count`. In other words, the shift count is computed from `count & 0x1F`. * When the type of `x` is `long` or `ulong`, the shift count is given by the low-order six bits of `count`. In other words, the shift count is computed from `count & 0x3F`. If the resulting shift count is zero, the shift operators simply return the value of `x`. Shift operations never cause overflows and produce the same results in `checked` and `unchecked` contexts. ### Assignment operators Section [§12.21](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1221-assignment-operators) will be adjusted to include *unsigned_right_shift_assignment* as follows: ```antlr assignment_operator : '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | right_shift_assignment | unsigned_right_shift_assignment ; ``` ### Integral types The Integral types [§8.3.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#836-integral-types) section will be adjusted to include information about `>>>` operator. The relevant bullet point is the following: * For the binary `<<`, `>>` and `>>>` operators, the left operand is converted to type `T`, where `T` is the first of `int`, `uint`, `long`, and `ulong` that can fully represent all possible values of the operand. The operation is then performed using the precision of type `T`, and the type of the result is `T`. ### Constant expressions Operator `>>>` will be added to the set of constructs permitted in constant expressions at [§12.23](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1223-constant-expressions). ### Operator overloading Operator `>>>` will be added to the set of overloadable binary operators at [§12.4.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1243-operator-overloading). ### Lifted operators Operator `>>>` will be added to the set of binary operators permitting a lifted form at [§12.4.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1248-lifted-operators). ### Operator precedence and associativity Section [§12.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1242-operator-precedence-and-associativity) will be adjusted to add `>>>` operator to the "Shift" category and `>>>=` operator to the "Assignment and lambda expression" category. ### Grammar ambiguities The `>>>` operator is subject to the same grammar ambiguities described at [§6.2.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/lexical-structure.md#625-grammar-ambiguities) as a regular `>>` operator. ### Operators The [§15.10](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1510-operators) section will be adjusted to include `>>>` operator. ```antlr overloadable_binary_operator : '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' | '<<' | right_shift | unsigned_right_shift | '==' | '!=' | '>' | '<' | '>=' | '<=' ; ``` ### Binary operators The signature of a `>>>` operator is subject to the same rules as those at [§15.10.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15103-binary-operators) for the signature of a `>>` operator. ### Metadata name Section "I.10.3.2 Binary operators" of ECMA-335 already reserved the name for an unsigned right shift operator - op_UnsignedRightShift. ### Linq Expression Trees The `>>>` operator will not be supported in Linq Expression Trees because semantics of predefined `>>>` operators on signed types cannot be accurately represented without adding conversions to an unsigned type and back. See https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md#unsigned-right-shift-operator for more information. ### Dynamic Binding It looks like dynamic binding uses values of System.Linq.Expressions.ExpressionType enum to communicate binary operator kind to the runtime binder. Since we don't have a member specifically representing an unsigned right shift operator, dynamic binding for `>>>` operator will not be supported and the static and dynamic binding ([§12.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#123-static-and-dynamic-binding)) section will be adjusted to reflect that. ## Drawbacks [drawbacks]: #drawbacks ## Alternatives [alternatives]: #alternatives ### Linq Expression Trees The `>>>` operator will be supported in Linq Expressioin Trees. - For a user-defined operator, a BinaryExpression node pointing to the operator method will be created. - For predefined operators - when the first operand is an ansigned type, a BinaryExpression node will be created. - when the first operand is a signed type, a conversion for the first operand to an unsigned type will be added, a BinaryExpression node will be created and conversion for the result back to the signed type will be added. For example: ``` C# Expression> z = (x, y) => x >>> y; // (x, y) => Convert((Convert(x, UInt32) >> y), Int32) ``` `Resolution:` Rejected, see https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md#unsigned-right-shift-operator for more information. ## Unresolved questions [unresolved]: #unresolved-questions ## Design meetings https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md ================================================ FILE: proposals/csharp-11.0/utf8-string-literals.md ================================================ Utf8 Strings Literals === [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary This proposal adds the ability to write UTF8 string literals in C# and have them automatically encoded into their UTF-8 `byte` representation. ## Motivation UTF8 is the language of the web and its use is necessary in significant portions of the .NET stack. While much of data comes in the form of `byte[]` off the network stack there is still significant uses of constants in the code. For example networking stack has to commonly write constants like `"HTTP/1.0\r\n"`, `" AUTH"` or . `"Content-Length: "`. Today there is no efficient syntax for doing this as C# represents all strings using UTF16 encoding. That means developers have to choose between the convenience of encoding at runtime which incurs overhead, including the time spent at startup actually performing the encoding operation (and allocations if targeting a type that doesn't actually require them), or manually translating the bytes and storing in a `byte[]`. ```c# // Efficient but verbose and error prone static ReadOnlySpan AuthWithTrailingSpace => new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 }; WriteBytes(AuthWithTrailingSpace); // Incurs allocation and startup costs performing an encoding that could have been done at compile-time static readonly byte[] s_authWithTrailingSpace = Encoding.UTF8.GetBytes("AUTH "); WriteBytes(s_authWithTrailingSpace); // Simplest / most convenient but terribly inefficient WriteBytes(Encoding.UTF8.GetBytes("AUTH ")); ``` This trade off is a pain point that comes up frequently for our partners in the runtime, ASP.NET and Azure. Often times it causes them to leave performance on the table because they don't want to go through the hassle of writing out the `byte[]` encoding by hand. To fix this we will allow for UTF8 literals in the language and encode them into the UTF8 `byte[]` at compile time. ## Detailed design ### `u8` suffix on string literals The language will provide the `u8` suffix on string literals to force the type to be UTF8. The suffix is case-insensitive, `U8` suffix will be supported and will have the same meaning as `u8` suffix. When the `u8` suffix is used, the value of the literal is a `ReadOnlySpan` containing a UTF-8 byte representation of the string. A null terminator is placed beyond the last byte in memory (and outside the length of the `ReadOnlySpan`) in order to handle some interop scenarios where the call expects null terminated strings. ```c# string s1 = "hello"u8; // Error var s2 = "hello"u8; // Okay and type is ReadOnlySpan ReadOnlySpan s3 = "hello"u8; // Okay. byte[] s4 = "hello"u8; // Error - Cannot implicitly convert type 'System.ReadOnlySpan' to 'byte[]'. byte[] s5 = "hello"u8.ToArray(); // Okay. Span s6 = "hello"u8; // Error - Cannot implicitly convert type 'System.ReadOnlySpan' to 'System.Span'. ``` Since the literals would be allocated as global constants, the lifetime of the resulting `ReadOnlySpan` would not prevent it from being returned or passed around to elsewhere. However, certain contexts, most notably within async functions, do not allow locals of ref struct types, so there would be a usage penalty in those situations, with a `ToArray()` call or similar being required. A `u8` literal doesn't have a constant value. That is because `ReadOnlySpan` cannot be the type of a constant today. If the definition of `const` is expanded in the future to consider `ReadOnlySpan`, then this value should also be considered a constant. Practically though this means a `u8` literal cannot be used as the default value of an optional parameter. ```c# // Error: The argument is not constant void Write(ReadOnlySpan message = "missing"u8) { ... } ``` When the input text for the literal is a malformed UTF16 string, then the language will emit an error: ```c# var bytes = "hello \uD8\uD8"u8; // Error: malformed UTF16 input string var bytes2 = "hello \uD801\uD802"u8; // Allowed: invalid UTF16 values, but it's correctly formed. ``` ### Addition operator A new bullet point will be added to [§12.10.5 Addition operator](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12105-addition-operator) as follows. - UTF8 byte representation concatenation: ```csharp ReadOnlySpan operator +(ReadOnlySpan x, ReadOnlySpan y); ``` This binary `+` operator performs byte sequences concatenation and is applicable if and only if both operands are semantically UTF8 byte representations. An operand is semantically a UTF8 byte representation when it is either a value of a `u8` literal, or a value produced by the UTF8 byte representation concatenation operator. The result of the UTF8 byte representation concatenation is a `ReadOnlySpan` that consists of the bytes of the left operand followed by the bytes of the right operand. A null terminator is placed beyond the last byte in memory (and outside the length of the `ReadOnlySpan`) in order to handle some interop scenarios where the call expects null terminated strings. ### Lowering The language will lower the UTF8 encoded strings exactly as if the developer had typed the resulting `byte[]` literal in code. For example: ```c# ReadOnlySpan span = "hello"u8; // Equivalent to ReadOnlySpan span = new ReadOnlySpan(new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00 }). Slice(0,5); // The `Slice` call will be optimized away by the compiler. ``` That means all optimizations that apply to the `new byte[] { ... }` form will apply to utf8 literals as well. This means the call site will be allocation free as C# will optimize this to be stored in the `.data` section of the PE file. Multiple consecutive applications of UTF8 byte representation concatenation operators are collapsed into a single creation of `ReadOnlySpan` with byte array containing the final byte sequence. ```c# ReadOnlySpan span = "h"u8 + "el"u8 + "lo"u8; // Equivalent to ReadOnlySpan span = new ReadOnlySpan(new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00 }). Slice(0,5); // The `Slice` call will be optimized away by the compiler. ``` ## Drawbacks ### Relying on core APIs The compiler implementation will use `UTF8Encoding` for both invalid string detection as well as translation to `byte[]`. The exact APIs will possibly depend on which target framework the compiler is using. But `UTF8Encoding` will be the workhorse of the implementation. Historically the compiler has avoided using runtime APIs for literal processing. That is because it takes control of how constants are processed away from the language and into the runtime. Concretely it means items like bug fixes can change constant encoding and mean that the outcome of C# compilation depends on which runtime the compiler is executing on. This is not a hypothetical problem. Early versions of Roslyn used `double.Parse` to handle floating point constant parsing. That caused a number of problems. First it meant that some floating point values had different representations between the native compiler and Roslyn. Second as .NET core evolved and fixed long standing bugs in the `double.Parse` code it meant that the meaning of those constants changed in the language depending on what runtime the compiler executed on. As a result the compiler ended up writing its own version of floating point parsing code and removing the dependency on `double.Parse`. This scenario was discussed with the runtime team and we do not feel it has the same problems we've hit before. The UTF8 parsing is stable across runtimes and there are no known issues in this area that are areas for future compat concerns. If one does come up we can re-evaluate the strategy. ## Alternatives ### Target type only The design could rely on target typing only and remove the `u8` suffix on `string` literals. In the majority of cases today the `string` literal is being assigned directly to a `ReadOnlySpan` hence it's unnecessary. ```c# ReadOnlySpan span = "Hello World;" ``` The `u8` suffix exists primarily to support two scenarios: `var` and overload resolution. For the latter consider the following use case: ```c# void Write(ReadOnlySpan span) { ... } void Write(string s) { var bytes = Encoding.UTF8.GetBytes(s); Write(bytes.AsSpan()); } ``` Given the implementation it is better to call `Write(ReadOnlySpan)` and the `u8` suffix makes this convenient: `Write("hello"u8)`. Lacking that developers need to resort to awkward casting `Write((ReadOnlySpan)"hello")`. Still this is a convenience item, the feature can exist without it and it is non-breaking to add it at a later time. ### Wait for Utf8String type While the .NET ecosystem is standardizing on `ReadOnlySpan` as the defacto Utf8 string type today it's possible the runtime will introduce an actual `Utf8String` type is the future. We should evaluate our design here in the face of this possible change and reflect on whether we'd regret the decisions we've made. This should be weighed though against the realistic probability we'll introduce `Utf8String`, a probability which seems to decrease every day we find `ReadOnlySpan` as an acceptable alternative. It seems unlikely that we would regret the target type conversion between string literals and `ReadOnlySpan`. The use of `ReadOnlySpan` as utf8 is embedded in our APIs now and hence there is still value in the conversion even if `Utf8String` comes along and is a "better" type. The language could simply prefer conversions to `Utf8String` over `ReadOnlySpan`. It seems more likely that we'd regret the `u8` suffix pointing to `ReadOnlySpan` instead of `Utf8String`. It would be similar to how we regret that `stackalloc int[]` has a natural type of `int*` instead of `Span`. This is not a deal breaker though, just an inconvenience. ### Conversions between `string` constants and `byte` sequences The conversions in this section have not been implemented. These conversions remain active proposals.
The language will allow conversions between `string` constants and `byte` sequences where the text is converted into the equivalent UTF8 byte representation. Specifically the compiler will allow _string_constant_to_UTF8_byte_representation_conversion_ - implicit conversions from `string` constants to `byte[]`, `Span`, and `ReadOnlySpan`. A new bullet point will be added to the implicit conversions [§10.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#102-implicit-conversions) section. This conversion is not a standard conversion [§10.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#104-standard-conversions). ```c# byte[] array = "hello"; // new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f } Span span = "dog"; // new byte[] { 0x64, 0x6f, 0x67 } ReadOnlySpan span = "cat"; // new byte[] { 0x63, 0x61, 0x74 } ``` When the input text for the conversion is a malformed UTF16 string then the language will emit an error: ```c# const string text = "hello \uD801\uD802"; byte[] bytes = text; // Error: the input string is not valid UTF16 ``` The predominant usage of this feature is expected to be with literals but it will work with any `string` constant value. A conversion from a `string` constant with `null` value will be supported as well. The result of the conversion will be `default` value of the target type. ```c# const string data = "dog" ReadOnlySpan span = data; // new byte[] { 0x64, 0x6f, 0x67 } ``` In the case of any constant operation on strings, such as `+`, the encoding to UTF8 will occur on the final `string` vs. happening for the individual parts and then concatenating the results. This ordering is important to consider because it can impact whether or not the conversion succeeds. ```c# const string first = "\uD83D"; // high surrogate const string second = "\uDE00"; // low surrogate ReadOnlySpan span = first + second; ``` The two parts here are invalid on their own as they are incomplete portions of a surrogate pair. Individually there is no correct translation to UTF8 but together they form a complete surrogate pair that can be successfully translated to UTF8. The _string_constant_to_UTF8_byte_representation_conversion_ is not allowed in Linq Expression Trees. While the inputs to these conversions are constants and the data is fully encoded at compile time, the conversion is **not** considered constant by the language. That is because arrays are not constant today. If the definition of `const` is expanded in the future to consider arrays then these conversions should also be considered. Practically though this means a result of these conversions cannot be used as the default value of an optional parameter. ```c# // Error: The argument is not constant void Write(ReadOnlySpan message = "missing") { ... } ``` Once implemented string literals will have the same problem that other literals have in the language: what type they represent depends on how they are used. C# provides a literal suffix to disambiguate the meaning for other literals. For example developers can write `3.14f` to force the value to be a `float` or `1l` to force the value to be a `long`.
## Unresolved questions The first three design questions relate to string to `Span` / `ReadOnlySpan` conversions.They haven't been implemented.
### (Resolved) Conversions between a `string` constant with `null` value and `byte` sequences Whether this conversion is supported and, if so, how it is performed is not specified. *Proposal:* Allow implicit conversions from a `string` constant with `null` value to `byte[]`, `Span`, and `ReadOnlySpan`. The result of the conversion is `default` value of the target type. *Resolution:* The proposal is approved - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversions-from-null-literals. ### (Resolved) Where does _string_constant_to_UTF8_byte_representation_conversion_ belong? Is _string_constant_to_UTF8_byte_representation_conversion_ a bullet point in the implicit conversions [§10.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#102-implicit-conversions) section on its own, or is it part of [§10.2.11](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#10211-implicit-constant-expression-conversions), or does it belong to some other existing implicit conversions group? *Proposal:* It is a new bullet point in implicit conversions [§10.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#102-implicit-conversions), similar to "Implicit interpolated string conversions" or "Method group conversions". It doesn't feel like it belongs to "Implicit constant expression conversions" because, even though the source is a constant expression, the result is never a constant expression. Also, "Implicit constant expression conversions" are considered to be "Standard implicit conversions" [§10.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1042-standard-implicit-conversions), which is likely to lead to non-trivial behavior changes involving user-defined conversions. *Resolution:* We will introduce a new conversion kind for string constant to UTF-8 bytes - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-kinds ### (Resolved) Is _string_constant_to_UTF8_byte_representation_conversion_ a standard conversion In addition to "pure" Standard Conversions (the standard conversions are those pre-defined conversions that can occur as part of a user-defined conversion), compiler also treats some predefined conversions as "somewhat" standard. For example, an implicit interpolated string conversion can occur as part of a user-defined conversion if there is an explicit cast to the target type in code. As if it is a Standard Explicit Conversion, even though it is an implicit conversion not explicitly included into the set of standard implicit or explicit conversions. For example: ``` C# class C { static void Main() { C1 x = $"hello"; // error CS0266: Cannot implicitly convert type 'string' to 'C1'. An explicit conversion exists (are you missing a cast?) var y = (C1)$"dog"; // works } } class C1 { public static implicit operator C1(System.FormattableString x) => new C1(); } ``` *Proposal:* The new conversion is not a standard conversion. This will avoid non-trivial behavior changes involving user-defined conversions. For example, we won't need to worry about user-defined cinversions under implicit tuple literal conversions, etc. *Resolution:* Not a standard conversion, for now - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#implicit-standard-conversion.
### (Resolved) Linq Expression Tree conversion Should _string_constant_to_UTF8_byte_representation_conversion_ be allowed in context of a Linq Expression Tree conversion? We can disallow it for now, or we could simply include the "lowered" form into the tree. For example: ``` C# Expression> x = () => "hello"; // () => new [] {104, 101, 108, 108, 111} Expression y = () => "dog"; // () => new Span`1(new [] {100, 111, 103}) Expression z = () => "cat"; // () => new ReadOnlySpan`1(new [] {99, 97, 116}) ``` What about string literals with `u8` suffix? We could surface those as byte array creations: ``` C# Expression> x = () => "hello"u8; // () => new [] {104, 101, 108, 108, 111} ``` *Resolution:* Disallow in Linq Expression Trees - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#expression-tree-representation. ### (Resolved) The natural type of a string literal with `u8` suffix The "Detailed design" section says: "The natural type though will be `ReadOnlySpan`." At the same time: "When the `u8` suffix is used the literal can still be converted to any of the allowed types: `byte[]`, `Span` or `ReadOnlySpan`." There are several disadvantages with this approach: - `ReadOnlySpan` is not available on desktop framework; - There are no existing conversions from `ReadOnlySpan` to `byte[]` or `Span`. In order to support them we will likely need to treat the literals as target typed. Both the language rules and implementation will become more complicated. *Proposal:* The natural type will be `byte[]`. It is readily available on all frameworks. BTW, at runtime we will always be starting with creating a byte array, even with the original proposal. We also don't need any special conversion rules to support conversions to `Span` and `ReadOnlySpan`. There are already implicit user-defined conversions from `byte[]` to `Span` and `ReadOnlySpan`. There is even implicit user-defined conversion to `ReadOnlyMemory` (see the "Depth of the conversion" question below). There is a disadvantage, language doesn't allow chaining user-defined conversions. So, the following code will not compile: ```C# using System; class C { static void Main() { var y = (C2)"dog"u8; // error CS0030: Cannot convert type 'byte[]' to 'C2' var z = (C3)"cat"u8; // error CS0030: Cannot convert type 'byte[]' to 'C3' } } class C2 { public static implicit operator C2(Span x) => new C2(); } class C3 { public static explicit operator C3(ReadOnlySpan x) => new C3(); } ``` However, as with any user-defined conversion, an explicit cast can be used to make one user-defined conversion a part of another user-defined conversion. It feels like all motivating scenarios are going to be addressed with `byte[]` as the natural type, but the language rules and implementation will be significantly simpler. *Resolution:* The proposal is approved - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#natural-type-of-u8-literals. We will likely want to have a deeper debate about whether `u8` string literals should have a type of a mutable array, but we don't think that debate is necessary for now. Only the explicit conversion operator has been implemented. ### (Resolved) Depth of the conversion Will it also work anywhere that a byte[] could work? Consider: ```c# static readonly ReadOnlyMemory s_data1 = "Data"u8; static readonly ReadOnlyMemory s_data2 = "Data"; ``` The first example likely should work because of the natural type that comes from `u8`. The second example is hard to make work because it requires conversions in both directions. That is unless we add `ReadOnlyMemory` as one of the allowed conversion types. *Proposal:* Don't do anything special. *Resolution:* No new conversion targets added for now https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-depth. Neither conversion compiles. ### (Resolved) Overload resolution breaks The following API would become ambiguous: ```c# M(""); static void M1(ReadOnlySpan charArray) => ...; static void M1(byte[] byteArray) => ...; ``` What should we do to address this? *Proposal:* Similar to https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#overload-resolution, Better function member ([§11.6.4.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12643-better-function-member)) is updated to prefer members where none of the conversions involved require converting `string` constants to UTF8 `byte` sequences. > #### Better function member > ... > Given an argument list `A` with a set of argument expressions `{E1, E2, ..., En}` and two applicable function members `Mp` and `Mq` with parameter types `{P1, P2, ..., Pn}` and `{Q1, Q2, ..., Qn}`, `Mp` is defined to be a ***better function member*** than `Mq` if > > 1. **for each argument, the implicit conversion from `Ex` to `Px` is not a _string_constant_to_UTF8_byte_representation_conversion_, and for at least one argument, the implicit conversion from `Ex` to `Qx` is a _string_constant_to_UTF8_byte_representation_conversion_, or** > 2. for each argument, the implicit conversion from `Ex` to `Px` is not a _function_type_conversion_, and > * `Mp` is a non-generic method or `Mp` is a generic method with type parameters `{X1, X2, ..., Xp}` and for each type parameter `Xi` the type argument is inferred from an expression or from a type other than a _function_type_, and > * for at least one argument, the implicit conversion from `Ex` to `Qx` is a _function_type_conversion_, or `Mq` is a generic method with type parameters `{Y1, Y2, ..., Yq}` and for at least one type parameter `Yi` the type argument is inferred from a _function_type_, or > 3. for each argument, the implicit conversion from `Ex` to `Qx` is not better than the implicit conversion from `Ex` to `Px`, and for at least one argument, the conversion from `Ex` to `Px` is better than the conversion from `Ex` to `Qx`. Note that the addition of this rule is not going to cover scenarios with instance methods becoming applicable and "shadowing" extension methods. For example: ``` C# using System; class Program { static void Main() { var p = new Program(); Console.WriteLine(p.M("")); } public string M(byte[] b) => "byte[]"; } static class E { public static string M(this object o, string s) => "string"; } ``` Behavior of this code will silently change from printing "string" to printing "byte[]". Are we Ok with this behavior change? Should it be documented as a breaking change? Note that there is no proposal to make _string_constant_to_UTF8_byte_representation_conversion_ unavailable when C#10 language version is targeted. In that case, the example above becomes an error rather than returns to C#10 behavior. This follows a general principle that target language version doesn't affect semantics of the language. Are we Ok with this behavior? Should it be documented as a breaking change? The new rule also is not going to prevent breaks involving tuple literal conversions. For example, ``` C# class C { static void Main() { System.Console.Write(Test(("s", 1))); } static string Test((object, int) a) => "object"; static string Test((byte[], int) a) => "array"; } ``` is going to silently print "array" instead of "object". Are we Ok with this behavior? Should it be documented as a breaking change? Perhaps we could complicate the new rule to dig into the tuple literal conversions. *Resolution:* The prototype will not adjust any rules here, so we can hopefully see what breaks in practice - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#breaking-changes. ### (Resolved) Should `u8` suffix be case-insensitive? *Proposal:* Support `U8` suffix as well for consistency with numeric suffixes. *Resolution:* Approved - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#suffix-case-sensitivity. ## Examples today Examples of where runtime has manually encoded the UTF8 bytes today - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/Common/src/System/Net/Http/aspnetcore/Http2/Hpack/StatusCodes.cs#L13-L78 - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Memory/src/System/Buffers/Text/Base64Encoder.cs#L581-L591 - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpResponseStream.Windows.cs#L284 - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs#L30 - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs#L852 - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs#L35-L42 Examples where we leave perf on the table - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeChannelBindingHandle.cs#L16-L17 - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs#L37-L43 - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs#L78 - https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs#L669-L687 ## Design meetings https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-18.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-06-06.md ================================================ FILE: proposals/csharp-12.0/collection-expressions.md ================================================ # Collection expressions [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Collection expressions introduce a new terse syntax, `[e1, e2, e3, etc]`, to create common collection values. Inlining other collections into these values is possible using a spread element `..e` like so: `[e1, ..c2, e2, ..c2]`. Several collection-like types can be created without requiring external BCL support. These types are: * [Array types](https://github.com/dotnet/csharplang/blob/main/spec/types.md#array-types), such as `int[]`. * [`Span`](https://learn.microsoft.com/dotnet/api/system.span-1) and [`ReadOnlySpan`](https://learn.microsoft.com/dotnet/api/system.readonlyspan-1). * Types that support [collection initializers](https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#collection-initializers), such as [`List`](https://learn.microsoft.com/dotnet/api/system.collections.generic.list-1). Further support is present for collection-like types not covered under the above through a new attribute and API pattern that can be adopted directly on the type itself. ## Motivation [motivation]: #motivation * Collection-like values are hugely present in programming, algorithms, and especially in the C#/.NET ecosystem. Nearly all programs will utilize these values to store data and send or receive data from other components. Currently, almost all C# programs must use many different and unfortunately verbose approaches to create instances of such values. Some approaches also have performance drawbacks. Here are some common examples: * Arrays, which require either `new Type[]` or `new[]` before the `{ ... }` values. * Spans, which may use `stackalloc` and other cumbersome constructs. * Collection initializers, which require syntax like `new List` (lacking inference of a possibly verbose `T`) prior to their values, and which can cause multiple reallocations of memory because they use N `.Add` invocations without supplying an initial capacity. * Immutable collections, which require syntax like `ImmutableArray.Create(...)` to initialize the values, and which can cause intermediary allocations and data copying. More efficient construction forms (like `ImmutableArray.CreateBuilder`) are unwieldy and still produce unavoidable garbage. * Looking at the surrounding ecosystem, we also find examples everywhere of list creation being more convenient and pleasant to use. TypeScript, Dart, Swift, Elm, Python, and more opt for a succinct syntax for this purpose, with widespread usage, and to great effect. Cursory investigations have revealed no substantive problems arising in those ecosystems with having these literals built in. * C# has also added [list patterns](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/list-patterns.md) in C# 11. This pattern allows matching and deconstruction of list-like values using a clean and intuitive syntax. However, unlike almost all other pattern constructs, this matching/deconstruction syntax lacks the corresponding construction syntax. * Getting the best performance for constructing each collection type can be tricky. Simple solutions often waste both CPU and memory. Having a literal form allows for maximum flexibility from the compiler implementation to optimize the literal to produce at least as good a result as a user could provide, but with simple code. Very often the compiler will be able to do better, and the specification aims to allow the implementation large amounts of leeway in terms of implementation strategy to ensure this. An inclusive solution is needed for C#. It should meet the vast majority of casse for customers in terms of the collection-like types and values they already have. It should also feel natural in the language and mirror the work done in pattern matching. This leads to a natural conclusion that the syntax should be like `[e1, e2, e3, e-etc]` or `[e1, ..c2, e2]`, which correspond to the pattern equivalents of `[p1, p2, p3, p-etc]` and `[p1, ..p2, p3]`. ## Detailed design [design]: #detailed-design The following [grammar](https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#primary-expressions) productions are added: ```diff primary_no_array_creation_expression ... + | collection_expression ; + collection_expression : '[' ']' | '[' collection_element ( ',' collection_element )* ']' ; + collection_element : expression_element | spread_element ; + expression_element : expression ; + spread_element : '..' expression ; ``` The grammar for `collection_element` is known to introduce a syntax ambiguity. Specifically `.. expr` is both exactly the production-body for `spread_element`, and is also reachable through `expression_element -> expression -> ... -> range_expression`. There is a simple overarching rule for `collection_elements`. Specifically, if the element [lexically](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/lexical-structure.md) starts with `..` then it is *always* treated as a `spread_element`. For example, `..x ? y : z;` is always treated as a `spread_element` (so `.. (x ? y : z)`) even though it can be legally parsed as an expression (like `(..x) ? y : z`). This is beneficial in two ways. First, a compiler implementation needs only look at the very first token it sees to determine what to parse next (a `spread_element` or `expression_element`). Second, correspondingly, a user can trivially understand what sort of element they have without having to mentally try to parse what follows to see if they should think of it as a spread or an expression. Collection literals are [target-typed](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.1/target-typed-default.md#motivation). ### Spec clarifications [spec-clarifications]: #spec-clarifications * For brevity, `collection_expression` will be referred to as "literal" in the following sections. * `expression_element` instances will commonly be referred to as `e1`, `e_n`, etc. * `spread_element` instances will commonly be referred to as `..s1`, `..s_n`, etc. * *span type* means either `Span` or `ReadOnlySpan`. * Literals will commonly be shown as `[e1, ..s1, e2, ..s2, etc]` to convey any number of elements in any order. Importantly, this form will be used to represent all cases such as: * Empty literals `[]` * Literals with no `expression_element` in them. * Literals with no `spread_element` in them. * Literals with arbitrary ordering of any element type. * The *iteration type* of `..s_n` is the type of the *iteration variable* determined as if `s_n` were used as the expression being iterated over in a [`foreach_statement`](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement). * Variables starting with `__name` are used to represent the results of the evaluation of `name`, stored in a location so that it is only evaluated once. For example `__e1` is the evaluation of `e1`. * `List`, `IEnumerable`, etc. refer to the respective types in the `System.Collections.Generic` namespace. * The specification defines a [translation](#collection-literal-translation) of the literal to existing C# constructs. Similar to the [*query expression translation*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12203-query-expression-translation), the literal is itself only legal if the translation would result in legal code. The purpose of this rule is to avoid having to repeat other rules of the language that are implied (for example, about convertibility of expressions when assigned to storage locations). * An implementation is not required to translate literals exactly as specified below. Any translation is legal if the same result is produced and there are no observable differences in the production of the result. * For example, an implementation could translate literals like `[1, 2, 3]` directly to a `new int[] { 1, 2, 3 }` expression that itself bakes the raw data into the assembly, eliding the need for `__index` or a sequence of instructions to assign each value. Importantly, this does mean if any step of the translation might cause an exception at runtime that the program state is still left in the state indicated by the translation. * References to 'stack allocation' refer to any strategy to allocate on the stack and not the heap. Importantly, it does not imply or require that that strategy be through the actual `stackalloc` mechanism. For example, the use of [inline arrays](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/inline-arrays.md) is also an allowed and desirable approach to accomplish stack allocation where available. Note that in C# 12, inline arrays can't be initialized with a collection expression. That remains an open proposal. * Collections are assumed to be well-behaved. For example: * It is assumed that the value of `Count` on a collection will produce that same value as the number of elements when enumerated. * The types used in this spec defined in the `System.Collections.Generic` namespace are presumed to be side-effect free. As such, the compiler can optimize scenarios where such types might be used as intermediary values, but otherwise not be exposed. * It is assumed that a call to some applicable `.AddRange(x)` member on a collection will result in the same final value as iterating over `x` and adding all of its enumerated values individually to the collection with `.Add`. * The behavior of collection literals with collections that are not well-behaved is undefined. ## Conversions [conversions]: #conversions A *collection expression conversion* allows a collection expression to be converted to a type. An implicit *collection expression conversion* exists from a collection expression to the following types: * A single dimensional *array type* `T[]`, in which case the *element type* is `T` * A *span type*: * `System.Span` * `System.ReadOnlySpan` In which case the *element type* is `T` * A *type* with an appropriate *[create method](#create-methods)*, in which case the *element type* is the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) determined from a `GetEnumerator` instance method or enumerable interface, not from an extension method * A *struct* or *class type* that implements `System.Collections.IEnumerable` where: * The *type* has an *[applicable](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11642-applicable-function-member)* constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression. * If the collection expression has any elements, the *type* has an instance or extension method `Add` where: * The method can be invoked with a single value argument. * If the method is generic, the type arguments can be inferred from the collection and argument. * The method is accessible at the location of the collection expression. In which case the *element type* is the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement) of the *type*. * An *interface type*: * `System.Collections.Generic.IEnumerable` * `System.Collections.Generic.IReadOnlyCollection` * `System.Collections.Generic.IReadOnlyList` * `System.Collections.Generic.ICollection` * `System.Collections.Generic.IList` In which case the *element type* is `T` The implicit conversion exists if the type has an *element type* `T` where for each *element* `Eᵢ` in the collection expression: * If `Eᵢ` is an *expression element*, there is an implicit conversion from `Eᵢ` to `T`. * If `Eᵢ` is a *spread element* `..Sᵢ`, there is an implicit conversion from the *iteration type* of `Sᵢ` to `T`. There is no *collection expression conversion* from a collection expression to a multi dimensional *array type*. Types for which there is an implicit collection expression conversion from a collection expression are the valid *target types* for that collection expression. The following additional implicit conversions exist from a *collection expression*: * To a *nullable value type* `T?` where there is a *collection expression conversion* from the collection expression to a value type `T`. The conversion is a *collection expression conversion* to `T` followed by an *implicit nullable conversion* from `T` to `T?`. * To a reference type `T` where there is a *[create method](#create-methods)* associated with `T` that returns a type `U` and an *implicit reference conversion* from `U` to `T`. The conversion is a *collection expression conversion* to `U` followed by an *implicit reference conversion* from `U` to `T`. * To an interface type `I` where there is a *[create method](#create-methods)* associated with `I` that returns a type `V` and an *implicit boxing conversion* from `V` to `I`. The conversion is a *collection expression conversion* to `V` followed by an *implicit boxing conversion* from `V` to `I`. ## Create methods [create-methods]: #create-methods A *create method* is indicated with a `[CollectionBuilder(...)]` attribute on the *collection type*. The attribute specifies the *builder type* and *method name* of a method to be invoked to construct an instance of the collection type. ```c# namespace System.Runtime.CompilerServices { [AttributeUsage( AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] public sealed class CollectionBuilderAttribute : System.Attribute { public CollectionBuilderAttribute(Type builderType, string methodName); public Type BuilderType { get; } public string MethodName { get; } } } ``` The attribute can be applied to a `class`, `struct`, `ref struct`, or `interface`. The attribute is not inherited although the attribute can be applied to a base `class` or an `abstract class`. The *builder type* must be a non-generic `class` or `struct`. First, the set of applicable *create methods* `CM` is determined. It consists of methods that meet the following requirements: * The method must have the name specified in the `[CollectionBuilder(...)]` attribute. * The method must be defined on the *builder type* directly. * The method must be `static`. * The method must be accessible where the collection expression is used. * The *arity* of the method must match the *arity* of the collection type. * The method must have a single parameter of type `System.ReadOnlySpan`, passed by value. * There is an [*identity conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1022-identity-conversion), [*implicit reference conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1028-implicit-reference-conversions), or [*boxing conversion*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/conversions.md#1029-boxing-conversions) from the method return type to the *collection type*. Methods declared on base types or interfaces are ignored and not part of the `CM` set. If the `CM` set is empty, then the *collection type* doesn't have an *element type* and doesn't have a *create method*. None of the following steps apply. If only one method among those in the `CM` set has an [*identity conversion*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1022-identity-conversion) from `E` to the *element type* of the *collection type*, that is the *create method* for the *collection type*. Otherwise, the *collection type* doesn't have a *create method*. An error is reported if the `[CollectionBuilder]` attribute does not refer to an invokable method with the expected signature. For a *collection expression* with a target type C<S0, S1, …> where the *type declaration* C<T0, T1, …> has an associated *builder method* B.M<U0, U1, …>(), the *generic type arguments* from the target type are applied in order — and from outermost containing type to innermost — to the *builder method*. The span parameter for the *create method* can be explicitly marked `scoped` or `[UnscopedRef]`. If the parameter is implicitly or explicitly `scoped`, the compiler *may* allocate the storage for the span on the stack rather than the heap. For example, a possible *create method* for `ImmutableArray`: ```csharp [CollectionBuilder(typeof(ImmutableArray), "Create")] public struct ImmutableArray { ... } public static class ImmutableArray { public static ImmutableArray Create(ReadOnlySpan items) { ... } } ``` With the *create method* above, `ImmutableArray ia = [1, 2, 3];` could be emitted as: ```csharp [InlineArray(3)] struct __InlineArray3 { private T _element0; } Span __tmp = new __InlineArray3(); __tmp[0] = 1; __tmp[1] = 2; __tmp[2] = 3; ImmutableArray ia = ImmutableArray.Create((ReadOnlySpan)__tmp); ``` ## Construction [construction]: #construction The elements of a collection expression are *evaluated* in order, left to right. Each element is evaluated exactly once, and any further references to the elements refer to the results of this initial evaluation. A spread element may be *iterated* before or after the subsequent elements in the collection expression are *evaluated*. An unhandled exception thrown from any of the methods used during construction will be uncaught and will prevent further steps in the construction. `Length`, `Count`, and `GetEnumerator` are assumed to have no side effects. --- If the target type is a *struct* or *class type* that implements `System.Collections.IEnumerable`, and the target type does not have a *[create method](#create-methods)*, the construction of the collection instance is as follows: * The elements are evaluated in order. Some or all elements may be evaluated *during* the steps below rather than before. * The compiler *may* determine the *known length* of the collection expression by invoking [*countable*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#adding-index-and-range-support-to-existing-library-types) properties — or equivalent properties from well-known interfaces or types — on each *spread element expression*. * The constructor that is applicable with no arguments is invoked. * For each element in order: * If the element is an *expression element*, the applicable `Add` instance or extension method is invoked with the element *expression* as the argument. (Unlike classic [*collection initializer behavior*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#117154-collection-initializers), element evaluation and `Add` calls are not necessarily interleaved.) * If the element is a *spread element* then one of the following is used: * An applicable `GetEnumerator` instance or extension method is invoked on the *spread element expression* and for each item from the enumerator the applicable `Add` instance or extension method is invoked on the *collection instance* with the item as the argument. If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions. * An applicable `AddRange` instance or extension method is invoked on the *collection instance* with the spread element *expression* as the argument. * An applicable `CopyTo` instance or extension method is invoked on the *spread element expression* with the collection instance and `int` index as arguments. * During the construction steps above, an applicable `EnsureCapacity` instance or extension method *may* be invoked one or more times on the *collection instance* with an `int` capacity argument. --- If the target type is an *array*, a *span*, a type with a *[create method](#create-methods)*, or an *interface*, the construction of the collection instance is as follows: * The elements are evaluated in order. Some or all elements may be evaluated *during* the steps below rather than before. * The compiler *may* determine the *known length* of the collection expression by invoking [*countable*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#adding-index-and-range-support-to-existing-library-types) properties — or equivalent properties from well-known interfaces or types — on each *spread element expression*. * An *initialization instance* is created as follows: * If the target type is an *array* and the collection expression has a *known length*, an array is allocated with the expected length. * If the target type is a *span* or a type with a *create method*, and the collection has a *known length*, a span with the expected length is created referring to contiguous storage. * Otherwise intermediate storage is allocated. * For each element in order: * If the element is an *expression element*, the initialization instance *indexer* is invoked to add the evaluated expression at the current index. * If the element is a *spread element* then one of the following is used: * A member of a well-known interface or type is invoked to copy items from the spread element expression to the initialization instance. * An applicable `GetEnumerator` instance or extension method is invoked on the *spread element expression* and for each item from the enumerator, the initialization instance *indexer* is invoked to add the item at the current index. If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions. * An applicable `CopyTo` instance or extension method is invoked on the *spread element expression* with the initialization instance and `int` index as arguments. * If intermediate storage was allocated for the collection, a collection instance is allocated with the actual collection length and the values from the initialization instance are copied to the collection instance, or if a span is required the compiler *may* use a span of the actual collection length from the intermediate storage. Otherwise the initialization instance is the collection instance. * If the target type has a *create method*, the create method is invoked with the span instance. --- > *Note:* > The compiler may *delay* adding elements to the collection — or *delay* iterating through spread elements — until after evaluating subsequent elements. (When subsequent spread elements have *countable* properties that would allow calculating the expected length of the collection before allocating the collection.) Conversely, the compiler may *eagerly* add elements to the collection — and *eagerly* iterate through spread elements — when there is no advantage to delaying. > > Consider the following collection expression: > ```c# > int[] x = [a, ..b, ..c, d]; > ``` > > If spread elements `b` and `c` are *countable*, the compiler could delay adding items from `a` and `b` until after `c` is evaluated, to allow allocating the resulting array at the expected length. After that, the compiler could eagerly add items from `c`, before evaluating `d`. > ```c# > var __tmp1 = a; > var __tmp2 = b; > var __tmp3 = c; > var __result = new int[2 + __tmp2.Length + __tmp3.Length]; > int __index = 0; > __result[__index++] = __tmp1; > foreach (var __i in __tmp2) __result[__index++] = __i; > foreach (var __i in __tmp3) __result[__index++] = __i; > __result[__index++] = d; > x = __result; > ``` ## Empty collection literal * The empty literal `[]` has no type. However, similar to the [*null-literal*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/lexical-structure.md#6457-the-null-literal), this literal can be implicitly converted to any [*constructible*](#conversions) collection type. For example, the following is not legal as there is no *target type* and there are no other conversions involved: ```c# var v = []; // illegal ``` * Spreading an empty literal is permitted to be elided. For example: ```c# bool b = ... List l = [x, y, .. b ? [1, 2, 3] : []]; ``` Here, if `b` is false, it is not required that any value actually be constructed for the empty collection expression since it would immediately be spread into zero values in the final literal. * The empty collection expression is permitted to be a singleton if used to construct a final collection value that is known to not be mutable. For example: ```c# // Can be a singleton, like Array.Empty() int[] x = []; // Can be a singleton. Allowed to use Array.Empty(), Enumerable.Empty(), // or any other implementation that can not be mutated. IEnumerable y = []; // Must not be a singleton. Value must be allowed to mutate, and should not mutate // other references elsewhere. List z = []; ``` ## Ref safety [ref-safety]: #ref-safety See [*safe context constraint*](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/structs.md#164121-general) for definitions of the *safe-context* values: *declaration-block*, *function-member*, and *caller-context*. The *safe-context* of a collection expression is: * The safe-context of an empty collection expression `[]` is the *caller-context*. * If the target type is a *span type* `System.ReadOnlySpan`, and `T` is one of the *primitive types* `bool`, `sbyte`, `byte`, `short`, `ushort`, `char`, `int`, `uint`, `long`, `ulong`, `float`, or `double`, and the collection expression contains *constant values only*, the safe-context of the collection expression is the *caller-context*. * If the target type is a *span type* `System.Span` or `System.ReadOnlySpan`, the safe-context of the collection expression is the *declaration-block*. * If the target type is a *ref struct type* with a [*create method*](#create-methods), the safe-context of the collection expression is the [*safe-context of an invocation*](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/structs.md#164126-method-and-property-invocation) of the create method where the collection expression is the span argument to the method. * Otherwise the safe-context of the collection expression is the *caller-context*. A collection expression with a safe-context of *declaration-block* cannot escape the enclosing scope, and the compiler *may* store the collection on the stack rather than the heap. To allow a collection expression for a ref struct type to escape the *declaration-block*, it may be necessary to cast the expression to another type. ```csharp static ReadOnlySpan AsSpanConstants() { return [1, 2, 3]; // ok: span refers to assembly data section } static ReadOnlySpan AsSpan2(T x, T y) { return [x, y]; // error: span may refer to stack data } static ReadOnlySpan AsSpan3(T x, T y, T z) { return (T[])[x, y, z]; // ok: span refers to T[] on heap } ``` ## Type inference [type-inference]: #type-inference ```c# var a = AsArray([1, 2, 3]); // AsArray(int[]) var b = AsListOfArray([[4, 5], []]); // AsListOfArray(List) static T[] AsArray(T[] arg) => arg; static List AsListOfArray(List arg) => arg; ``` The [*type inference*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1263-type-inference) rules are updated as follows. The existing rules for the [*first phase*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12632-the-first-phase) are extracted to a new *input type inference* section, and a rule is added to *input type inference* and *output type inference* for collection expression expressions. > 11.6.3.2 The first phase > > For each of the method arguments `Eᵢ`: > > * An *input type inference* is made *from* `Eᵢ` *to* the corresponding *parameter type* `Tᵢ`. > > An *input type inference* is made *from* an expression `E` *to* a type `T` in the following way: > > * If `E` is a *collection expression* with elements `Eᵢ`, and `T` is a type with an *element type* `Tₑ` or `T` is a *nullable value type* `T0?` and `T0` has an *element type* `Tₑ`, then for each `Eᵢ`: > * If `Eᵢ` is an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. > * If `Eᵢ` is a *spread element* with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement) `Sᵢ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#126310-lower-bound-inferences) is made *from* `Sᵢ` *to* `Tₑ`. > * *[existing rules from first phase]* ... > 11.6.3.7 Output type inferences > > An *output type inference* is made *from* an expression `E` *to* a type `T` in the following way: > > * If `E` is a *collection expression* with elements `Eᵢ`, and `T` is a type with an *element type* `Tₑ` or `T` is a *nullable value type* `T0?` and `T0` has an *element type* `Tₑ`, then for each `Eᵢ`: > * If `Eᵢ` is an *expression element*, then an *output type inference* is made *from* `Eᵢ` *to* `Tₑ`. > * If `Eᵢ` is a *spread element*, no inference is made from `Eᵢ`. > * *[existing rules from output type inferences]* ... ## Extension methods No changes to [*extension method invocation*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#128103-extension-method-invocations) rules. > 12.8.10.3 Extension method invocations > > An extension method `Cᵢ.Mₑ` is *eligible* if: > > * ... > * An implicit identity, reference, or boxing conversion exists from *expr* to the type of the first parameter of `Mₑ`. A collection expression does not have a natural type so the existing conversions from *type* are not applicable. As a result, a collection expression cannot be used directly as the first parameter for an extension method invocation. ```c# static class Extensions { public static ImmutableArray AsImmutableArray(this ImmutableArray arg) => arg; } var x = [1].AsImmutableArray(); // error: collection expression has no target type var y = [2].AsImmutableArray(); // error: ... var z = Extensions.AsImmutableArray([3]); // ok ``` ## Overload resolution [overload-resolution]: #overload-resolution [*Better conversion from expression*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12645-better-conversion-from-expression) is updated to prefer certain target types in collection expression conversions. In the updated rules: * A *span_type* is one of: * `System.Span` * `System.ReadOnlySpan`. * An *array_or_array_interface* is one of: * an *array type* * one of the following *interface types* implemented by an *array type*: * `System.Collections.Generic.IEnumerable` * `System.Collections.Generic.IReadOnlyCollection` * `System.Collections.Generic.IReadOnlyList` * `System.Collections.Generic.ICollection` * `System.Collections.Generic.IList` > Given an implicit conversion `C₁` that converts from an expression `E` to a type `T₁`, and an implicit conversion `C₂` that converts from an expression `E` to a type `T₂`, `C₁` is a ***better conversion*** than `C₂` if one of the following holds: > > * **`E` is a *collection expression* and one of the following holds:** > * **`T₁` is `System.ReadOnlySpan`, and `T₂` is `System.Span`, and an implicit conversion exists from `E₁` to `E₂`** > * **`T₁` is `System.ReadOnlySpan` or `System.Span`, and `T₂` is an *array_or_array_interface* with *element type* `E₂`, and an implicit conversion exists from `E₁` to `E₂`** > * **`T₁` is not a *span_type*, and `T₂` is not a *span_type*, and an implicit conversion exists from `T₁` to `T₂`** > * **`E` is not a *collection expression* and one of the following holds:** > * `E` exactly matches `T₁` and `E` does not exactly match `T₂` > * `E` exactly matches both or neither of `T₁` and `T₂`, and `T₁` is a [*better conversion target*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11646-better-conversion-target) than `T₂` > * `E` is a method group, ... Examples of differences with overload resolution between array initializers and collection expressions: ```c# static void Generic(Span value) { } static void Generic(T[] value) { } static void SpanDerived(Span value) { } static void SpanDerived(object[] value) { } static void ArrayDerived(Span value) { } static void ArrayDerived(string[] value) { } // Array initializers Generic(new[] { "" }); // string[] SpanDerived(new[] { "" }); // ambiguous ArrayDerived(new[] { "" }); // string[] // Collection expressions Generic([""]); // Span SpanDerived([""]); // Span ArrayDerived([""]); // ambiguous ``` ## Span types [span-types]: #span-types The span types `ReadOnlySpan` and `Span` are both [*constructible collection types*](#conversions). Support for them follows the design for [`params Span`](https://github.com/dotnet/csharplang/blob/main/proposals/rejected/params-span.md). Specifically, constructing either of those spans will result in an array T[] created on the [stack](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/unsafe-code.md#229-stack-allocation) if the params array is within limits (if any) set by the compiler. Otherwise the array will be allocated on the heap. If the compiler chooses to allocate on the stack, it is not required to translate a literal directly to a `stackalloc` at that specific point. For example, given: ```c# foreach (var x in y) { Span span = [a, b, c]; // do things with span } ``` The compiler is allowed to translate that using `stackalloc` as long as the `Span` meaning stays the same and [*span-safety*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md) is maintained. For example, it can translate the above to: ```c# Span __buffer = stackalloc int[3]; foreach (var x in y) { __buffer[0] = a __buffer[1] = b __buffer[2] = c; Span span = __buffer; // do things with span } ``` The compiler can also use [inline arrays](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/inline-arrays.md), if available, when choosing to allocate on the stack. Note that in C# 12, inline arrays can't be initialized with a collection expression. That feature is an open proposal. If the compiler decides to allocate on the heap, the translation for `Span` is simply: ```c# T[] __array = [...]; // using existing rules Span __result = __array; ``` ## Collection literal translation [collection-literal-translation]: #collection-literal-translation A collection expression has a *known length* if the compile-time type of each *spread element* in the collection expression is [*countable*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#adding-index-and-range-support-to-existing-library-types). ### Interface translation [interface-translation]: #interface-translation #### Non-mutable interface translation [non-mutable-interface-translation]: #non-mutable-interface-translation Given a target type which does not contain mutating members, namely `IEnumerable`, `IReadOnlyCollection`, and `IReadOnlyList`, a compliant implementation is required to produce a value that implements that interface. If a type is synthesized, it is recommended the synthesized type implements all these interfaces, as well as `ICollection` and `IList`, regardless of which interface type was targeted. This ensures maximal compatibility with existing libraries, including those that introspect the interfaces implemented by a value in order to light up performance optimizations. In addition, the value must implement the nongeneric `ICollection` and `IList` interfaces. This enables collection expressions to support dynamic introspection in scenarios such as data binding. A compliant implementation is free to: 1. Use an existing type that implements the required interfaces. 1. Synthesize a type that implements the required interfaces. In either case, the type used is allowed to implement a larger set of interfaces than those strictly required. Synthesized types are free to employ any strategy they want to implement the required interfaces properly. For example, a synthesized type might inline the elements directly within itself, avoiding the need for additional internal collection allocations. A synthesized type could also not use any storage whatsoever, opting to compute the values directly. For example, returning `index + 1` for `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]`. 1. The value must return `true` when queried for `ICollection.IsReadOnly` (if implemented) and nongeneric `IList.IsReadOnly` and `IList.IsFixedSize`. This ensures consumers can appropriately tell that the collection is non-mutable, despite implementing the mutable views. 1. The value must throw on any call to a mutation method (like `IList.Add`). This ensures safety, preventing a non-mutable collection from being accidentally mutated. #### Mutable interface translation [mutable-interface-translation]: #mutable-interface-translation Given target type that contains mutating members, namely `ICollection` or `IList`: 1. The value must be an instance of `List`. ### Known length translation [known-length-translation]: #known-length-translation Having a *known length* allows for efficient construction of a result with the potential for no copying of data and no unnecessary slack space in a result. Not having a *known length* does not prevent any result from being created. However, it may result in extra CPU and memory costs producing the data, then moving to the final destination. * For a *known length* literal `[e1, ..s1, etc]`, the translation first starts with the following: ```c# int __len = count_of_expression_elements + __s1.Count; ... __s_n.Count; ``` * Given a target type `T` for that literal: * If `T` is some `T1[]`, then the literal is translated as: ```c# T1[] __result = new T1[__len]; int __index = 0; __result[__index++] = __e1; foreach (T1 __t in __s1) __result[__index++] = __t; // further assignments of the remaining elements ``` The implementation is allowed to utilize other means to populate the array. For example, utilizing efficient bulk-copy methods like `.CopyTo()`. * If `T` is some `Span`, then the literal is translated as the same as above, except that the `__result` initialization is translated as: ```c# Span __result = new T1[__len]; // same assignments as the array translation ``` The translation may use `stackalloc T1[]` or an [*inline array*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/inline-arrays.md) rather than `new T1[]` if [*span-safety*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md) is maintained. * If `T` is some `ReadOnlySpan`, then the literal is translated the same as for the `Span` case except that the final result will be that `Span` [implicitly converted](https://learn.microsoft.com/dotnet/api/system.span-1.op_implicit#system-span-1-op-implicit(system-span((-0)))-system-readonlyspan((-0))) to a `ReadOnlySpan`. A `ReadOnlySpan` where `T1` is some primitive type, and all collection elements are constant does not need its data to be on the heap, or on the stack. For example, an implementation could construct this span directly as a reference to portion of the data segment of the program. The above forms (for arrays and spans) are the base representations of the collection expression and are used for the following translation rules: * If `T` is some `C` which has a corresponding [create-method](#create-methods) `B.M()`, then the literal is translated as: ```c# // Collection literal is passed as is as the single B.M<...>(...) argument C __result = B.M([...]) ``` As the *create method* must have an argument type of some instantiated `ReadOnlySpan`, the translation rule for spans applies when passing the collection expression to the create method. * If `T` supports [collection initializers](https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#collection-initializers), then: * if the type `T` contains an accessible constructor with a single parameter `int capacity`, then the literal is translated as: ```c# T __result = new T(capacity: __len); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elements ``` Note: the name of the parameter is required to be `capacity`. This form allows for a literal to inform the newly constructed type of the count of elements to allow for efficient allocation of internal storage. This avoids wasteful reallocations as the elements are added. * otherwise, the literal is translated as: ```c# T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elements ``` This allows creating the target type, albeit with no capacity optimization to prevent internal reallocation of storage. ### Unknown length translation [unknown-length-translation]: #unknown-length-translation * Given a target type `T` for an *unknown length* literal: * If `T` supports [collection initializers](https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#collection-initializers), then the literal is translated as: ```c# T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elements ``` This allows spreading of any iterable type, albeit with the least amount of optimization possible. * If `T` is some `T1[]`, then the literal has the same semantics as: ```c# List __list = [...]; /* initialized using predefined rules */ T1[] __result = __list.ToArray(); ``` The above is inefficient though; it creates the intermediary list, and then creates a copy of the final array from it. Implementations are free to optimize this away, for example producing code like so: ```c# T1[] __result = .CreateArray( count_of_expression_elements); int __index = 0; .Add(ref __result, __index++, __e1); foreach (var __t in __s1) .Add(ref __result, __index++, __t); // further additions of the remaining elements .Resize(ref __result, __index); ``` This allows for minimal waste and copying, without additional overhead that library collections might incur. The counts passed to `CreateArray` are used to provide a starting size hint to prevent wasteful resizes. * If `T` is some *span type*, an implementation may follow the above `T[]` strategy, or any other strategy with the same semantics, but better performance. For example, instead of allocating the array as a copy of the list elements, `CollectionsMarshal.AsSpan(__list)` could be used to obtain a span value directly. ## Unsupported scenarios [unsupported-scenarios]: #unsupported-scenarios While collection literals can be used for many scenarios, there are a few that they are not capable of replacing. These include: * Multi-dimensional arrays (e.g. `new int[5, 10] { ... }`). There is no facility to include the dimensions, and all collection literals are either linear or map structures only. * Collections which pass special values to their constructors. There is no facility to access the constructor being used. * Nested collection initializers, e.g. `new Widget { Children = { w1, w2, w3 } }`. This form needs to stay since it has very different semantics from `Children = [w1, w2, w3]`. The former calls `.Add` repeatedly on `.Children` while the latter would assign a new collection over `.Children`. We could consider having the latter form fall back to adding to an existing collection if `.Children` can't be assigned, but that seems like it could be extremely confusing. ## Syntax ambiguities [syntax-ambiguities]: #syntax-ambiguities * There are two "true" syntactic ambiguities where there are multiple legal syntactic interpretations of code that uses a `collection_literal_expression`. * The `spread_element` is ambiguous with a [`range_expression`](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#systemrange). One could technically have: ```c# Range[] ranges = [range1, ..e, range2]; ``` To resolve this, we can either: * Require users to parenthesize `(..e)` or include a start index `0..e` if they want a range. * Choose a different syntax (like `...`) for spread. This would be unfortunate for the lack of consistency with slice patterns. * There are two cases where there isn't a true ambiguity but where the syntax greatly increases parsing complexity. While not a problem given engineering time, this does still increase cognitive overhead for users when looking at code. * Ambiguity between `collection_literal_expression` and `attributes` on statements or local functions. Consider: ```c# [X(), Y, Z()] ``` This could be one of: ```c# // A list literal inside some expression statement [X(), Y, Z()].ForEach(() => ...); // The attributes for a statement or local function [X(), Y, Z()] void LocalFunc() { } ``` Without complex lookahead, it would be impossible to tell without consuming the entirety of the literal. Options to address this include: * Allow this, doing the parsing work to determine which of these cases this is. * Disallow this, and require the user wrap the literal in parentheses like `([X(), Y, Z()]).ForEach(...)`. * Ambiguity between a `collection_literal_expression` in a `conditional_expression` and a `null_conditional_operations`. Consider: ```c# M(x ? [a, b, c] ``` This could be one of: ```c# // A ternary conditional picking between two collections M(x ? [a, b, c] : [d, e, f]); // A null conditional safely indexing into 'x': M(x ? [a, b, c]); ``` Without complex lookahead, it would be impossible to tell without consuming the entirety of the literal. Note: this is a problem even without a *natural type* because target typing applies through `conditional_expressions`. As with the others, we could require parentheses to disambiguate. In other words, presume the `null_conditional_operation` interpretation unless written like so: `x ? ([1, 2, 3]) :`. However, that seems rather unfortunate. This sort of code does not seem unreasonable to write and will likely trip people up. ## Drawbacks [drawbacks]: #drawbacks * This introduces [yet another form](https://xkcd.com/927/) for collection expressions on top of the myriad ways we already have. This is extra complexity for the language. That said, this also makes it possible to unify on one ~~ring~~ syntax to rule them all, which means existing codebases can be simplified and moved to a uniform look everywhere. * Using `[`...`]` instead of `{`...`}` moves away from the syntax we've generally used for arrays and collection initializers already. Specifically that it uses `[`...`]` instead of `{`...`}`. However, this was already settled on by the language team when we did list patterns. We attempted to make `{`...`}` work with list patterns and ran into insurmountable issues. Because of this, we moved to `[`...`]` which, while new for C#, feels natural in many programming languages and allowed us to start fresh with no ambiguity. Using `[`...`]` as the corresponding literal form is complementary with our latest decisions, and gives us a clean place to work without problem. This does introduce warts into the language. For example, the following are both legal and (fortunately) mean the exact same thing: ```c# int[] x = { 1, 2, 3 }; int[] x = [ 1, 2, 3 ]; ``` However, given the breadth and consistency brought by the new literal syntax, we should consider recommending that people move to the new form. IDE suggestions and fixes could help in that regard. ## Alternatives [alternatives]: #alternatives * What other designs have been considered? What is the impact of not doing this? ## Resolved questions [resolved]: #resolved-questions * Should the compiler use `stackalloc` for stack allocation when *inline arrays* are not available and the *iteration type* is a primitive type? Resolution: No. Managing a `stackalloc` buffer requires additional effort over an *inline array* to ensure the buffer is not allocated repeatedly when the collection expression is within a loop. The additional complexity in the compiler and in the generated code outweighs the benefit of stack allocation on older platforms. * In what order should we evaluate literal elements compared with Length/Count property evaluation? Should we evaluate all elements first, then all lengths? Or should we evaluate an element, then its length, then the next element, and so on? Resolution: We evaluate all elements first, then everything else follows that. * Can an *unknown length* literal create a collection type that needs a *known length*, like an array, span, or Construct(array/span) collection? This would be harder to do efficiently, but it might be possible through clever use of pooled arrays and/or builders. Resolution: Yes, we allow creating a fixes-length collection from an *unknown length* literal. The compiler is permitted to implement this in as efficient a manner as possible. The following text exists to record the original discussion of this topic.
Users could always make an *unknown length* literal into a *known length* one with code like this: ```c# ImmutableArray x = [a, ..unknownLength.ToArray(), b]; ``` However, this is unfortunate due to the need to force allocations of temporary storage. We could potentially be more efficient if we controlled how this was emitted.
* Can a `collection_expression` be target-typed to an `IEnumerable` or other collection interfaces? For example: ```c# void DoWork(IEnumerable values) { ... } // Needs to produce `longs` not `ints` for this to work. DoWork([1, 2, 3]); ``` Resolution: Yes, a literal can be target-typed to any interface type `I` that `List` implements. For example, `IEnumerable`. This is the same as target-typing to `List` and then assigning that result to the specified interface type. The following text exists to record the original discussion of this topic.
The open question here is determining what underlying type to actually create. One option is to look at the proposal for [`params IEnumerable`](https://github.com/dotnet/csharplang/issues/179). There, we would generate an array to pass the values along, similar to what happens with `params T[]`.
* Can/should the compiler emit `Array.Empty()` for `[]`? Should we mandate that it does this, to avoid allocations whenever possible? Yes. The compiler should emit `Array.Empty()` for any case where this is legal and the final result is non-mutable. For example, targeting `T[]`, `IEnumerable`, `IReadOnlyCollection` or `IReadOnlyList`. It should not use `Array.Empty` when the target is mutable (`ICollection` or `IList`). * Should we expand on collection initializers to look for the very common `AddRange` method? It could be used by the underlying constructed type to perform adding of spread elements potentially more efficiently. We might also want to look for things like `.CopyTo` as well. There may be drawbacks here as those methods might end up causing excess allocations/dispatches versus directly enumerating in the translated code. Yes. An implementation is allowed to utilize other methods to initialize a collection value, under the presumption that these methods have well-defined semantics, and that collection types should be "well behaved". In practice though, an implementation should be cautious as benefits in one way (bulk copying) may come with negative consequences as well (for example, boxing a struct collection). An implementation should take advantage in the cases where there are no downsides. For example, with an `.AddRange(ReadOnlySpan)` method. ## Unresolved questions [unresolved]: #unresolved-questions * Should we allow inferring the *element type* when the *iteration type* is "ambiguous" (by some definition)? For example: ```csharp Collection x = [1L, 2L]; // error CS1640: foreach statement cannot operate on variables of type 'Collection' because it implements multiple instantiations of 'IEnumerable'; try casting to a specific interface instantiation foreach (var x in new Collection) { } static class Builder { public Collection Create(ReadOnlySpan items) => throw null; } [CollectionBuilder(...)] class Collection : IEnumerable, IEnumerable { IEnumerator IEnumerable.GetEnumerator() => throw null; IEnumerator IEnumerable.GetEnumerator() => throw null; IEnumerator IEnumerable.GetEnumerator() => throw null; } ``` * Should it be legal to create and immediately index into a collection literal? Note: this requires an answer to the unresolved question below of whether collection literals have a *natural type*. * Stack allocations for huge collections might blow the stack. Should the compiler have a heuristic for placing this data on the heap? Should the language be unspecified to allow for this flexibility? We should follow the spec for [`params Span`](https://github.com/dotnet/csharplang/issues/1757). * Do we need to target-type `spread_element`? Consider, for example: ```c# Span span = [a, ..b ? [c] : [d, e], f]; ``` Note: this may commonly come up in the following form to allow conditional inclusion of some set of elements, or nothing if the condition is false: ```c# Span span = [a, ..b ? [c, d, e] : [], f]; ``` In order to evaluate this full literal, we need to evaluate the element expressions within. That means being able to evaluate `b ? [c] : [d, e]`. However, absent a target type to evaluate this expression in the context of, and absent any sort of *natural type*, this would we would be unable to determine what to do with either `[c]` or `[d, e]` here. To resolve this, we could say that when evaluating a literal's `spread_element` expression, there was an implicit target type equivalent to the target type of the literal itself. So, in the above, that would be rewritten as: ```c# int __e1 = a; Span __s1 = b ? [c] : [d, e]; int __e2 = f; Span __result = stackalloc int[2 + __s1.Length]; int __index = 0; __result[__index++] = a; foreach (int __t in __s1) __result[index++] = __t; __result[__index++] = f; Span span = __result; ``` ### Specification of a [*constructible*](#conversions) collection type utilizing a [*create method*](#create-methods) is sensitive to the context at which conversion is classified An existence of the conversion in this case depends on the notion of an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement) of the *collection type*. If there is a *create method* that takes a `ReadOnlySpan` where `T` is the *iteration type*, the conversion exists. Otherwise, it doesn't. However, an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement) is sensitive to the context at which `foreach` is performed. For the same *collection type* it can be different based on what extension methods are in scope, and it can also be undefined. That feels fine for the purpose of `foreach` when the type isn't designed to be foreach-able on itself. If it is, extension methods cannot change how the type is foreach-ed over, no matter what the context is. However, that feels somewhat strange for a conversion to be context sensitive like that. Effectively the conversion is "unstable". A *collection type* explicitly designed to be *constructible* is allowed to leave out a definition of a very important detail - its *iteration type*. Leaving the type "unconvertible" on itself. Here is an example: ``` C# using System; using System.Collections.Generic; using System.Runtime.CompilerServices; [CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] class MyCollection { } class MyCollectionBuilder { public static MyCollection Create(ReadOnlySpan items) => throw null; public static MyCollection Create(ReadOnlySpan items) => throw null; } namespace Ns1 { static class Ext { public static IEnumerator GetEnumerator(this MyCollection x) => throw null; } class Program { static void Main() { foreach (var l in new MyCollection()) { long s = l; } MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long' 2]; } } } namespace Ns2 { static class Ext { public static IEnumerator GetEnumerator(this MyCollection x) => throw null; } class Program { static void Main() { foreach (var l in new MyCollection()) { string s = l; } MyCollection x1 = ["a", 2]; // error CS0029: Cannot implicitly convert type 'int' to 'string' } } } namespace Ns3 { class Program { static void Main() { // error CS1579: foreach statement cannot operate on variables of type 'MyCollection' because 'MyCollection' does not contain a public instance or extension definition for 'GetEnumerator' foreach (var l in new MyCollection()) { } MyCollection x1 = ["a", 2]; // error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type. } } } ``` Given the current design, if the type doesn't define *iteration type* itself, compiler is unable to reliably validate an application of a `CollectionBuilder` attribute. If we don't know the *iteration type*, we don't know what the signature of the *create method* should be. If the *iteration type* comes from context, there is no guarantee that the type is always going to be used in a similar context. [Params Collections](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/params-collections.md#method-parameters) feature is also affected by this. It feels strange to be unable to reliably predict element type of a `params` parameter at the declaration point. The current proposal also requires to ensure that the *create method* is at least as accessible as the `params` *collection type*. It is impossible to perform this check in a reliable fashion, unless the *collection type* defines its *iteration type* itself. Note, that we also have https://github.com/dotnet/roslyn/issues/69676 opened for compiler, which basically observes the same issue, but talks about it from the perspective of optimization. #### Proposal Require a type utilizing `CollectionBuilder` attribute to define its *iteration type* on itself. In other words this means, that the type should either implement `IEnumarable`/`IEnumerable`, or it should have public `GetEnumerator` method with the right signature (this excludes any extension methods). Also, right now [*create method*](#create-methods) is required to "be accessible where the collection expression is used". This is another point of context dependency based on accessibility. The purpose of this method is very similar to the purpose of a user-defined conversion method, and that one must be public. Therefore, we should consider requiring the *create method* to be public as well. #### Conclusion Approved with modifications [LDM-2024-01-08](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-08.md#iteration-type-of-collectionbuilderattribute-collections) ### The notion of [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) is not applied consistently throughout [*conversions*](#conversions) > * To a *struct* or *class type* that implements `System.Collections.Generic.IEnumerable` where: > * For each *element* `Ei` there is an *implicit conversion* to `T`. It looks like an assumption is made that `T` is necessary the *iteration type* of the *struct* or *class type* in this case. However, that assumption is incorrect. Which can lead to a very strange behavior. For example: ``` C# using System.Collections; using System.Collections.Generic; class MyCollection : IEnumerable { IEnumerator IEnumerable.GetEnumerator() => throw null; IEnumerator IEnumerable.GetEnumerator() => throw null; public void Add(string l) => throw null; public IEnumerator GetEnumerator() => throw null; } class Program { static void Main() { foreach (var l in new MyCollection()) { string s = l; // Iteration type is string } MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long' 2]; MyCollection x2 = new MyCollection() { "b" }; } } ``` > * To a *struct* or *class type* that implements `System.Collections.IEnumerable` and *does not implement* `System.Collections.Generic.IEnumerable`. It looks like implementation assumes that the *iteration type* is `object`, but the specification leaves this fact unspecified, and simply doesn't require each *element* to convert to anything. In general, however, the *iteration type* is not necessary the `object` type. Which can be observed in the following example: ``` C# using System.Collections; using System.Collections.Generic; class MyCollection : IEnumerable { public IEnumerator GetEnumerator() => throw null; IEnumerator IEnumerable.GetEnumerator() => throw null; } class Program { static void Main() { foreach (var l in new MyCollection()) { string s = l; // Iteration type is string } } } ``` The notion of *iteration type* is fundamental to [Params Collections](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/params-collections.md#applicable-function-member) feature. And this issue leads to a strange discrepancy between the two features. For Example: ``` C# using System.Collections; using System.Collections.Generic; class MyCollection : IEnumerable { IEnumerator IEnumerable.GetEnumerator() => throw null; IEnumerator IEnumerable.GetEnumerator() => throw null; public IEnumerator GetEnumerator() => throw null; public void Add(long l) => throw null; public void Add(string l) => throw null; } class Program { static void Main() { Test("2"); // error CS0029: Cannot implicitly convert type 'string' to 'long' Test(["2"]); // error CS1503: Argument 1: cannot convert from 'collection expressions' to 'string' Test(3); // error CS1503: Argument 1: cannot convert from 'int' to 'string' Test([3]); // Ok MyCollection x1 = ["2"]; // error CS0029: Cannot implicitly convert type 'string' to 'long' MyCollection x2 = [3]; } static void Test(params MyCollection a) { } } ``` ``` C# using System.Collections; using System.Collections.Generic; class MyCollection : IEnumerable { IEnumerator IEnumerable.GetEnumerator() => throw null; public IEnumerator GetEnumerator() => throw null; public void Add(object l) => throw null; } class Program { static void Main() { Test("2", 3); // error CS1503: Argument 2: cannot convert from 'int' to 'string' Test(["2", 3]); // Ok } static void Test(params MyCollection a) { } } ``` It will probably be good to align one way or the other. #### Proposal Specify convertibility of *struct* or *class type* that implements `System.Collections.Generic.IEnumerable` or `System.Collections.IEnumerable` in terms of *iteration type* and require an *implicit conversion* for each *element* `Ei` to the *iteration type*. #### Conclusion Approved [LDM-2024-01-08](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-08.md#iteration-type-in-conversions) ### Should *collection expression conversion* require availability of a minimal set of APIs for construction? A *constructible* collection type according to [*conversions*](#conversions) can actually be not constructible, which is likely to lead to some unexpected overload resolution behavior. For example: ``` C# class C1 { public static void M1(string x) { } public static void M1(char[] x) { } void Test() { M1(['a', 'b']); // error CS0121: The call is ambiguous between the following methods or properties: 'C1.M1(string)' and 'C1.M1(char[])' } } ``` However, the 'C1.M1(string)' is not a candidate that can be used because: ``` error CS1729: 'string' does not contain a constructor that takes 0 arguments error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) ``` Here is another example with a user-defined type and a stronger error that doesn't even mention a valid candidate: ``` C# using System.Collections; using System.Collections.Generic; class C1 : IEnumerable { public static void M1(C1 x) { } public static void M1(char[] x) { } void Test() { M1(['a', 'b']); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?) } public static implicit operator char[](C1 x) => throw null; IEnumerator IEnumerable.GetEnumerator() => throw null; IEnumerator IEnumerable.GetEnumerator() => throw null; } ``` It looks like the situation is very similar to what we used to have with method group to delegate conversions. I.e. there were scenarios where the conversion existed, but was erroneous. We decided to improve that by ensuring that, if conversion is erroneous, then it doesn't exist. Note, that with "Params Collections" feature we will be running into a similar issue. It might be good to disallow usage of `params` modifier for not constructible collections. However in the current proposal that check is based on [*conversions*](#conversions) section. Here is an example: ``` C# using System.Collections; using System.Collections.Generic; class C1 : IEnumerable { public static void M1(params C1 x) // It is probably better to report an error about an invalid `params` modifier { } public static void M1(params ushort[] x) { } void Test() { M1('a', 'b'); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?) M2('a', 'b'); // Ok } public static void M2(params ushort[] x) { } IEnumerator IEnumerable.GetEnumerator() => throw null; IEnumerator IEnumerable.GetEnumerator() => throw null; } ``` It looks like the issue was somewhat discussed previously, see https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md#collection-expressions. At that time an argument was made that the rules, as specified right now, are consistent with how interpolated string handlers are specified. Here is a quote: >In particular, interpolated string handlers were originally specified this way, but we revised the specification after considering this issue. While there is some similarity, there is also an important distinction worth considering. Here is a quote from https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#interpolated-string-handler-conversion: > Type `T` is said to be an _applicable\_interpolated\_string\_handler\_type_ if it is attributed with `System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute`. There exists an implicit _interpolated\_string\_handler\_conversion_ to `T` from an _interpolated\_string\_expression_, or an _additive\_expression_ composed entirely of _interpolated\_string\_expression_s and using only `+` operators. The target type must have a special attribute which is a strong indicator of author's intent for the type to be an interpolated string handler. It is fair to assume that presence of the attribute is not a coincidence. In contrast, the fact that a type is "enumerable", doesn't necessary mean that there was author's intent for the type to be constructible. A presence of a *[create method](#create-methods)*, however, which is indicated with a `[CollectionBuilder(...)]` attribute on the *collection type*, feels like a strong indicator of author's intent for the type to be constructible. #### Proposal For a *struct* or *class type* that implements `System.Collections.IEnumerable` and that does not have a [*create method*](#create-methods) [*conversions*](#conversions) section should require presence of at least the following APIs: - An accessible constructor that is applicable with no arguments. - An accessible `Add` instance or extension method that can be invoked with value of *iteration type* as the argument. For the purpose of [Params Collections](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/params-collections.md#method-parameters) feature, such types are valid `params` types when these APIs are declared public and are instance (vs. extension) methods. #### Conclusion Approved with modifications [LDM-2024-01-10](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-10.md) ================================================ FILE: proposals/csharp-12.0/experimental-attribute.md ================================================ ExperimentalAttribute ===================== [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Report warnings for references to types and members marked with `System.Diagnostics.CodeAnalysis.ExperimentalAttribute`. ```cs namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] public sealed class ExperimentalAttribute : Attribute { public ExperimentalAttribute(string diagnosticId) { DiagnosticId = diagnosticId; } public string DiagnosticId { get; } public string? UrlFormat { get; set; } public string? Message { get; set; } } } ``` ## Reported diagnostic Although the diagnostic is technically a warning, so that the compiler allows suppressing it, it is treated as an error for purpose of reporting. This causes the build to fail if the diagnostic is not suppressed. The diagnostic is reported for any reference to a type or member that is either: - marked with the attribute, - in an assembly or module marked with the attribute, except when the reference occurs within `[Experimental]` members, when it is automatically suppressed. It is also possible to suppress the diagnostic by usual means, such as an explicit compiler option or `#pragma`. For example, if the API is marked with `[Experimental("DiagID")]` or `[Experimental("DiagID", UrlFormat = "https://example.org/{0}")]`, the diagnostic can be suppressed with `#pragma warning disable DiagID`. An error is produced if the diagnostic ID given to the experimental attribute is not a [valid C# identifier](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/lexical-structure.md#643-identifiers). If a value for `Message` property is not provided, the diagnostic message is a specific message, where `'{0}'` is the fully-qualified type or member name. ``` '{0}' is for evaluation purposes only and is subject to change or removal in future updates. ``` If a value for `Message` property is provided, the diagnostic message is a specific message, where `'{0}'` is the fully-qualified type or member name and `'{1}'` is the `Message`. ``` '{0}' is for evaluation purposes only and is subject to change or removal in future updates: '{1}'. ``` The attribute is not inherited from base types or overridden members. ## ObsoleteAttribute and DeprecatedAttribute Warnings for `[Experimental]` are reported within `[Obsolete]` or `[Deprecated]` members. Warnings and errors for `[Obsolete]` and `[Deprecated]` are reported inside `[Experimental]` members. But warnings and errors for `[Obsolete]` and `[Deprecated]` are reported instead of `[Experimental]` if there are multiple attributes. ================================================ FILE: proposals/csharp-12.0/inline-arrays.md ================================================ Inline Arrays ===== [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Provide a general-purpose and safe mechanism for consuming struct types utilizing [InlineArrayAttribute](https://github.com/dotnet/runtime/issues/61135) feature. Provide a general-purpose and safe mechanism for declaring inline arrays within C# classes, structs, and interfaces. > Note: Previous versions of this spec used the terms "ref-safe-to-escape" and "safe-to-escape", which were introduced in the [Span safety](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.2/span-safety.md) feature specification. The [ECMA standard committee](https://www.ecma-international.org/task-groups/tc49-tg2/) changed the names to ["ref-safe-context"](https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/variables#972-ref-safe-contexts) and ["safe-context"](https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/structs#16412-safe-context-constraint), respectively. The values of the safe context have been refined to use "declaration-block", "function-member", and "caller-context" consistently. The speclets had used different phrasing for these terms, and also used "safe-to-return" as a synonym for "caller-context". This speclet has been updated to use the terms in the C# 7.3 standard. ## Motivation This proposal plans to address the many limitations of https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers. Specifically it aims to allow: - accessing elements of struct types utilizing [InlineArrayAttribute](https://github.com/dotnet/runtime/issues/61135) feature; - the declaration of inline arrays for managed and unmanaged types in a `struct`, `class`, or `interface`. And provide language safety verification for them. ## Detailed Design Recently runtime added [InlineArrayAttribute](https://github.com/dotnet/runtime/issues/61135) feature. In short, a user can declare a structure type like the following: ``` C# [System.Runtime.CompilerServices.InlineArray(10)] public struct Buffer { private object _element0; } ``` Runtime provides a special type layout for the `Buffer` type: - The size of the type is extended to fit 10 (the number comes from the InlineArray attribute) elements of `object` type (the type comes from the type of the only instance field in the struct, `_element0` in this example). - The first element is aligned with the instance field and with the beginning of the struct - The elements are laid out sequentially in memory as though they are elements of an array. Runtime provides regular GC tracking for all elements in the struct. This proposal will refer to types like this as "inline array types". Elements of an inline array type can be accessed through pointers or through span instances returned by [System.Runtime.InteropServices.MemoryMarshal.CreateSpan](https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.memorymarshal.createspan)/[System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan](https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.memorymarshal.createreadonlyspan) APIs. However, neither the pointer approach, nor the APIs provide type and bounds checking out of the box. Language will provide a type-safe/ref-safe way for accessing elements of inline array types. The access will be span based. This limits support to inline array types with element types that can be used as a type argument. For example, a pointer type cannot be used as an element type. Other examples the span types. ### Obtaining instances of span types for an inline array type Since there is a guarantee that the first element in an inline array type is aligned at the beginning of the type (no gap), compiler will use the following code to get a `Span` value: ``` C# MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), size); ``` And the following code to get a `ReadOnlySpan` value: ``` C# MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in buffer)), size); ``` In order to reduce IL size at use sites compiler should be able to add two generic reusable helpers into private implementation detail type and use them across all use sites in the same program. ``` C# public static System.Span InlineArrayAsSpan(ref TBuffer buffer, int size) where TBuffer : struct { return MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), size); } public static System.ReadOnlySpan InlineArrayAsReadOnlySpan(in TBuffer buffer, int size) where TBuffer : struct { return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As(ref Unsafe.AsRef(in buffer)), size); } ``` ### Element access The [Element access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12821-element-access) will be extended to support inline array element access. An *element_access* consists of a *primary_no_array_creation_expression*, followed by a “`[`” token, followed by an *argument_list*, followed by a “`]`” token. The *argument_list* consists of one or more *argument*s, separated by commas. ```ANTLR element_access : primary_no_array_creation_expression '[' argument_list ']' ; ``` The *argument_list* of an *element_access* is not allowed to contain `ref` or `out` arguments. An *element_access* is dynamically bound ([§11.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1233-dynamic-binding)) if at least one of the following holds: - The *primary_no_array_creation_expression* has compile-time type `dynamic`. - At least one expression of the *argument_list* has compile-time type `dynamic` and the *primary_no_array_creation_expression* does not have an array type, **and the *primary_no_array_creation_expression* does not have an inline array type or there is more than one item in the argument list**. In this case, the compiler classifies the *element_access* as a value of type `dynamic`. The rules below to determine the meaning of the *element_access* are then applied at run-time, using the run-time type instead of the compile-time type of those of the *primary_no_array_creation_expression* and *argument_list* expressions which have the compile-time type `dynamic`. If the *primary_no_array_creation_expression* does not have compile-time type `dynamic`, then the element access undergoes a limited compile-time check as described in [§11.6.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation). If the *primary_no_array_creation_expression* of an *element_access* is a value of an *array_type*, the *element_access* is an array access ([§12.8.12.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128122-array-access)). **If the *primary_no_array_creation_expression* of an *element_access* is a variable or value of an inline array type and the *argument_list* consists of a single argument, the *element_access* is an inline array element access.** Otherwise, the *primary_no_array_creation_expression* shall be a variable or value of a class, struct, or interface type that has one or more indexer members, in which case the *element_access* is an indexer access ([§12.8.12.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128123-indexer-access)). #### Inline array element access For an inline array element access, the *primary_no_array_creation_expression* of the *element_access* must be a variable or value of an inline array type. Furthermore, the *argument_list* of an inline array element access is not allowed to contain named arguments. The *argument_list* must contain a single expression, and the expression must be - of type `int`, or - implicitly convertible to `int`, or - implicitly convertible to `System.Index`, or - implicitly convertible to `System.Range`. ##### When the expression type is int If *primary_no_array_creation_expression* is a writable variable, the result of evaluating an inline array element access is a writable variable equivalent to invoking [`public ref T this[int index] { get; }`](https://learn.microsoft.com/dotnet/api/system.span-1.item) with that integer value on an instance of `System.Span` returned by `System.Span InlineArrayAsSpan` method on *primary_no_array_creation_expression*. For the purpose of ref-safety analysis the *ref-safe-context*/*safe-context* of the access are equivalent to the same for an invocation of a method with the signature `static ref T GetItem(ref InlineArrayType array)`. The resulting variable is considered movable if and only if *primary_no_array_creation_expression* is movable. If *primary_no_array_creation_expression* is a readonly variable, the result of evaluating an inline array element access is a readonly variable equivalent to invoking [`public ref readonly T this[int index] { get; }`](https://learn.microsoft.com/dotnet/api/system.readonlyspan-1.item) with that integer value on an instance of `System.ReadOnlySpan` returned by `System.ReadOnlySpan InlineArrayAsReadOnlySpan` method on *primary_no_array_creation_expression*. For the purpose of ref-safety analysis the *ref-safe-context*/*safe-context* of the access are equivalent to the same for an invocation of a method with the signature `static ref readonly T GetItem(in InlineArrayType array)`. The resulting variable is considered movable if and only if *primary_no_array_creation_expression* is movable. If *primary_no_array_creation_expression* is a value, the result of evaluating an inline array element access is a value equivalent to invoking [`public ref readonly T this[int index] { get; }`](https://learn.microsoft.com/dotnet/api/system.readonlyspan-1.item) with that integer value on an instance of `System.ReadOnlySpan` returned by `System.ReadOnlySpan InlineArrayAsReadOnlySpan` method on *primary_no_array_creation_expression*. For the purpose of ref-safety analysis the *ref-safe-context*/*safe-context* of the access are equivalent to the same for an invocation of a method with the signature `static T GetItem(InlineArrayType array)`. For example: ``` C# [System.Runtime.CompilerServices.InlineArray(10)] public struct Buffer10 { private T _element0; } void M1(Buffer10 x) { ref int a = ref x[0]; // Ok, equivalent to `ref int a = ref InlineArrayAsSpan, int>(ref x, 10)[0]` } void M2(in Buffer10 x) { ref readonly int a = ref x[0]; // Ok, equivalent to `ref readonly int a = ref InlineArrayAsReadOnlySpan, int>(in x, 10)[0]` ref int b = ref x[0]; // An error, `x` is a readonly variable => `x[0]` is a readonly variable } Buffer10 GetBuffer() => default; void M3() { int a = GetBuffer()[0]; // Ok, equivalent to `int a = InlineArrayAsReadOnlySpan, int>(GetBuffer(), 10)[0]` ref readonly int b = ref GetBuffer()[0]; // An error, `GetBuffer()[0]` is a value ref int c = ref GetBuffer()[0]; // An error, `GetBuffer()[0]` is a value } ``` Indexing into an inline array with a constant expression outside of the declared inline array bounds is a compile time error. ##### When the expression is implicitly convertible to `int` The expression is converted to int and then the element access is interpreted as described in **When the expression type is int** section. ##### When the expression implicitly convertible to `System.Index` The expression is converted to `System.Index`, which is then transformed to an int-based index value as described at https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, assuming that the length of the collection is known at compile time and is equal to the amount of elements in the inline array type of the *primary_no_array_creation_expression*. Then the element access is interpreted as described in **When the expression type is int** section. ##### When the expression implicitly convertible to `System.Range` If *primary_no_array_creation_expression* is a writable variable, the result of evaluating an inline array element access is a value equivalent to invoking [`public Span Slice (int start, int length)`](https://learn.microsoft.com/dotnet/api/system.span-1.slice) on an instance of `System.Span` returned by `System.Span InlineArrayAsSpan` method on *primary_no_array_creation_expression*. For the purpose of ref-safety analysis the *ref-safe-context*/*safe-context* of the access are equivalent to the same for an invocation of a method with the signature `static System.Span GetSlice(ref InlineArrayType array)`. If *primary_no_array_creation_expression* is a readonly variable, the result of evaluating an inline array element access is a value equivalent to invoking [`public ReadOnlySpan Slice (int start, int length)`](https://learn.microsoft.com/dotnet/api/system.readonlyspan-1.slice) on an instance of `System.ReadOnlySpan` returned by `System.ReadOnlySpan InlineArrayAsReadOnlySpan` method on *primary_no_array_creation_expression*. For the purpose of ref-safety analysis the *ref-safe-context*/*safe-context* of the access are equivalent to the same for an invocation of a method with the signature `static System.ReadOnlySpan GetSlice(in InlineArrayType array)`. If *primary_no_array_creation_expression* is a value, an error is reported. The arguments for the `Slice` method invocation are calculated from the index expression converted to `System.Range` as described at https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, assuming that the length of the collection is known at compile time and is equal to the amount of elements in the inline array type of the *primary_no_array_creation_expression*. Compiler can omit the `Slice` call if it is known at compile time that `start` is 0 and `length` is less or equal to the amount of elements in the inline array type. Compiler can also report an error if it is known at compile time that slicing goes out of inline array bounds. For example: ``` C# void M1(Buffer10 x) { System.Span a = x[..]; // Ok, equivalent to `System.Span a = InlineArrayAsSpan, int>(ref x, 10).Slice(0, 10)` } void M2(in Buffer10 x) { System.ReadOnlySpan a = x[..]; // Ok, equivalent to `System.ReadOnlySpan a = InlineArrayAsReadOnlySpan, int>(in x, 10).Slice(0, 10)` System.Span b = x[..]; // An error, System.ReadOnlySpan cannot be converted to System.Span } Buffer10 GetBuffer() => default; void M3() { _ = GetBuffer()[..]; // An error, `GetBuffer()` is a value } ``` ### Conversions A new conversion, an inline array conversion, from expression will be added. The inline array conversion is a [standard conversion](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#104-standard-conversions). There is an implicit conversion from expression of an inline array type to the following types: - `System.Span` - `System.ReadOnlySpan` However, converting a readonly variable to `System.Span` or converting a value to either type is an error. For example: ``` C# void M1(Buffer10 x) { System.ReadOnlySpan a = x; // Ok, equivalent to `System.ReadOnlySpan a = InlineArrayAsReadOnlySpan, int>(in x, 10)` System.Span b = x; // Ok, equivalent to `System.Span b = InlineArrayAsSpan, int>(ref x, 10)` } void M2(in Buffer10 x) { System.ReadOnlySpan a = x; // Ok, equivalent to `System.ReadOnlySpan a = InlineArrayAsReadOnlySpan, int>(in x, 10)` System.Span b = x; // An error, readonly mismatch } Buffer10 GetBuffer() => default; void M3() { System.ReadOnlySpan a = GetBuffer(); // An error, ref-safety System.Span b = GetBuffer(); // An error, ref-safety } ``` For the purpose of ref-safety analysis the *safe-context* of the conversion is equivalent to *safe-context* for an invocation of a method with the signature `static System.Span Convert(ref InlineArrayType array)`, or `static System.ReadOnlySpan Convert(in InlineArrayType array)`. ### List patterns [List patterns](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/list-patterns.md) will not be supported for instances of inline array types. ### Definite assignment checking Regular definite assignment rules are applicable to variables that have an inline array type. ### Collection literals An instance of an inline array type is a valid expression in a [*spread_element*](collection-expressions.md#detailed-design). The following feature did not ship in C# 12. It remains an open proposal. The code in this example generates **CS9174**:
An inline array type is a valid *constructible collection* target type for a [collection expression](collection-expressions.md). For example: ``` C# Buffer10 b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array ``` The length of the collection literal must match the length of the target inline array type. If the length of the literal is known at compile time and it doesn't match the target length, an error is reported. Otherwise, an exception is going to be thrown at runtime once the mismatch is encountered. The exact exception type is TBD. Some candidates are: System.NotSupportedException, System.InvalidOperationException.
### Validation of the InlineArrayAttribute applications Compiler will validate the following aspects of the InlineArrayAttribute applications: - The target type is a [non-record](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#conclusion-3) struct - The target type has only one field - Specified length > 0 - The target struct doesn't have an explicit layout specified ### Inline Array elements in an object initializer By default, element initialization will not be supported via *initializer_target* of form `'[' argument_list ']'` (see https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128173-object-initializers): ``` C# static C M2() => new C() { F = {[0] = 111} }; // error CS1913: Member '[0]' cannot be initialized. It is not a field or property. class C { public Buffer10 F; } ``` However, if the inline array type explicitly defines suitable indexer, object initializer will use it: ``` C# static C M2() => new C() { F = {[0] = 111} }; // Ok, indexer is invoked class C { public Buffer10 F; } [System.Runtime.CompilerServices.InlineArray(10)] public struct Buffer10 { private T _element0; public T this[int i] { get => this[i]; set => this[i] = value; } } ``` ### The foreach statement [The foreach statement](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement) will be adjusted to allow usage of an inline array type as a collection in a foreach statement. For example: ``` C# foreach (var a in getBufferAsValue()) { WriteLine(a); } foreach (var b in getBufferAsWritableVariable()) { WriteLine(b); } foreach (var c in getBufferAsReadonlyVariable()) { WriteLine(c); } Buffer10 getBufferAsValue() => default; ref Buffer10 getBufferAsWritableVariable() => default; ref readonly Buffer10 getBufferAsReadonlyVariable() => default; ``` is equivalent to: ``` C# Buffer10 temp = getBufferAsValue(); foreach (var a in (System.ReadOnlySpan)temp) { WriteLine(a); } foreach (var b in (System.Span)getBufferAsWritableVariable()) { WriteLine(b); } foreach (var c in (System.ReadOnlySpan)getBufferAsReadonlyVariable()) { WriteLine(c); } ``` We will support `foreach` over inline arrays, even if it starts as restricted in `async` methods due to involvement of the span types into the translation. ## Open design questions ## Alternatives ### Inline array type syntax The grammar at https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general will be adjusted as follows: ``` diff antlr array_type : non_array_type rank_specifier+ ; rank_specifier : '[' ','* ']' + | '[' constant_expression ']' ; ``` The type of the *constant_expression* must be implicitly convertible to type `int`, and the value must be a non-zero positive integer. The relevant part of the https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general section will be adjusted as follows. The grammar productions for array types are provided in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general. An array type is written as a *non_array_type* followed by one or more *rank_specifier*s. A *non_array_type* is any *type* that is not itself an *array_type*. The rank of an array type is given by the leftmost *rank_specifier* in the *array_type*: A *rank_specifier* indicates that the array is an array with a rank of one plus the number of “`,`” tokens in the *rank_specifier*. The element type of an array type is the type that results from deleting the leftmost *rank_specifier*: - An array type of the form `T[ constant_expression ]` is an anonymous inline array type with length denoted by *constant_expression* and a non-array element type `T`. - An array type of the form `T[ constant_expression ][R₁]...[Rₓ]` is an anonymous inline array type with length denoted by *constant_expression* and an element type `T[R₁]...[Rₓ]`. - An array type of the form `T[R]` (where R is not a *constant_expression*) is a regular array type with rank `R` and a non-array element type `T`. - An array type of the form `T[R][R₁]...[Rₓ]` (where R is not a *constant_expression*) is a regular array type with rank `R` and an element type `T[R₁]...[Rₓ]`. In effect, the *rank_specifier*s are read from left to right *before* the final non-array element type. > *Example*: The type in `int[][,,][,]` is a single-dimensional array of three-dimensional arrays of two-dimensional arrays of `int`. *end example* At run-time, a value of a regular array type can be `null` or a reference to an instance of that array type. > *Note*: Following the rules of https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariance, > the value may also be a reference to a covariant array type. *end note* An anonymous inline array type is a compiler synthesized inline array type with internal accessibility. The element type must be a type that can be used as a type argument. Unlike an explicitly declared inline array type, an anonymous inline array type cannot be referenced by name, it can be referenced only by *array_type* syntax. In context of the same program, any two *array_type*s denoting inline array types of the same element type and of the same length, refer to the same anonymous inline array type. Besides internal accessibility, compiler will prevent consumption of APIs utilizing anonymous inline array types across assembly boundaries by using a required custom modifier (exact type TBD) applied to an anonymous inline array type reference in the signature. #### Array creation expressions [Array creation expressions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128175-array-creation-expressions) ```ANTLR array_creation_expression : 'new' non_array_type '[' expression_list ']' rank_specifier* array_initializer? | 'new' array_type array_initializer | 'new' rank_specifier array_initializer ; ``` Given the current grammar, use of a *constant_expression* in place of the *expression_list* already has meaning of allocating a regular single-dimensional array type of the specified length. Therefore, *array_creation_expression* will continue to represent an allocation of a regular array. However, the new form of the *rank_specifier* could be used to incorporate an anonymous inline array type into the element type of the allocated array. For example, the following expressions create a regular array of length 2 with an element type of an anonymous inline array type with element type int and length 5: ``` C# new int[2][5]; new int[][5] {default, default}; new [] {default(int[5]), default(int[5])}; ``` #### Array initializers Array initializers were not implemented in C# 12. This section remains an active proposal.
The [Array initializers](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#177-array-initializers) section will be adjusted to allow use of *array_initializer* to initialize inline array types (no changes to the grammar necessary). ```ANTLR array_initializer : '{' variable_initializer_list? '}' | '{' variable_initializer_list ',' '}' ; variable_initializer_list : variable_initializer (',' variable_initializer)* ; variable_initializer : expression | array_initializer ; ``` The length of the inline array must be explicitly provided by the target type. For example: ``` C# int[5] a = {1, 2, 3, 4, 5}; // initializes anonymous inline array of length 5 Buffer10 b = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // initializes user-defined inline array var c = new int[][] {{11, 12}, {21, 22}, {31, 32}}; // An error for the nested array initializer var d = new int[][2] {{11, 12}, {21, 22}, {31, 32}}; // An error for the nested array initializer ```
### Detailed Design (Option 2) Note, that for the purpose of this proposal a term "fixed-size buffer" refers to a the proposed "safe fixed-size buffer" feature rather than to a buffer described at https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers. In this design, fixed-size buffer types do not get general special treatment by the language. There is a special syntax to declare members that represent fixed-size buffers and new rules around consuming those members. They are not fields from the language point of view. The grammar for *variable_declarator* in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields will be extended to allow specifying the size of the buffer: ``` diff antlr field_declaration : attributes? field_modifier* type variable_declarators ';' ; field_modifier : 'new' | 'public' | 'protected' | 'internal' | 'private' | 'static' | 'readonly' | 'volatile' | unsafe_modifier // unsafe code support ; variable_declarators : variable_declarator (',' variable_declarator)* ; variable_declarator : identifier ('=' variable_initializer)? + | fixed_size_buffer_declarator ; fixed_size_buffer_declarator : identifier '[' constant_expression ']' ; ``` A *fixed_size_buffer_declarator* introduces a fixed-size buffer of a given element type. The buffer element type is the *type* specified in `field_declaration`. A fixed-size buffer declarator introduces a new member and consists of an identifier that names the member, followed by a constant expression enclosed in `[` and `]` tokens. The constant expression denotes the number of elements in the member introduced by that fixed-size buffer declarator. The type of the constant expression must be implicitly convertible to type `int`, and the value must be a non-zero positive integer. The elements of a fixed-size buffer shall be laid out sequentially in memory as though they are elements of an array. A *field_declaration* with a *fixed_size_buffer_declarator* in an interface must have `static` modifier. Depending on the situation (details are specified below), an access to a fixed-size buffer member is classified as a value (never a variable) of either `System.ReadOnlySpan` or `System.Span`, where S is the element type of the fixed-size buffer. Both types provide indexers returning a reference to a specific element with appropriate "readonly-ness", which prevents direct assignment to the elements when language rules don't permit that. This limits the set of types that can be used as a fixed-size buffer element type to types that can be used as type arguments. For example, a pointer type cannot be used as an element type. The resulting span instance will have a length equal to the size declared on the fixed-size buffer. Indexing into the span with a constant expression outside of the declared fixed-size buffer bounds is a compile time error. The *safe-context* of the value will be equal to the *safe-context* of the container, just as it would if the backing data was accessed as a field. #### Fixed-size buffers in expressions Member lookup of a fixed-size buffer member proceeds exactly like member lookup of a field. A fixed-size buffer can be referenced in an expression using a *simple_name* or a *member_access* . When an instance fixed-size buffer member is referenced as a simple name, the effect is the same as a member access of the form `this.I`, where `I` is the fixed-size buffer member. When a static fixed-size buffer member is referenced as a simple name, the effect is the same as a member access of the form `E.I`, where `I` is the fixed-size buffer member and `E` is the declaring type. ##### Non-readonly fixed-size buffers In a member access of the form `E.I`, if `E` is of a struct type and a member lookup of `I` in that struct type identifies a non-readonly instance fixed-size member, then `E.I` is evaluated and classified as follows: - If `E` is classified as a value, then `E.I` can be used only as a *primary_no_array_creation_expression* of an [element access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12812-element-access) with index of `System.Index` type, or of a type implicitly convertible to int. Result of the element access is a fixed-size member's element at the specified position, classified as a value. - Otherwise, if `E` is classified as a readonly variable and the result of the expression is classified as a value of type `System.ReadOnlySpan`, where S is the element type of `I`. The value can be used to access member's elements. - Otherwise, `E` is classified as a writable variable and the result of the expression is classified as a value of type `System.Span`, where S is the element type of `I`. The value can be used to access member's elements. In a member access of the form `E.I`, if `E` is of a class type and a member lookup of `I` in that class type identifies a non-readonly instance fixed-size member, then `E.I` is evaluated and classified as a value of type `System.Span`, where S is the element type of `I`. In a member access of the form `E.I`, if member lookup of `I` identifies a non-readonly static fixed-size member, then `E.I` is evaluated and classified as a value of type `System.Span`, where S is the element type of `I`. ##### Readonly fixed-size buffers When a *field_declaration* includes a `readonly` modifier, the member introduced by the fixed_size_buffer_declarator is a ***readonly fixed-size buffer***. Direct assignments to elements of a readonly fixed-size buffer can only occur in an instance constructor, init member or static constructor in the same type. Specifically, direct assignments to an element of readonly fixed-size buffer are permitted only in the following contexts: - For an instance member, in the instance constructors or init member of the type that contains the member declaration; for a static member, in the static constructor of the type that contains the member declaration. These are also the only contexts in which it is valid to pass an element of readonly fixed-size buffer as an `out` or `ref` parameter. Attempting to assign to an element of a readonly fixed-size buffer or pass it as an `out` or `ref` parameter in any other context is a compile-time error. This is achieved by the following. A member access for a readonly fixed-size buffer is evaluated and classified as follows: - In a member access of the form `E.I`, if `E` is of a struct type and `E` is classified as a value, then `E.I` can be used only as a *primary_no_array_creation_expression* of an [element access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12812-element-access) with index of `System.Index` type, or of a type implicitly convertible to int. Result of the element access is a fixed-size member's element at the specified position, classified as a value. - If access occurs in a context where direct assignments to an element of readonly fixed-size buffer are permitted, the result of the expression is classified as a value of type `System.Span`, where S is the element type of the fixed-size buffer. The value can be used to access member's elements. - Otherwise, the expression is classified as a value of type `System.ReadOnlySpan`, where S is the element type of the fixed-size buffer. The value can be used to access member's elements. #### Definite assignment checking Fixed-size buffers are not subject to definite assignment-checking, and fixed-size buffer members are ignored for purposes of definite-assignment checking of struct type variables. When a fixed-size buffer member is static or the outermost containing struct variable of a fixed-size buffer member is a static variable, an instance variable of a class instance, or an array element, the elements of the fixed-size buffer are automatically initialized to their default values. In all other cases, the initial content of a fixed-size buffer is undefined. #### Metadata ##### Metadata emit and code generation For metadata encoding compiler will rely on recently added [`System.Runtime.CompilerServices.InlineArrayAttribute`](https://github.com/dotnet/runtime/issues/61135). Fixed-size buffers like the following pseudocode: ``` C# // Not valid C# public partial class C { public int buffer1[10]; public readonly int buffer2[10]; } ``` will be emitted as fields of a specially decorated struct type. Equivalent C# code will be: ``` C# public partial class C { public Buffer10 buffer1; public readonly Buffer10 buffer2; } [System.Runtime.CompilerServices.InlineArray(10)] public struct Buffer10 { private T _element0; [UnscopedRef] public System.Span AsSpan() { return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref _element0, 10); } [UnscopedRef] public readonly System.ReadOnlySpan AsReadOnlySpan() { return System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan( ref System.Runtime.CompilerServices.Unsafe.AsRef(in _element0), 10); } } ``` The actual naming conventions for the type and its members are TBD. The framework will likely include a set of predefined "buffer" types that cover a limited set of buffer sizes. When a predefined type doesn't exist, compiler will synthesize it in the module being built. Names of the generated types will be "speakable" in order to support consumption from other languages. A code generated for an access like: ``` C# public partial class C { void M1(int val) { buffer1[1] = val; } int M2() { return buffer2[1]; } } ``` will be equivalent to: ``` C# public partial class C { void M1(int val) { buffer.AsSpan()[1] = val; } int M2() { return buffer2.AsReadOnlySpan()[1]; } } ``` ##### Metadata import When compiler imports a field declaration of type *T* and the following conditions are all met: - *T* is a struct type decorated with the `InlineArray` attribute, and - The first instance field declared within *T* has type *F*, and - There is a `public System.Span AsSpan()` within *T*, and - There is a `public readonly System.ReadOnlySpan AsReadOnlySpan()` or `public System.ReadOnlySpan AsReadOnlySpan()` within *T*. the field will be treated as C# fixed-size buffer with element type *F*. Otherwise, the field will be treated as a regular field of type *T*. ### Method or property group like approach in the language One thought is to treat these members more like method groups, in that they aren't automatically a value in and of themselves, but can be made into one if necessary. Here’s how that would work: - Safe fixed-size buffer accesses have their own classification (just like e.g. method groups and lambdas) - They can be indexed directly as a language operation (not via span types) to produce a variable (which is readonly if the buffer is in a readonly context, just the same as fields of a struct) - They have implicit conversions-from-expression to `Span` and `ReadOnlySpan`, but use of the former is an error if they are in a readonly context - Their natural type is `ReadOnlySpan`, so that’s what they contribute if they participate in type inference (e.g., var, best-common-type or generic) ### C/C++ fixed-size buffers C/C++ has a different notion of fixed-size buffers. For example, there is a notion of "zero-length fixed sized buffers", which is often used as a way to indicate that the data is "variable length". It is not a goal of this proposal to be able to interop with that. ## LDM meetings - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-03.md#fixed-size-buffers - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-10.md#fixed-size-buffers - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-01.md#fixed-size-buffers - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#inline-arrays - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-17.md#inline-arrays - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#inline-arrays-as-record-structs ================================================ FILE: proposals/csharp-12.0/lambda-method-group-defaults.md ================================================ # Optional and parameter array parameters for lambdas and method groups [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary To build on top of the lambda improvements introduced in C# 10 (see [relevant background](#relevant-background)), we propose adding support for default parameter values and `params` arrays in lambdas. This would enable users to implement the following lambdas: ```csharp var addWithDefault = (int addTo = 2) => addTo + 1; addWithDefault(); // 3 addWithDefault(5); // 6 var counter = (params int[] xs) => xs.Length; counter(); // 0 counter(1, 2, 3); // 3 ``` Similarly, we will allow the same kind of behavior for method groups: ```csharp var addWithDefault = AddWithDefaultMethod; addWithDefault(); // 3 addWithDefault(5); // 6 var counter = CountMethod; counter(); // 0 counter(1, 2); // 2 int AddWithDefaultMethod(int addTo = 2) { return addTo + 1; } int CountMethod(params int[] xs) { return xs.Length; } ``` ## Relevant background [Lambda Improvements in C# 10](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md) [Method group conversion specification §10.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#108-method-group-conversions) ## Motivation [motivation]: #motivation App frameworks in the .NET ecosystem leverage lambdas heavily to allow users to quickly write business logic associated with an endpoint. ```csharp var app = WebApplication.Create(args); app.MapPost("/todos/{id}", (TodoService todoService, int id, string task) => { var todo = todoService.Create(id, task); return Results.Created(todo); }); ``` Lambdas don't currently support setting default values on parameters, so if a developer wanted to build an application that was resilient to scenarios where users didn't provide data, they're left to either use local functions or set the default values within the lambda body, as opposed to the more succinct proposed syntax. ```csharp var app = WebApplication.Create(args); app.MapPost("/todos/{id}", (TodoService todoService, int id, string task = "foo") => { var todo = todoService.Create(id, task); return Results.Created(todo); }); ``` The proposed syntax also has the benefit of reducing confusing differences between lambdas and local functions, making it easier to reason about constructs and "grow up" lambdas to functions without compromising features, particularly in other scenarios where lambdas are used in APIs where method groups can also be provided as references. This is also the main motivation for supporting the `params` array which is not covered by the aforementioned use-case scenario. For example: ```csharp var app = WebApplication.Create(args); Result TodoHandler(TodoService todoService, int id, string task = "foo") { var todo = todoService.Create(id, task); return Results.Created(todo); } app.MapPost("/todos/{id}", TodoHandler); ``` ## Previous behavior Before C# 12, when a user implements a lambda with an optional or `params` parameter, the compiler raises an error. ```csharp var addWithDefault = (int addTo = 2) => addTo + 1; // error CS1065: Default values are not valid in this context. var counter = (params int[] xs) => xs.Length; // error CS1670: params is not valid in this context ``` When a user attempts to use a method group where the underlying method has an optional or `params` parameter, this information isn't propagated, so the call to the method doesn't typecheck due to a mismatch in the number of expected arguments. ```cs void M1(int i = 1) { } var m1 = M1; // Infers Action m1(); // error CS7036: There is no argument given that corresponds to the required parameter 'obj' of 'Action' void M2(params int[] xs) { } var m2 = M2; // Infers Action m2(); // error CS7036: There is no argument given that corresponds to the required parameter 'obj' of 'Action' ``` ## New behavior Following this proposal (part of C# 12), default values and `params` can be applied to lambda parameters with the following behavior: ```csharp var addWithDefault = (int addTo = 2) => addTo + 1; addWithDefault(); // 3 addWithDefault(5); // 6 var counter = (params int[] xs) => xs.Length; counter(); // 0 counter(1, 2, 3); // 3 ``` Default values and `params` can be applied to method group parameters by specifically defining such method group: ```cs int AddWithDefault(int addTo = 2) { return addTo + 1; } var add1 = AddWithDefault; add1(); // ok, default parameter value will be used int Counter(params int[] xs) { return xs.Length; } var counter1 = Counter; counter1(1, 2, 3); // ok, `params` will be used ``` ## Breaking change Before C# 12, the inferred type of a method group is `Action` or `Func` so the following code compiles: ```csharp void WriteInt(int i = 0) { Console.Write(i); } var writeInt = WriteInt; // Inferred as Action DoAction(writeInt, 3); // Ok, writeInt is an Action void DoAction(Action a, int p) { a(p); } int Count(params int[] xs) { return xs.Length; } var counter = Count; // Inferred as Func DoFunction(counter, 3); // Ok, counter is a Func int DoFunction(Func f, int p) { return f(new[] { p }); } ``` Following this change (part of C# 12), code of this nature ceases to compile in .NET SDK 7.0.200 or later. ```csharp void WriteInt(int i = 0) { Console.Write(i); } var writeInt = WriteInt; // Inferred as anonymous delegate type DoAction(writeInt, 3); // Error, cannot convert from anonymous delegate type to Action void DoAction(Action a, int p) { a(p); } int Count(params int[] xs) { return xs.Length; } var counter = Count; // Inferred as anonymous delegate type DoFunction(counter, 3); // Error, cannot convert from anonymous delegate type to Func int DoFunction(Func f, int p) { return f(new[] { p }); } ``` The impact of this breaking change needs to be considered. Fortunately, the use of `var` to infer the type of a method group has only been supported since C# 10, so only code which has been written since then which explicitly relies on this behavior would break. ## Detailed design [design]: #detailed-design ### Grammar and parser changes This enhancement requires the following changes to the grammar for lambda expressions. ```diff lambda_expression : modifier* identifier '=>' (block | expression) - | attribute_list* modifier* type? lambda_parameters '=>' (block | expression) + | attribute_list* modifier* type? lambda_parameter_list '=>' (block | expression) ; +lambda_parameter_list + : lambda_parameters (',' parameter_array)? + | parameter_array + ; lambda_parameter : identifier - | attribute_list* modifier* type? identifier + | attribute_list* modifier* type? identifier default_argument? ; ``` Note that this allows default parameter values and `params` arrays only for lambdas, not for anonymous methods declared with `delegate { }` syntax. Same rules as for method parameters ([§15.6.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/classes.md#1562-method-parameters)) apply for lambda parameters: - A parameter with a `ref`, `out` or `this` modifier cannot have a *default_argument*. - A *parameter_array* may occur after an optional parameter, but cannot have a default value – the omission of arguments for a *parameter_array* would instead result in the creation of an empty array. No changes to the grammar are necessary for method groups since this proposal would only change their semantics. The following addition (in bold) is required to anonymous function conversions ([§10.7](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/conversions.md#107-anonymous-function-conversions)): > Specifically, an anonymous function `F` is compatible with a delegate type `D` provided: > > - [...] > - If `F` has an explicitly typed parameter list, each parameter in `D` has the same type and modifiers as the corresponding parameter in `F` **ignoring `params` modifiers and default values**. ### Updates of prior proposals The following addition (in bold) is required to the [function types](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#natural-function-type) specification in a prior proposal: > A _method group_ has a natural type if all candidate methods in the method group have a common signature **including default values and `params` modifiers**. (If the method group may include extension methods, the candidates include the containing type and all extension method scopes.) > The natural type of an anonymous function expression or method group is a *function_type*. A *function_type* represents a method signature: the parameter types, **default values, ref kinds, `params` modifiers**, and return type and ref kind. Anonymous function expressions or method groups with the same signature have the same *function_type*. The following addition (in bold) is required to the [delegate types](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#delegate-types) specification in a prior proposal: > The delegate type for the anonymous function or method group with parameter types `P1, ..., Pn` and return type `R` is: > > - if any parameter or return value is not by value, **or any parameter is optional or `params`**, or there are more than 16 parameters, or any of the parameter types or return are not valid type arguments (say, `(int* p) => { }`), then the delegate is a synthesized `internal` anonymous delegate type with signature that matches the anonymous function or method group, and with parameter names `arg1, ..., argn` or `arg` if a single parameter; > [...] ### Binder changes #### Synthesizing new delegate types As with the behavior for delegates with `ref` or `out` parameters, delegate types are synthesized for lambdas or method groups defined with optional or `params` parameters. Note that in the below examples, the notation `a'`, `b'`, etc. is used to represent these anonymous delegate types. ```csharp var addWithDefault = (int addTo = 2) => addTo + 1; // internal delegate int a'(int arg = 2); var printString = (string toPrint = "defaultString") => Console.WriteLine(toPrint); // internal delegate void b'(string arg = "defaultString"); var counter = (params int[] xs) => xs.Length; // internal delegate int c'(params int[] arg); string PathJoin(string s1, string s2, string sep = "/") { return $"{s1}{sep}{s2}"; } var joinFunc = PathJoin; // internal delegate string d'(string arg1, string arg2, string arg3 = " "); ``` #### Conversion and unification behavior Anonymous delegates with optional parameters will be unified when the same parameter (based on position) has the same default value, regardless of parameter name. ```csharp int E(int j = 13) { return 11; } int F(int k = 0) { return 3; } int G(int x = 13) { return 4; } var a = (int i = 13) => 1; // internal delegate int b'(int arg = 13); var b = (int i = 0) => 2; // internal delegate int c'(int arg = 0); var c = (int i = 13) => 3; // internal delegate int b'(int arg = 13); var d = (int c = 13) => 1; // internal delegate int b'(int arg = 13); var e = E; // internal delegate int b'(int arg = 13); var f = F; // internal delegate int c'(int arg = 0); var g = G; // internal delegate int b'(int arg = 13); a = b; // Not allowed a = c; // Allowed a = d; // Allowed c = e; // Allowed e = f; // Not Allowed b = f; // Allowed e = g; // Allowed d = (int c = 10) => 2; // Warning: default parameter value is different between new lambda // and synthesized delegate b'. We won't do implicit conversion ``` Anonymous delegates with an array as the last parameter will be unified when the last parameter has the same `params` modifier and array type, regardless of parameter name. ```csharp int C(int[] xs) { return xs.Length; } int D(params int[] xs) { return xs.Length; } var a = (int[] xs) => xs.Length; // internal delegate int a'(int[] xs); var b = (params int[] xs) => xs.Length; // internal delegate int b'(params int[] xs); var c = C; // internal delegate int a'(int[] xs); var d = D; // internal delegate int b'(params int[] xs); a = b; // Not allowed a = c; // Allowed b = c; // Not allowed b = d; // Allowed c = (params int[] xs) => xs.Length; // Warning: different delegate types; no implicit conversion d = (int[] xs) => xs.Length; // OK. `d` is `delegate int (params int[] arg)` ``` Similarly, there is of course compatibility with named delegates that already support optional and `params` parameters. When default values or `params` modifiers differ in a conversion, the source one will be unused if it's in a lambda expression, since the lambda cannot be called in any other way. That might seem counter-intuitive to users, hence a warning will be emitted when the source default value or `params` modifier is present and different from the target one. If the source is a method group, it can be called on its own, hence no warning will be emitted. ```csharp delegate int DelegateNoDefault(int x); delegate int DelegateWithDefault(int x = 1); int MethodNoDefault(int x) => x; int MethodWithDefault(int x = 2) => x; DelegateNoDefault d1 = MethodWithDefault; // no warning: source is a method group DelegateWithDefault d2 = MethodWithDefault; // no warning: source is a method group DelegateWithDefault d3 = MethodNoDefault; // no warning: source is a method group DelegateNoDefault d4 = (int x = 1) => x; // warning: source present, target missing DelegateWithDefault d5 = (int x = 2) => x; // warning: source present, target different DelegateWithDefault d6 = (int x) => x; // no warning: source missing, target present delegate int DelegateNoParams(int[] xs); delegate int DelegateWithParams(params int[] xs); int MethodNoParams(int[] xs) => xs.Length; int MethodWithParams(params int[] xs) => xs.Length; DelegateNoParams d7 = MethodWithParams; // no warning: source is a method group DelegateWithParams d8 = MethodNoParams; // no warning: source is a method group DelegateNoParams d9 = (params int[] xs) => xs.Length; // warning: source present, target missing DelegateWithParams d10 = (int[] xs) => xs.Length; // no warning: source missing, target present ``` ### IL/runtime behavior The default parameter values will be emitted to metadata. The IL for this feature will be very similar in nature to the IL emitted for lambdas with `ref` and `out` parameters. A class which inherits from `System.Delegate` or similar will be generated, and the `Invoke` method will include `.param` directives to set default parameter values or `System.ParamArrayAttribute` – just as would be the case for a standard named delegate with optional or `params` parameters. These delegate types can be inspected at runtime, as normal. In code, users can introspect the `DefaultValue` in the `ParameterInfo` associated with the lambda or method group by using the associated `MethodInfo`. ```csharp var addWithDefault = (int addTo = 2) => addTo + 1; int AddWithDefaultMethod(int addTo = 2) { return addTo + 1; } var defaultParm = addWithDefault.Method.GetParameters()[0].DefaultValue; // 2 var add1 = AddWithDefaultMethod; defaultParm = add1.Method.GetParameters()[0].DefaultValue; // 2 ``` ## Open questions Neither of these have been implemented. They remain open proposals. **Open question:** how does this interact with the existing `DefaultParameterValue` attribute? **Proposed answer:** For parity, permit the `DefaultParameterValue` attribute on lambdas and ensure that the delegate generation behavior matches for default parameter values supported via the syntax. ```csharp var a = (int i = 13) => 1; // same as var b = ([DefaultParameterValue(13)] int i) => 1; b = a; // Allowed ``` **Open question:** First, note that this is outside the scope of the current proposal but it might be worth discussing in the future. Do we want to support defaults with implicitly typed lambda parameters? I.e., ```csharp delegate void M1(int i = 3); M1 m = (x = 3) => x + x; // Ok delegate void M2(long i = 2); M2 m = (x = 3.0) => ...; //Error: cannot convert implicitly from long to double ``` This inference leads to some tricky conversion issues which would require more discussion. There are also parsing performance considerations here. For instance, today the term `(x = ` could never be the start of a lambda expression. If this syntax was allowed for lambda defaults, then the parser would need a larger lookahead (scanning all the way until a `=>` token) in order to determine whether a term is a lambda or not. ## Design meetings - [LDM 2022-10-10](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-10.md#params-support-for-lambda-default-parameters): decision to add support for `params` in the same way as default parameter values. ================================================ FILE: proposals/csharp-12.0/primary-constructors.md ================================================ # Primary constructors [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Classes and structs can have a parameter list, and their base class specification can have an argument list. Primary constructor parameters are in scope throughout the class or struct declaration, and if they are captured by a function member or anonymous function, they are appropriately stored (e.g. as unspeakable private fields of the declared class or struct). The proposal "retcons" the primary constructors already available on records in terms of this more general feature with some additional members synthesized. ## Motivation [motivation]: #motivation The ability of a class or struct in C# to have more than one constructor provides for generality, but at the expense of some tedium in the declaration syntax, because the constructor input and the class state need to be cleanly separated. Primary constructors put the parameters of one constructor in scope for the whole class or struct to be used for initialization or directly as object state. The trade-off is that any other constructors must call through the primary constructor. ``` c# public class B(bool b) { } // base class public class C(bool b, int i, string s) : B(b) // b passed to base constructor { public int I { get; set; } = i; // i used for initialization public string S // s used directly in function members { get => s; set => s = value ?? throw new ArgumentNullException(nameof(S)); } public C(string s) : this(true, 0, s) { } // must call this(...) } ``` ## Detailed design [design]: #detailed-design This describes the generalized design across records and non-records, and then details how the existing primary constructors for records are specified by adding a set of synthesized members in the presence of a primary constructor. ### Syntax Class and struct declarations are augmented to allow a parameter list on the type name, an argument list on the base class, and a body consisting of just a `;`: ``` antlr class_declaration : attributes? class_modifier* 'partial'? class_designator identifier type_parameter_list? parameter_list? class_base? type_parameter_constraints_clause* class_body ; class_designator : 'record' 'class'? | 'class' class_base : ':' class_type argument_list? | ':' interface_type_list | ':' class_type argument_list? ',' interface_type_list ; class_body : '{' class_member_declaration* '}' ';'? | ';' ; struct_declaration : attributes? struct_modifier* 'partial'? 'record'? 'struct' identifier type_parameter_list? parameter_list? struct_interfaces? type_parameter_constraints_clause* struct_body ; struct_body : '{' struct_member_declaration* '}' ';'? | ';' ; interface_declaration : attributes? interface_modifier* 'partial'? 'interface' identifier variant_type_parameter_list? interface_base? type_parameter_constraints_clause* interface_body ; interface_body : '{' interface_member_declaration* '}' ';'? | ';' ; enum_declaration : attributes? enum_modifier* 'enum' identifier enum_base? enum_body ; enum_body : '{' enum_member_declarations? '}' ';'? | '{' enum_member_declarations ',' '}' ';'? | ';' ; ``` ***Note:*** These productions replace `record_declaration` in [Records](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/records.md#records) and `record_struct_declaration` in [Record structs](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/record-structs.md#record-structs), which both become obsolete. It is an error for a `class_base` to have an `argument_list` if the enclosing `class_declaration` does not contain a `parameter_list`. At most one partial type declaration of a partial class or struct may provide a `parameter_list`. The parameters in the `parameter_list` of a `record` declaration must all be value parameters. Note, according to this proposal `class_body`, `struct_body`, `interface_body` and `enum_body` are allowed to consist of just a `;`. A class or struct with a `parameter_list` has an implicit public constructor whose signature corresponds to the value parameters of the type declaration. This is called the ***primary constructor*** for the type, and causes the implicitly declared parameterless constructor, if present, to be suppressed. It is an error to have a primary constructor and a constructor with the same signature already present in the type declaration. ### Lookup The [lookup of simple names](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1284-simple-names) is augmented to handle primary constructor parameters. The changes are highlighted in **bold** in the following excerpt: > - Otherwise, for each instance type `T` ([§15.3.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1532-the-instance-type)), starting with the instance type of the immediately enclosing type declaration and continuing with the instance type of each enclosing class or struct declaration (if any): > - **If the declaration of `T` includes a primary constructor parameter `I` and the reference occurs within the `argument_list` of `T`'s `class_base` or within an initializer of a field, property or event of `T`, the result is the primary constructor parameter `I`** > - **Otherwise,** if `e` is zero and the declaration of `T` includes a type parameter with name `I`, then the *simple_name* refers to that type parameter. > - Otherwise, if a member lookup ([§12.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#125-member-lookup)) of `I` in `T` with `e` type arguments produces a match: > - If `T` is the instance type of the immediately enclosing class or struct type and the lookup identifies one or more methods, the result is a method group with an associated instance expression of `this`. If a type argument list was specified, it is used in calling a generic method ([§12.8.10.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128102-method-invocations)). > - Otherwise, if `T` is the instance type of the immediately enclosing class or struct type, if the lookup identifies an instance member, and if the reference occurs within the *block* of an instance constructor, an instance method, or an instance accessor ([§12.2.1](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1221-general)), the result is the same as a member access ([§12.8.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1287-member-access)) of the form `this.I`. This can only happen when `e` is zero. > - Otherwise, the result is the same as a member access ([§12.8.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1287-member-access)) of the form `T.I` or `T.I`. > - **Otherwise, if the declaration of `T` includes a primary constructor parameter `I`, the result is the primary constructor parameter `I`.** The first addition corresponds to the change incurred by [primary constructors on records](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/records.md#primary-constructor), and ensures that primary constructor parameters are found before any corresponding fields within initializers and base class arguments. It extends this rule to static initializers as well. However, since records always have an instance member with the same name as the parameter, the extension can only lead to a change in an error message. Illegal access to a parameter vs. illegal access to an instance member. The second addition allows primary constructor parameters to be found elsewhere within the type body, but only if not shadowed by members. It is an error to reference a primary constructor parameter if the reference does not occur within one of the following: - a `nameof` argument - an initializer of an instance field, property or event of the declaring type (type declaring primary constructor with the parameter). - the `argument_list` of `class_base` of the declaring type. - the body of an instance method (note that instance constructors are excluded) of the declaring type. - the body of an instance accessor of the declaring type. In other words, primary constructor parameters are in scope throughout the declaring type body. They shadow members of the declaring type within an initializer of a field, property or event of the declaring type, or within the `argument_list` of `class_base` of the declaring type. They are shadowed by members of the declaring type everywhere else. Thus, in the following declaration: ``` c# class C(int i) { protected int i = i; // references parameter public int I => i; // references field } ``` The initializer for the field `i` references the parameter `i`, whereas the body of the property `I` references the field `i`. #### Warn on shadowing by a member from base Compiler will produce a warning on usage of an identifier when a base member shadows a primary constructor parameter if that primary constructor parameter was not passed to the base type via its constructor. A primary constructor parameter is considered to be passed to the base type via its constructor when all the following conditions are true for an argument in *class_base*: - The argument represents an implicit or explicit identity conversion of a primary constructor parameter; - The argument is not part of an expanded `params` argument; ## Semantics A primary constructor leads to the generation of an instance constructor on the enclosing type with the given parameters. If the `class_base` has an argument list, the generated instance constructor will have a `base` initializer with the same argument list. Primary constructor parameters in class/struct declarations can be declared `ref`, `in` or `out`. Declaring `ref` or `out` parameters remains illegal in primary constructors of record declaration. All instance member initializers in the class body will become assignments in the generated constructor. If a primary constructor parameter is referenced from within an instance member, and the reference is not inside of a `nameof` argument, it is captured into the state of the enclosing type, so that it remains accessible after the termination of the constructor. A likely implementation strategy is via a private field using a mangled name. In a readonly struct the capture fields will be readonly. Therefore, access to captured parameters of a readonly struct will have similar restrictions as access to readonly fields. Access to captured parameters within a readonly member will have similar restrictions as access to instance fields in the same context. Capturing is not allowed for parameters that have ref-like type, and capturing is not allowed for `ref`, `in` or `out` parameters. This is similar to a limitation for capturing in lambdas. If a primary constructor parameter is only referenced from within instance member initializers, those can directly reference the parameter of the generated constructor, as they are executed as part of it. Primary Constructor will do the following sequence of operations: 1. Parameter values are stored in capture fields, if any. 2. Instance initializers are executed 3. Base constructor initializer is called Parameter references in any user code are replaced with corresponding capture field references. For instance this declaration: ``` c# public class C(bool b, int i, string s) : B(b) // b passed to base constructor { public int I { get; set; } = i; // i used for initialization public string S // s used directly in function members { get => s; set => s = value ?? throw new ArgumentNullException(nameof(value)); } public C(string s) : this(true, 0, s) { } // must call this(...) } ``` Generates code similar to the following: ``` c# public class C : B { public int I { get; set; } public string S { get => __s; set => __s = value ?? throw new ArgumentNullException(nameof(value)); } public C(string s) : this(0, s) { ... } // must call this(...) // generated members private string __s; // for capture of s public C(bool b, int i, string s) { __s = s; // capture s I = i; // run I's initializer B(b) // run B's constructor } } ``` It is an error for a non-primary constructor declaration to have the same parameter list as the primary constructor. All non-primary constructor declarations must use a `this` initializer, so that the primary constructor is ultimately called. Records produce a warning if a primary constructor parameter isn't read within the (possibly generated) instance initializers or base initializer. Similar warnings will be reported for primary constructor parameters in classes and structures: - for a by-value parameter, if the parameter is not captured and is not read within any instance initializers or base initializer. - for an `in` parameter, if the parameter is not read within any instance initializers or base initializer. - for a `ref` parameter, if the parameter is not read or written to within any instance initializers or base initializer. ### Identical simple names and type names There is a special language rule for scenarios often referred to as "Color Color" scenarios - [Identical simple names and type names](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12872-identical-simple-names-and-type-names). >In a member access of the form `E.I`, if `E` is a single identifier, and if the meaning of `E` as a *simple_name* ([§12.8.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1274-simple-names)) is a constant, field, property, local variable, or parameter with the same type as the meaning of `E` as a *type_name* ([§7.8.1](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#781-general)), then both possible meanings of `E` are permitted. The member lookup of `E.I` is never ambiguous, since `I` shall necessarily be a member of the type `E` in both cases. In other words, the rule simply permits access to the static members and nested types of `E` where a compile-time error would otherwise have occurred. With respect to primary constructors, the rule affects whether an identifier within an instance member should be treated as a type reference, or as a primary constructor parameter reference, which, in turn, captures the parameter into the the state of the enclosing type. Even though "the member lookup of `E.I` is never ambiguous", when lookup yields a member group, in some cases it is impossible to determine whether a member access refers to a static member or an instance member without fully resolving (binding) the member access. At the same time, capturing a primary constructor parameter changes properties of enclosing type in a way that affects semantic analysis. For example, the type might become unmanaged and fail certain constraints because of that. There are even scenarios for which binding can succeed either way, depending on whether the parameter is considered captured or not. For example: ``` C# struct S1(Color Color) { public void Test() { Color.M1(this); // Error: ambiguity between parameter and typename } } class Color { public void M1(T x, int y = 0) { System.Console.WriteLine("instance"); } public static void M1(T x) where T : unmanaged { System.Console.WriteLine("static"); } } ``` If we treat receiver ```Color``` as a value, we capture the parameter and 'S1' becomes managed. Then the static method becomes inapplicable due to the constraint and we would call instance method. However, if we treat the receiver as a type, we don't capture the parameter and 'S1' remains unmanaged, then both methods are applicable, but the static method is "better" because it doesn't have an optional parameter. Neither choice leads to an error, but each would result in distinct behavior. Given this, compiler will produce an ambiguity error for a member access `E.I` when all the following conditions are met: - Member lookup of `E.I` yields a member group containing instance and static members at the same time. Extension methods applicable to the receiver type are treated as instance methods for the purpose of this check. - If `E` is treated as a simple name, rather than a type name, it would refer to a primary constructor parameter and would capture the parameter into the state of the enclosing type. ### Double storage warnings If a primary constructor parameter is passed to the base and *also* captured, there's a high risk that it is inadvertently stored twice in the object. Compiler will produce a warning for `in` or by value argument in a `class_base` `argument_list` when all the following conditions are true: - The argument represents an implicit or explicit identity conversion of a primary constructor parameter; - The argument is not part of an expanded `params` argument; - The primary constructor parameter is captured into the state of the enclosing type. Compiler will produce a warning for a `variable_initializer` when all the following conditions are true: - The variable initializer represents an implicit or explicit identity conversion of a primary constructor parameter; - The primary constructor parameter is captured into the state of the enclosing type. For example: ``` c# public class Person(string name) { public string Name { get; set; } = name; // warning: initialization public override string ToString() => name; // capture } ``` ## Attributes targeting primary constructors At https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-13.md we decided to embrace the https://github.com/dotnet/csharplang/issues/7047 proposal. The "method" attribute target is allowed on a *class_declaration*/*struct_declaration* with *parameter_list* and results in the corresponding primary constructor having that attribute. Attributes with the `method` target on a *class_declaration*/*struct_declaration* without *parameter_list* are are ignored with a warning. ``` C# [method: FooAttr] // Good public partial record Rec( [property: Foo] int X, [field: NonSerialized] int Y ); [method: BarAttr] // warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. public partial record Rec { public void Frobnicate() { ... } } [method: Attr] // Good public record MyUnit1(); [method: Attr] // warning CS0657: 'method' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'type'. All attributes in this block will be ignored. public record MyUnit2; ``` ## Primary constructors on records With this proposal, records no longer need to separately specify a primary constructor mechanism. Instead, record (class and struct) declarations that have primary constructors would follow the general rules, with these simple additions: - For each primary constructor parameter, if a member with the same name already exists, it must be an instance property or field. If not, a public init-only auto-property of the same name is synthesized with a property initializer assigning from the parameter. - A deconstructor is synthesized with out parameters to match the primary constructor parameters. - If an explicit constructor declaration is a "copy constructor" - a constructor that takes a single parameter of the enclosing type - it is not required to call a `this` initializer, and will not execute the member initializers present in the record declaration. ## Drawbacks [drawbacks]: #drawbacks * The allocation size of constructed objects is less obvious, as the compiler determines whether to allocate a field for a primary constructor parameter based on the full text of the class. This risk is similar to the implicit capture of variables by lambda expressions. * A common temptation (or accidental pattern) might be to capture the "same" parameter at multiple levels of inheritance as it is passed up the constructor chain instead of explicitly allotting it a protected field at the base class, leading to duplicated allocations for the same data in objects. This is very similar to today's risk of overriding auto-properties with auto-properties. * As proposed here, there is no place for additional logic that might usually be expressed in constructor bodies. The "primary constructor bodies" extension below addresses that. * As proposed, execution order semantics are subtly different from within ordinary constructors, delaying member initializers to after the base calls. This could probably be remedied, but at the cost of some of the extension proposals (notably "primary constructor bodies"). * The proposal only works for scenarios where a single constructor can be designated primary. * There is no way to express separate accessibility of the class and the primary constructor. An example is when public constructors all delegate to one private "build-it-all" constructor. If necessary, syntax could be proposed for that later. ## Alternatives [alternatives]: #alternatives ### No capture A much simpler version of the feature would prohibit primary constructor parameters from occurring in member bodies. Referencing them would be an error. Fields would have to be explicitly declared if storage is desired beyond the initialization code. ``` c# public class C(string s) { public string S1 => s; // Nope! public string S2 { get; } = s; // Still allowed } ``` This could still be evolved to the full proposal at a later time, and would avoid a number of decisions and complexities, at the cost of removing less boilerplate initially, and probably also seeming unintuitive. ### Explicit generated fields An alternative approach is for primary constructor parameters to always and visibly generate a field of the same name. Instead of closing over the parameters in the same manner as local and anonymous functions, there would explicitly be a generated member declaration, similar to the public properties generated for primary construcor parameters in records. Just like for records, if a suitable member already exists, one would not be generated. If the generated field is private it could still be elided when it is not used as a field in member bodies. In classes, however, a private field would often not be the right choice, because of the state duplication it could cause in derived classes. An option here would be to instead generating a protected field in classes, encouraging reuse of storage across inheritance layers. However, then we would not be able to elide the declaration, and would incur allocation cost for every primary constructor parameter. This would align non-record primary constructors more closely with record ones, in that members are always (at least conceptually) generated, albeit different kinds of members with different accessibilities. But it would also lead to surprising differences from how parameters and locals are captured elsewhere in C#. If we were ever to allow local classes, for example, they would capture enclosing parameters and locals implicitly. Visibly generating shadowing fields for them would not seem to be a reasonable behavior. Another problem often raised with this approach is that many developers have different naming conventions for parameters and fields. Which should be used for the primary constructor parameter? Either choice would lead to inconsistency with the rest of the code. Finally, visibly generating member declarations is really the name of the game for records, but much more surprising and "out of character" for non-record classes and structs. All in all, those are the reasons why the main proposal opts for implicit capture, with sensible behavior (consistent with records) for explicit member declarations when they are desired. ### Remove instance members from initializer scope The lookup rules above are intended to allow for the current behavior of primary constructor parameters in records when a corresponding member is manually declared, and to explain the behavior of the generated member when it is not. This requires lookup to differ between "initialization scope" (this/base initializers, member initializers) and "body scope" (member bodies), which the above proposal achieves by changing *when* primary constructor parameters are looked for, depending on where the reference occurs. An observation is that referencing an instance member with a simple name in initializer scope always leads to an error. Instead of merely shadowing instance members in those places, could we simply take them out of scope? That way, there wouldn't be this weird conditional ordering of scopes. This alternative is probably possible, but it would have some consequences that are somewhat far-reaching and potentially undesirable. First of all, if we remove instance members from initializer scope then a simple name that *does* correspond to an instance member and *not* to a primary constructor parameter could accidentally bind to something outside of the type declaration! This seems like it would rarely be intentional, and an error would be better. Furthermore, *static* members are fine to reference in initialization scope. So we would have to distinguish between static and instance members in lookup, something we don't do today. (We do distinguish in overload resolution but that is not in play here). So that would have to also be changed, leading to yet more situations where e.g. in static contexts something would bind "further out" rather than error because it found an instance member. All in all this "simplification" would lead to quite a downstream complication that no-one asked for. ## Possible extensions [extensions]: #possible-extensions These are variations or additions to the core proposal that may be considered in conjunction with it, or at a later stage if deemed useful. ### Primary constructor parameter access within constructors The rules above make it an error to reference a primary constructor parameter within another constructor. This could be allowed within the *body* of other constructors, though, since the primary constructor runs first. However it would need to remain disallowed within the argument list of the `this` initializer. ``` c# public class C(bool b, int i, string s) : B(b) { public C(string s) : this(b, s) // b still disallowed { i++; // could be allowed } } ``` Such access would still incur capture, as that would be the only way the constructor body could get at the variable after the primary constructor has already run. The prohibition on primary constructor parameters in the this-initializer's arguments could be weakened to allow them, but make them not definitely assigned, but that does not seem useful. ### Allow constructors without a `this` initializer Constructors without a `this` initializer (i.e. with an implicit or explicit `base` initializer) could be allowed. Such a constructor would *not* run instance field, property and event initializers, as those would be considered to be part of the primary constructor only. In the presence of such base-calling constructors, there are a couple of options for how primary constructor parameter capture is handled. The simplest is to completely disallow capture in this situation. Primary constructor parameters would be for initialization only when such constructors exist. Alternatively, if combined with the previously described option to allow access to primary constructor parameters within constructors, the parameters could enter the constructor body as not definitely assigned, and ones that are captured would need to be definitely assigned by the end of the constructor body. They would essentially be implicit out parameters. That way, captured primary constructor parameters would always have a sensible (i.e. explicitly assigned) value by the time they are consumed by other function members. An attraction of this extension (in either form) is that it fully generalizes the current exemption for "copy constructors" in records, without leading to situations where uninitialized primary constructor parameters are observed. Essentially, constructors that initialize the object in alternative ways are fine. The capture-related restrictions would not be a breaking change for existing manually defined copy constructors in records, because records never capture their primary constructor parameters (they generate fields instead). ``` c# public class C(bool b, int i, string s) : B(b) { public int I { get; set; } = i; // i used for initialization public string S // s used directly in function members { get => s; set => s = value ?? throw new ArgumentNullException(nameof(value)); } public C(string s2) : base(true) // cannot use `string s` because it would shadow { s = s2; // must initialize s because it is captured by S } protected C(C original) : base(original) // copy constructor { this.s = original.s; // assignment to b and i not required because not captured } } ``` ### Primary constructor bodies Constructors themselves often contain parameter validation logic or other nontrivial initialization code that cannot be expressed as initializers. Primary constructors could be extended to allow statement blocks to appear directly in the class body. Those statements would be inserted in the generated constructor at the point where they appear between initializing assignments, and would thus be executed interspersed with initializers. For instance: ``` c# public class C(int i, string s) : B(s) { { if (i < 0) throw new ArgumentOutOfRangeException(nameof(i)); } int[] a = new int[i]; public int S => s; } ``` A lot of this scenario might be adequately be covered if we were to introduce "final initializers" which run after the constructors *and* any object/collection initializers have completed. However, argument validation is one thing that would ideally happen as early as possible. Primary constructor bodies could also provide a place for allowing an access modifier for the primary constructor, allowing it to deviate from the accessibility of the enclosing type. ### Combined parameter and member declarations A possible and often mentioned addition could be to allow primary constructor parameters to be annotated so that they would *also* declare a member on the type. Most commonly it is proposed to allow an access specifier on the parameters to trigger the member generation: ``` c# public class C(bool b, protected int i, string s) : B(b) // i is a field as well as a parameter { void M() { ... i ... // refers to the field i ... s ... // closes over the parameter s } } ``` There are some problems: - What if a property is desired, not a field? Having `{ get; set; }` syntax inline in a parameter list does not seem appetizing. - What if different naming conventions are used for parameters and fields? Then this feature would be useless. This is a potential future addition that can be adopted or not. The current proposal leaves the possibility open. ## Open questions ### Lookup order for type parameters The https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/primary-constructors.md#lookup section specifies that type parameters of declaring type should come before type's primary constructor parameters in every context where those parameters are in scope. However, we already have existing behavior with records - primary constructor parameters come before type parameters in base initializer and field initializers. What should we do about this discrepancy? - Adjust the rules to match the behavior. - Adjust the behavior (a possible breaking change). - Disallow a primiry constructor parameter to use type parameter's name (a possible breaking change). - Do nothing, accept the inconsistency between the spec and implementation. #### Conclusion: Adjust the rules to match the behavior (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-25.md#primary-constructors). ### Field targeting attributes for captured primary constructor parameters Should we allow field targeting attributes for captured primary constructor parameters? ``` C# class C1([field: Test] int x) // Parameter is captured, the attribute goes to the capture field { public int X => x; } class C2([field: Test] int x) // Parameter is not captured, the attribute is ignored with a warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored. { public int X = x; } ``` Right now the attributes are ignored with the warning regardless of whether the parameter is captured. Note that for records, field targeted attributes are allowed when a property is synthesized for it. The attributes go on the backing field then. ``` C# record R1([field: Test]int X); // Ok, the attribute goes on the backing field record R2([field: Test]int X) // warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'param'. All attributes in this block will be ignored. { public int X = X; } ``` #### Conclusion: Not allowed (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#attributes-on-captured-parameters). ### Warn on shadowing by a member from base Should we report a warning when a member from base is shadowing a primary constructor parameter inside a member (see https://github.com/dotnet/csharplang/discussions/7109#discussioncomment-5666621)? #### Conclusion: An alternative design is approved - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-08.md#primary-constructors ### Capturing instance of the enclosing type in a closure When a parameter captured into the state of the enclosing type is also referenced in a lambda inside an instance initializer or a base initializer, the lambda and the state of the enclosing type should refer to the same location for the parameter. For example: ``` C# partial class C1 { public System.Func F1 = Execute1(() => p1++); } partial class C1 (int p1) { public int M1() { return p1++; } static System.Func Execute1(System.Func f) { _ = f(); return f; } } ``` Since naive implementation of capturing a parameter into the state of the type simply captures the parameter in a private instance field, the lambda needs to refer to the same field. As a result, it needs to be able to access the instance of the type. This requires capturing `this` into a closure before the base constructor is invoked. That, in turn, results in safe, but an unverifiable IL. Is this acceptable? Alternatively we could: - Disallow lambdas like that; - Or, instead, capture parameters like that in an instance of a separate class (yet another closure), and share that instance between the closure and the instance of the enclosing type. Thus eliminating the need to capture `this` in a closure. #### Conclusion: We are comfortable with capturing `this` into a closure before the base constructor is invoked (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md). The runtime team didn't find the IL pattern problematic as well. ### Assigning to `this` within a struct C# allows to assign to `this` within a struct. If the struct captures a primary constructor parameter, the assignment is going to overwrite its value, which might be not obvious to the user. Do we want to report a warning for assignments like this? ``` C# struct S(int x) { int X => x; void M(S s) { this = s; // 'x' is overwritten } } ``` #### Conclusion: Allowed, no warning (https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md). ### Double storage warning for initialization plus capture We have a warning if a primary constructor parameter is passed to the base and *also* captured, because there's a high risk that it is inadvertently stored twice in the object. It seems that there's a similar risk if a parameter is used to initialize a member, and is also captured. Here's a small example: ``` c# public class Person(string name) { public string Name { get; set; } = name; // initialization public override string ToString() => name; // capture } ``` For a given instance of `Person`, changes to `Name` would not be reflected in the output of `ToString`, which is probably unintended on the developer's part. Should we introduce a double storage warning for this situation? This is how it would work: The compiler will produce a warning for a `variable_initializer` when all the following conditions are true: - The variable initializer represents an implicit or explicit identity conversion of a primary constructor parameter; - The primary constructor parameter is captured into the state of the enclosing type. #### Conclusion: Approved, see https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-15.md#primary-constructors ## LDM meetings - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-17.md - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-01-18.md - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-15.md - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-02-22.md - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-03-13.md - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#primary-constructors - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-08.md#primary-constructors - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-15.md#primary-constructors - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-09-25.md#primary-constructors ================================================ FILE: proposals/csharp-12.0/ref-readonly-parameters.md ================================================ # `ref readonly` parameters [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Allow parameter declaration-site modifier `ref readonly` and change callsite rules as follows: | Callsite annotation | `ref` parameter | `ref readonly` parameter | `in` parameter | `out` parameter | |----------------------|-----------------|--------------------------|----------------|-----------------| | `ref` | Allowed | **Allowed** | **Warning** | Error | | `in` | Error | **Allowed** | Allowed | Error | | `out` | Error | **Error** | Error | Allowed | | No annotation | Error | **Warning** | Allowed | Error | (Note that there is one change to the existing rules: `in` parameter with `ref` callsite annotation produces a warning instead of an error.) Change argument value rules as follows: | Value kind | `ref` parameter | `ref readonly` parameter | `in` parameter | `out` parameter | |------------|-----------------|--------------------------|----------------|-----------------| | rvalue | Error | **Warning** | Allowed | Error | | lvalue | Allowed | **Allowed** | Allowed | Allowed | Where lvalue means a variable (i.e., a value with a location; does not have to be writable/assignable) and rvalue means any kind of value. ## Motivation [motivation]: #motivation C# 7.2 [introduced `in` parameters](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/proposals/csharp-7.2/readonly-ref.md#solution-in-parameters) as a way to pass readonly references. `in` parameters allow both lvalues and rvalues and can be used without any annotation at the callsite. However, APIs which capture or return references from their parameters would like to disallow rvalues and also enforce some indication at the callsite that a reference is being captured. `ref readonly` parameters are ideal in such cases as they warn if used with rvalues or without any annotation at the callsite. Furthermore, there are APIs that need only read-only references but use - `ref` parameters since they were introduced before `in` became available and changing to `in` would be a source and binary breaking change, e.g., `QueryInterface`, or - `in` parameters to accept readonly references even though passing rvalues to them doesn't really make sense, e.g., `ReadOnlySpan..ctor(in T value)`, or - `ref` parameters to disallow rvalues even though they don't mutate the passed reference, e.g., `Unsafe.IsNullRef`. These APIs could migrate to `ref readonly` parameters without breaking users. For details on binary compatibility, see the proposed [metadata encoding][metadata]. Specifically, changing - `ref` → `ref readonly` would only be a binary breaking change for virtual methods, - `ref` → `in` would also be a binary breaking change for virtual methods, but not a source breaking change (because the rules change to only warn for `ref` arguments passed to `in` parameters), - `in` → `ref readonly` would not be a breaking change (but no callsite annotation or rvalue would result in a warning), - note that this would be a source breaking change for users using older compiler versions (as they interpret `ref readonly` parameters as `ref` parameters, disallowing `in` or no annotation at the callsite) and new compiler versions with `LangVersion <= 11` (for consistency with older compiler versions, an error will be emitted that `ref readonly` parameters are not supported unless the corresponding arguments are passed with the `ref` modifier). In the opposite direction, changing - `ref readonly` → `ref` would be potentially a source breaking change (unless only `ref` callsite annotation was used and only readonly references used as arguments), and a binary breaking change for virtual methods, - `ref readonly` → `in` would not be a breaking change (but `ref` callsite annotation would result in a warning). Note that the rules outlined above apply to method signatures, but not to delegate signatures. For example, changing `ref` to `in` in a delegate signature can be a source breaking change (if a user is assigning a method with `ref` parameter to that delegate type, it would become an error after the API change). ## Detailed design [design]: #detailed-design In general, rules for `ref readonly` parameters are the same as specified for `in` parameters in [their proposal](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/proposals/csharp-7.2/readonly-ref.md), except where explicitly changed in this proposal. ### Parameter declarations [declarations]: #parameter-declarations No changes in grammar are necessary. The modifier `ref readonly` will be allowed for parameters. Apart from normal methods, `ref readonly` will be allowed for indexer parameters (like `in` but unlike `ref`), but disallowed for operator parameters (like `ref` but unlike `in`). Default parameter values will be allowed for `ref readonly` parameters with a warning since they are equivalent to passing rvalues. This allows API authors to change `in` parameters with default values to `ref readonly` parameters without introducing a source breaking change. ### Value kind checks [value-kind-checks]: #value-kind-checks Note that even though `ref` argument modifier is allowed for `ref readonly` parameters, nothing changes w.r.t. value kind checks, i.e., - `ref` can only be used with assignable values; - to pass readonly references, one has to use the `in` argument modifier instead; - to pass rvalues, one has to use no modifier (which results in a warning for `ref readonly` parameters as described in [the summary of this proposal][summary]). ### Overload resolution [overload-resolution]: #overload-resolution Overload resolution will allow mixing `ref`/`ref readonly`/`in`/no callsite annotations and parameter modifiers as denoted by the table in [the summary of this proposal][summary], i.e., all *allowed* and *warning* cases will be considered as possible candidates during overload resolution. Specifically, there's a change in existing behavior where methods with `in` parameter will match calls with the corresponding argument marked as `ref`—this change will be gated on LangVersion. However, the warning for passing an argument with no callsite modifier to a `ref readonly` parameter will be suppressed if the parameter is - the receiver in an extension method invocation, - used implicitly as part of custom collection initializer or interpolated string handler. By-value overloads will be preferred over `ref readonly` overloads in case there is no argument modifier (`in` parameters have the same behavior). #### Method conversions [method-conversions]: #method-conversions Similarly, for the purpose of anonymous function [[§10.7](https://github.com/dotnet/csharpstandard/blob/47912d4fdae2bb8c3750e6485bdc6509560ec6bf/standard/conversions.md#107-anonymous-function-conversions)] and method group [[§10.8](https://github.com/dotnet/csharpstandard/blob/47912d4fdae2bb8c3750e6485bdc6509560ec6bf/standard/conversions.md#108-method-group-conversions)] conversions, these modifiers are considered compatible (but any allowed conversion between different modifiers results in a warning): - `ref readonly` parameter of the target method is allowed to match `in` or `ref` parameter of the delegate, - `in` parameter of the target method is allowed to match `ref readonly` or, gated on LangVersion, `ref` parameter of the delegate. - Note: `ref` parameter of the target method is *not* allowed to match `in` nor `ref readonly` parameter of the delegate. For example: ```cs DIn dIn = (ref int p) => { }; // error: cannot match `ref` to `in` DRef dRef = (in int p) => { }; // warning: mismatch between `in` and `ref` DRR dRR = (ref int p) => { }; // error: cannot match `ref` to `ref readonly` dRR = (in int p) => { }; // warning: mismatch between `in` and `ref readonly` dIn = (ref readonly int p) => { }; // warning: mismatch between `ref readonly` and `in` dRef = (ref readonly int p) => { }; // warning: mismatch between `ref readonly` and `ref` delegate void DIn(in int p); delegate void DRef(ref int p); delegate void DRR(ref readonly int p); ``` Note that there is no change in behavior of [function pointer conversions](https://github.com/dotnet/csharplang/blob/4b17ebb49654d21d4e96f415339c15c9f8a9ccde/proposals/csharp-9.0/function-pointers.md#function-pointer-conversions). As a reminder, implicit function pointer conversions are disallowed if there is a mismatch between reference kind modifiers, and explicit casts are always allowed without any warnings. ### Signature matching [signature-matching]: #signature-matching Members declared in a single type cannot differ in signature solely by `ref`/`out`/`in`/`ref readonly`. For other purposes of signature matching (e.g., hiding or overriding), `ref readonly` can be interchanged with `in` modifier, but that results in a warning at the declaration site [[§7.6](https://github.com/dotnet/csharpstandard/blob/47912d4fdae2bb8c3750e6485bdc6509560ec6bf/standard/basic-concepts.md#76-signatures-and-overloading)]. This doesn't apply when matching `partial` declaration with its implementation and when matching interceptor signature with intercepted signature. Note that there is no change in overriding for `ref`/`in` and `ref readonly`/`ref` modifier pairs, they cannot be interchanged, because the signatures aren't binary compatible. For consistency, the same is true for other signature matching purposes (e.g., hiding). ### Metadata encoding [metadata]: #metadata-encoding As a reminder, - `ref` parameters are emitted as plain byref types (`T&` in IL), - `in` parameters are like `ref` plus they are annotated with `System.Runtime.CompilerServices.IsReadOnlyAttribute`. In C# 7.3 and later, they are also emitted with `[in]` and if virtual, `modreq(System.Runtime.InteropServices.InAttribute)`. `ref readonly` parameters will be emitted as `[in] T&`, plus annotated with the following attribute: ```cs namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] public sealed class RequiresLocationAttribute : Attribute { } } ``` Furthermore, if virtual, they will be emitted with `modreq(System.Runtime.InteropServices.InAttribute)` to ensure binary compatibility with `in` parameters. Note that unlike `in` parameters, no `[IsReadOnly]` will be emitted for `ref readonly` parameters to avoid increasing metadata size and also to make older compiler versions interpret `ref readonly` parameters as `ref` parameters (and hence `ref` → `ref readonly` won't be a source breaking change even between different compiler versions). The `RequiresLocationAttribute` will be matched by namespace-qualified name and synthesized by the compiler if not already included in the compilation. Specifying the attribute in source will be an error if it's applied to a parameter, similarly to `ParamArrayAttribute`. #### Function pointers [funcptrs]: #function-pointers In function pointers, `in` parameters are emitted with `modreq(System.Runtime.InteropServices.InAttribute)` (see [function pointers proposal](https://github.com/dotnet/csharplang/blob/0376b4cc500b1370da86d26be634c9acf9d60b71/proposals/csharp-9.0/function-pointers.md#metadata-representation-of-in-out-and-ref-readonly-parameters-and-return-types)). `ref readonly` parameters will be emitted without that `modreq`, but instead with `modopt(System.Runtime.CompilerServices.RequiresLocationAttribute)`. Older compiler versions will ignore the `modopt` and hence interpret `ref readonly` parameters as `ref` parameters (consistent with older compiler behavior for normal methods with `ref readonly` parameters as described above) and new compiler versions aware of the `modopt` will use it to recognize `ref readonly` parameters to emit warnings during [conversions][method-conversions] and [invocations][overload-resolution]. For consistency with older compiler versions, new compiler versions with `LangVersion <= 11` will report errors that `ref readonly` parameters are not supported unless the corresponding arguments are passed with the `ref` modifier. Note that it is a binary break to change modifiers in function pointer signatures if they are part of public APIs, hence it will be a binary break when changing `ref` or `in` to `ref readonly`. However, a source break will only occur for callers with `LangVersion <= 11` when changing `in` → `ref readonly` (if invoking the pointer with `in` callsite modifier), consistent with normal methods. ## Breaking changes [breaking-changes]: #breaking-changes The `ref`/`in` mismatch relaxation in overload resolution introduces a behavior breaking change demonstrated in the following example: ```cs class C { string M(in int i) => "C"; static void Main() { int i = 5; System.Console.Write(new C().M(ref i)); } } static class E { public static string M(this C c, ref int i) => "E"; } ``` In C# 11, the call binds to `E.M`, hence `"E"` is printed. In C# 12, `C.M` is allowed to bind (with a warning) and no extension scopes are searched since we have an applicable candidate, hence `"C"` is printed. There is also a source breaking change due to the same reason. The example below prints `"1"` in C# 11, but fails to compile with an ambiguity error in C# 12: ```cs var i = 5; System.Console.Write(C.M(null, ref i)); interface I1 { } interface I2 { } static class C { public static string M(I1 o, ref int x) => "1"; public static string M(I2 o, in int x) => "2"; } ``` The examples above demonstrate the breaks for method invocations, but since they are caused by overload resolution changes, they can be similarly triggered for method conversions. ## Alternatives [alternatives]: #alternatives #### [Parameter declarations][declarations] API authors could annotate `in` parameters designed to accept only lvalues with a custom attribute and provide an analyzer to flag incorrect usages. This would not allow API authors to change signatures of existing APIs that opted to use `ref` parameters to disallow rvalues. Callers of such APIs would still need to perform extra work to get a `ref` if they have access only to a `ref readonly` variable. Changing these APIs from `ref` to `[RequiresLocation] in` would be a source breaking change (and in case of virtual methods, also a binary breaking change). Instead of allowing the modifier `ref readonly`, the compiler could recognize when a special attribute (like `[RequiresLocation]`) is applied to a parameter. This was discussed in [LDM 2022-04-25](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/meetings/2022/LDM-2022-04-25.md#ref-readonly-method-parameters), deciding this is a language feature, not an analyzer, so it should look like one. #### [Value kind checks][value-kind-checks] Passing lvalues without any modifiers to `ref readonly` parameters could be permitted without any warnings, similarly to C++'s implicit byref parameters. This was discussed in [LDM 2022-05-11](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/meetings/2022/LDM-2022-05-11.md#ref-readonly-method-parameters), noting that the primary motivation for `ref readonly` parameters are APIs which capture or return references from these parameters, so marker of some kind is a good thing. Passing rvalue to a `ref readonly` could be an error, not a warning. That was initially accepted in [LDM 2022-04-25](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/meetings/2022/LDM-2022-04-25.md#ref-readonly-method-parameters), but later e-mail discussions relaxed this because we would lose the ability to change existing APIs without breaking users. `in` could be the "natural" callsite modifier for `ref readonly` parameters and using `ref` could result in warnings. This would ensure a consistent code style and make it obvious at the callsite that the reference is readonly (unlike `ref`). It was initially accepted in [LDM 2022-04-25](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/meetings/2022/LDM-2022-04-25.md#ref-readonly-method-parameters). However, warnings could be a friction point for API authors to move from `ref` to `ref readonly`. Also, `in` has been redefined as `ref readonly` + convenience features, hence this was rejected in [LDM 2022-05-11](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/meetings/2022/LDM-2022-05-11.md#ref-readonly-method-parameters). ### Pending LDM review [to-review]: #pending-ldm-review None of the following options were implemented in C# 12. They remains potential proposals. #### [Parameter declarations][declarations] Inverse ordering of modifiers (`readonly ref` instead of `ref readonly`) could be allowed. This would be inconsistent with how `readonly ref` returns and fields behave (inverse ordering is disallowed or means something different, respectively) and could clash with readonly parameters if implemented in the future. Default parameter values could be an error for `ref readonly` parameters. #### [Value kind checks][value-kind-checks] Errors could be emitted instead of warnings when passing rvalues to `ref readonly` parameters or mismatching callsite annotations and parameter modifiers. Similarly, special `modreq` could be used instead of an attribute to ensure `ref readonly` parameters are distinct from `in` parameters on the binary level. This would provide stronger guarantees, so it would be good for new APIs, but prevent adoption in existing runtime APIs which cannot introduce breaking changes. Value kind checks could be relaxed to allow passing readonly references via `ref` into `in`/`ref readonly` parameters. That would be similar to how ref assignments and ref returns work today—they also allow passing references as readonly via the `ref` modifier on the source expression. However, the `ref` there is usually close to the place where the target is declared as `ref readonly`, so it is clear we are passing a reference as readonly, unlike invocations whose argument and parameter modifiers are usually far apart. Furthermore, they allow *only* the `ref` modifier unlike arguments which allow also `in`, hence `in` and `ref` would become interchangeable for arguments, or `in` would become practically obsolete if users wanted to make their code consistent (they would probably use `ref` everywhere since it's the only modifier allowed for ref assignments and ref returns). #### [Overload resolution][overload-resolution] Overload resolution, overriding, and conversion could disallow interchangeability of `ref readonly` and `in` modifiers. The overload resolution change for existing `in` parameters could be taken unconditionally (not considering LangVersion), but that would be a breaking change. Invoking an extension method with `ref readonly` receiver could result in warning "Argument 1 should be passed with `ref` or `in` keyword" as would happen for non-extension invocations with no callsite modifiers (user could fix such warning by turning the extension method invocation into static method invocation). The same warning could be reported when using custom collection initializer or interpolated string handler with `ref readonly` parameter, although user could not work around it. `ref readonly` overloads could be preferred over by-value overloads when there is no callsite modifier or there could be an ambiguity error. #### [Method conversions][method-conversions] We could allow `ref` parameter of the target method to match `in` and `ref readonly` parameter of the delegate. This would enable API authors to change for example `ref` to `in` in delegate signatures without breaking their users (consistently with what is allowed for normal method signatures). However, it would also result in the following violation of `readonly` guarantees with just a warning: ```cs class Program { static readonly int f = 123; static void Main() { var d = (in int x) => { }; d = (ref int x) => { x = 42; }; // warning: mismatch between `ref` and `in` d(f); // changes value of `f` even though it is `readonly`! System.Console.WriteLine(f); // prints 42 } } ``` Function pointer conversions could warn on `ref readonly`/`ref`/`in` mismatch, but if we wanted to gate that on LangVersion, a significant implementation investment would be required as today type conversions do not need access to compilation. Furthermore, even though mismatch is currently an error, it is easy for users to add a cast to allow the mismatch if they want. #### [Metadata encoding][metadata] Specifying the `RequiresLocationAttribute` in source could be allowed, similarly to `In` and `Out` attributes. Alternatively, it could be an error when applied in other contexts than just parameters, similarly to `IsReadOnly` attribute; to preserve further design space. Function pointer `ref readonly` parameters could be emitted with different `modopt`/`modreq` combinations (note that "source break" in this table means for callers with `LangVersion <= 11`): | Modifiers | Can be recognized across compilations | Old compilers see them as | `ref` → `ref readonly` | `in` → `ref readonly` | |---------------------------------------|---------------------------------------|---------------------------|------------------------|-----------------------| | `modreq(In) modopt(RequiresLocation)` | yes | `in` | binary, source break | binary break | | `modreq(In)` | no | `in` | binary, source break | ok | | `modreq(RequiresLocation)` | yes | unsupported | binary, source break | binary, source break | | `modopt(RequiresLocation)` | yes | `ref` | binary break | binary, source break | We could emit both `[RequiresLocation]` and `[IsReadOnly]` attributes for `ref readonly` parameters. Then `in` → `ref readonly` would not be a breaking change even for older compiler versions, but `ref` → `ref readonly` would become a source breaking change for older compiler versions (as they would interpret `ref readonly` as `in`, disallowing `ref` modifiers) and new compiler versions with `LangVersion <= 11` (for consistency). We could make the behavior for `LangVersion <= 11` different from the behavior for older compiler versions. For example, it could be an error whenever a `ref readonly` parameter is called (even when using the `ref` modifier at the callsite), or it could be always allowed without any errors. #### [Breaking changes][breaking-changes] This proposal suggests accepting a behavior breaking change because it should be rare to hit, is gated by LangVersion, and users can work around it by calling the extension method explicitly. Instead, we could mitigate it by - disallowing the `ref`/`in` mismatch (that would only prevent migration to `in` for old APIs that used `ref` because `in` wasn't available yet), - modifying the overload resolution rules to continue looking for a better match (determined by betterness rules specified below) when there's a ref kind mismatch introduced in this proposal, - or alternatively continue only for `ref` vs. `in` mismatch, not the others (`ref readonly` vs. `ref`/`in`/by-value). ##### Betterness rules The following example currently results in three ambiguity errors for the three invocations of `M`. We could add new betterness rules to resolve the ambiguities. This would also resolve the source breaking change described earlier. One way would be to make the example print `221` (where `ref readonly` parameter is matched with `in` argument since it would be a warning to call it with no modifier whereas for `in` parameter that's allowed). ```cs interface I1 { } interface I2 { } class C { static string M(I1 o, in int i) => "1"; static string M(I2 o, ref readonly int i) => "2"; static void Main() { int i = 5; System.Console.Write(M(null, ref i)); System.Console.Write(M(null, in i)); System.Console.Write(M(null, i)); } } ``` New betterness rules could mark as worse the parameter whose argument could have been passed with a different argument modifier to make it better. In other words, user should be always able to turn a worse parameter into a better parameter by changing its corresponding argument modifier. For example, when an argument is passed by `in`, a `ref readonly` parameter is preferred over an `in` parameter because user could pass the argument by-value to choose the `in` parameter. This rule is just an extension of by-value/`in` preference rule that is in effect today (it's the last overload resolution rule and the whole overload is better if any of its parameter is better and none is worse than the corresponding parameter of another overload). | argument | better parameter | worse parameter | |-------------|------------------|---------------------| | `ref`/`in` | `ref readonly` | `in` | | `ref` | `ref` | `ref readonly`/`in` | | by-value | by-value/`in` | `ref readonly` | | `in` | `in` | `ref` | We should handle method conversions similarly. The following example currently results in two ambiguity errors for the two delegate assignments. New betterness rules could prefer a method parameter whose refness modifier matches the corresponding target delegate parameter refness modifier over one that has a mismatch. Hence, the following example would print `12`. ```cs class C { void M(I1 o, ref readonly int x) => System.Console.Write("1"); void M(I2 o, ref int x) => System.Console.Write("2"); void Run() { D1 m1 = this.M; D2 m2 = this.M; // currently ambiguous var i = 5; m1(null, in i); m2(null, ref i); } static void Main() => new C().Run(); } interface I1 { } interface I2 { } class X : I1, I2 { } delegate void D1(X s, ref readonly int x); delegate void D2(X s, ref int x); ``` ## Design meetings - [LDM 2022-04-25](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/meetings/2022/LDM-2022-04-25.md#ref-readonly-method-parameters): feature accepted - [LDM 2022-05-09](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/meetings/2022/LDM-2022-05-09.md#ref-readonly-parameters): discussion split into three parts - [LDM 2022-05-11](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/meetings/2022/LDM-2022-05-11.md#ref-readonly-method-parameters): allowed `ref` and no callsite annotation for `ref readonly` parameters ================================================ FILE: proposals/csharp-12.0/using-alias-types.md ================================================ # Allow using alias directive to reference any kind of Type [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Relax the using_alias_directive ([§13.5.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#1352-using-alias-directives)) to allow it to point at any sort of type, not just named types. This would support types not allowed today, like: tuple types, pointer types, array types, etc. For example, this would now be allowed: ```c# using Point = (int x, int y); ``` ## Motivation For ages, C# has had the ability to introduce aliases for namespaces and named types (classes, delegated, interfaces, records and structs). This worked acceptably well as it provided a means to introduce non-conflicting names in cases where a normal named pulled in from `using_directive`s might be ambiguous, and it allowed a way to provide a simpler name when dealing with complex generic types. However, the rise of additional complex type symbols in the language has caused more use to arise where aliases would be valuable but are currently not allowed. For example, both tuples and function-pointers often can have large and complex regular textual forms that can be painful to continually write out, and a burden to try to read. Aliases would help in these cases by giving a short, developer-provided, name that can then be used in place of those full structural forms. ## Detailed design We will change the grammar of `using_alias_directive` thusly: ``` using_alias_directive - : 'using' identifier '=' namespace_or_type_name ';' + : 'using' identifier '=' (namespace_name | type) ';' ; ``` Top-level reference type nullability annotations are disallowed. Interestingly, most of the spec language in [§13.5.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#1352-using-alias-directives) does not need to change. Most language in it already refers to 'namespace or type', for example: > A using_alias_directive introduces an identifier that serves as an alias for a namespace or type within the immediately enclosing compilation unit or namespace body. This remains true, just that the grammar now allows the 'type' to be any arbitrary type, not the limited set allowed for by `namespace_or_type_name` previously. The sections that do need updating are: ```diff - The order in which using_alias_directives are written has no significance, and resolution of the namespace_or_type_name referenced by a using_alias_directive is not affected by the using_alias_directive itself or by other using_directives in the immediately containing compilation unit or namespace body. In other words, the namespace_or_type_name of a using_alias_directive is resolved as if the immediately containing compilation unit or namespace body had no using_directives. A using_alias_directive may however be affected by extern_alias_directives in the immediately containing compilation unit or namespace body. In the example + The order in which using_alias_directives are written has no significance, and resolution of the `(namespace_name | type)` referenced by a using_alias_directive is not affected by the using_alias_directive itself or by other using_directives in the immediately containing compilation unit or namespace body. In other words, the `(namespace_name | type)` of a using_alias_directive is resolved as if the immediately containing compilation unit or namespace body had no using_directives. A using_alias_directive may however be affected by extern_alias_directives in the immediately containing compilation unit or namespace body. In the example ``` ```diff - The namespace_name referenced by a using_namespace_directive is resolved in the same way as the namespace_or_type_name referenced by a using_alias_directive. Thus, using_namespace_directives in the same compilation unit or namespace body do not affect each other and can be written in any order. + The namespace_name referenced by a using_namespace_directive is resolved in the same way as the namespace_or_type_name referenced by a using_alias_directive. Thus, using_namespace_directives in the same compilation unit or namespace body do not affect each other and can be written in any order. ``` ```diff + It is illegal for a using alias type to be a nullable reference type. 1. `using X = string?;` is not legal. 2. `using X = List;` is legal. The alias is to `List<...>` which is itself not a nullable reference type itself, even though it contains one as a type argument. 3. `using X = int?;` is legal. This is a nullable *value* type, not a nullable *reference* type. ``` ## Supporting aliases to types containing pointers. A new [unsafe context](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/unsafe-code.md#222-unsafe-contexts) is added through an optional 'unsafe' keyword in the using_alias_directive production: ```diff using_alias_directive + : 'using' 'unsafe'? identifier '=' (namespace_name | type) ';' ; using_static_directive + : 'using' 'static' 'unsafe'? type_name ';' ; + 'unsafe' can only be used with an using_alias_directive or using_static_directive, not a using_directive. + The 'unsafe' keyword present in a 'using_alias_directive' causes the entire textual extent of the 'type' portion (not the 'namespace_name' portion) to become an unsafe context. + The 'unsafe' keyword present in a 'using_static_directive' causes the entire textual extent of the 'type_name' portion to become an unsafe context. ``` ================================================ FILE: proposals/csharp-13.0/collection-expressions-better-conversion.md ================================================ # Better conversion from collection expression element [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Updates to the better conversion rules to be more consistent with `params`, and better handle current ambiguity scenarios. For example, `ReadOnlySpan` vs `ReadOnlySpan` can currently cause ambiguities during overload resolution for `[""]`. ## Detailed Design The following are the better conversion from expression rules. These replace the rules in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution. These rules are: > Given an implicit conversion `C₁` that converts from an expression `E` to a type `T₁`, and an implicit conversion `C₂` that converts from an expression `E` to a type `T₂`, `C₁` is a ***better conversion*** than `C₂` if one of the following holds: > * **`E` is a *collection expression*, and `C₁` is a ***better collection conversion from expression*** than `C₂`** > * **`E` is not a *collection expression* and one of the following holds:** > * `E` exactly matches `T₁` and `E` does not exactly match `T₂` > * `E` exactly matches both or neither of `T₁` and `T₂`, and `T₁` is a [*better conversion target*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11646-better-conversion-target) than `T₂` > * `E` is a method group, ... We add a new definition for ***better collection conversion from expression***, as follows: Given: - `E` is a collection expression with element expressions `[EL₁, EL₂, ..., ELₙ]` - `T₁` and `T₂` are collection types - `E₁` is the element type of `T₁` - `E₂` is the element type of `T₂` - `CE₁ᵢ` are the series of conversions from `ELᵢ` to `E₁` - `CE₂ᵢ` are the series of conversions from `ELᵢ` to `E₂` If there is an identity conversion from `E₁` to `E₂`, then the element conversions are as good as each other. Otherwise, the element conversions to `E₁` are ***better than the element conversions*** to `E₂` if: - For every `ELᵢ`, `CE₁ᵢ` is at least as good as `CE₂ᵢ`, and - There is at least one i where `CE₁ᵢ` is better than `CE₂ᵢ` Otherwise, neither set of element conversions is better than the other, and they are also not as good as each other. Conversion comparisons are made using better conversion from expression if `ELᵢ` is not a spread element. If `ELᵢ` is a spread element, we use better conversion from the element type of the spread collection to `E₁` or `E₂`, respectively. `C₁` is a ***better collection conversion from expression*** than `C₂` if: - Both `T₁` and `T₂` are not *span types*, and `T₁` is implicitly convertible to `T₂`, and `T₂` is not implicitly convertible to `T₁`, or - `E₁` does not have an identity conversion to `E₂`, and the element conversions to `E₁` are ***better than the element conversions*** to `E₂`, or - `E₁` has an identity conversion to `E₂`, and one of the following holds: - `T₁` is `System.ReadOnlySpan`, and `T₂` is `System.Span`, or - `T₁` is `System.ReadOnlySpan` or `System.Span`, and `T₂` is an *array_or_array_interface* with *element type* `E₂` Otherwise, neither collection type is better, and the result is ambiguous. > [!NOTE] > These rules mean that methods that expose overloads that take different element types and without a conversion between the collection types are ambiguous for empty collection expressions. As an example: > ```cs > public void M(ReadOnlySpan ros) { ... } > public void M(Span span) { ... } > > M([]); // Ambiguous > ``` ### Scenarios: In plain English, the collection types themselves must be either the same, or unambiguously better (ie, `List` and `List` are the same, `List` is unambiguously better than `IEnumerable`, and `List` and `HashSet` cannot be compared), and the element conversions for the better collection type must also be the same or better (ie, we can't decide between `ReadOnlySpan` and `Span` for `[""]`, the user needs to make that decision). More examples of this are: | `T₁` | `T₂` | `E` | `C₁` Conversions | `C₂` Conversions | `CE₁ᵢ` vs `CE₂ᵢ` | Outcome | |--------|--------|------------|----------------|----------------|---------------------|---------| | `List` | `List` | `[1, 2, 3]` | `[Identity, Identity, Identity]` | `[Implicit Constant, Implicit Constant, Implicit Constant]` | `CE₁ᵢ` is better | `List` is picked | | `List` | `List` | `[(int)1, (byte)2]` | `[Identity, Implicit Numeric]` | Not applicable | `T₂` is not applicable | `List` is picked | | `List` | `List` | `[1, (byte)2]` | `[Identity, Implicit Numeric]` | `[Implicit Constant, Identity]` | Neither is better | Ambiguous | | `List` | `List` | `[(byte)1, (byte)2]` | `[Implicit Numeric, Implicit Numeric]` | `[Identity, Identity]` | `CE₂ᵢ` is better | `List` is picked | | `List` | `List` | `[1, 2, 3]` | `[Implicit Nullable, Implicit Nullable, Implicit Nullable]` | `[Implicit Numeric, Implicit Numeric, Implicit Numeric]` | Neither is better | Ambiguous | | `List` | `List` | `[1, 2, 3]` | `[Implicit Nullable, Implicit Nullable, Implicit Nullable]` | `[Implicit Numeric, Implicit Numeric, Implicit Numeric]` | `CE₁ᵢ` is better | `List` is picked | | `List` | `List` | `[1, 2, 3]` | `[Implicit Numeric, Implicit Numeric, Implicit Numeric]` | `[Implicit Numeric, Implicit Numeric, Implicit Numeric]` | `CE₁ᵢ` is better | `List` is picked | | `IEnumerable` | `List` | `[1, 2, 3]` | `[Identity, Identity, Identity]` | `[Implicit Constant, Implicit Constant, Implicit Constant]` | `CE₁ᵢ` is better | `IEnumerable` is picked | | `IEnumerable` | `List` | `[(byte)1, (byte)2]` | `[Implicit Numeric, Implicit Numeric]` | `[Identity, Identity]` | `CE₂ᵢ` is better | `List` is picked | | `int[]` | `List` | `[1, 2, 3]` | `[Identity, Identity, Identity]` | `[Implicit Constant, Implicit Constant, Implicit Constant]` | `CE₁ᵢ` is better | `int[]` is picked | | `ReadOnlySpan` | `ReadOnlySpan` | `["", "", ""]` | `[Identity, Identity, Identity]` | `[Implicit Reference, Implicit Reference, Implicit Reference]` | `CE₁ᵢ` is better | `ReadOnlySpan` is picked | | `ReadOnlySpan` | `ReadOnlySpan` | `["", new object()]` | Not applicable | `[Implicit Reference, Identity]` | `T₁` is not applicable | `ReadOnlySpan` is picked | | `ReadOnlySpan` | `Span` | `["", ""]` | `[Implicit Reference]` | `[Identity]` | `CE₂ᵢ` is better | `Span` is picked | | `ReadOnlySpan` | `Span` | `[new object()]` | `[Identity]` | Not applicable | `T₁` is not applicable | `ReadOnlySpan` is picked | | `ReadOnlySpan` | `ReadOnlySpan` | `[$"{1}"]` | `[Interpolated String Handler]` | `[Identity]` | `CE₁ᵢ` is better | `ReadOnlySpan` is picked | | `ReadOnlySpan` | `ReadOnlySpan` | `[$"{"blah"}"]` | `[Interpolated String Handler]` | `[Identity]` - But constant | `CE₂ᵢ` is better | `ReadOnlySpan` is picked | | `ReadOnlySpan` | `ReadOnlySpan` | `[$"{1}"]` | `[Identity]` | `[Interpolated String]` | `CE₂ᵢ` is better | `ReadOnlySpan` is picked | | `ReadOnlySpan` | `ReadOnlySpan` | `[$"{1}", (FormattableString)null]` | Not applicable | `[Interpolated String, Identity]` | `T₁` isn't applicable | `ReadOnlySpan` is picked | | `HashSet` | `Span` | `[1, 2]` | `[Implicit Constant, Implicit Constant]` | `[Implicit Numeric, Implicit Numeric]` | `CE₁ᵢ` is better | `HashSet` is picked | | `HashSet` | `Span` | `[1, 2]` | `[Implicit Numeric, Implicit Numeric]` | `[Implicit Constant, Implicit Constant]` | `CE₂ᵢ` is better | `Span` is picked | ## Open questions ### How far should we prioritize `ReadOnlySpan`/`Span` over other types? As specified today, the following overloads would be ambiguous: ```cs C.M1(["Hello world"]); // Ambiguous, no tiebreak between ROS and List C.M2(["Hello world"]); // Ambiguous, no tiebreak between Span and List C.M3(["Hello world"]); // Ambiguous, no tiebreak between ROS and MyList. C.M4(["Hello", "Hello"]); // Ambiguous, no tiebreak between ROS and HashSet. Created collections have different contents class C { public static void M1(ReadOnlySpan ros) {} public static void M1(List list) {} public static void M2(Span ros) {} public static void M2(List list) {} public static void M3(ReadOnlySpan ros) {} public static void M3(MyList list) {} public static void M4(ReadOnlySpan ros) {} public static void M4(HashSet hashset) {} } class MyList : List {} ``` How far do we want to go here? The `List` variant seems reasonable, and subtypes of `List` exist aplenty. But the `HashSet` version has very different semantics, how sure are we that it's actually "worse" than `ReadOnlySpan` in this API? ================================================ FILE: proposals/csharp-13.0/esc-escape-sequence.md ================================================ # String/Character escape sequence `\e` [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary An addition of the string/character escape sequence `\e` as a shortcut/short-hand replacement for the character code point `0x1b`, commonly known as the `ESCAPE` (or `ESC`) character. This character is currently accessible using one of the following escape sequences: - `\u001b` - `\U0000001b` - `\x1b` (not recommended, see the picture attached at the bottom.) With the implementation of this proposal, the following assertions should be true: ```csharp char escape_char = '\e'; Assert.IsTrue(escape_char == (char)0x1b, "..."); Assert.IsTrue(escape_char == '\u001b', "..."); Assert.IsTrue(escape_char == '\U0000001b', "..."); Assert.IsTrue(escape_char == '\x1b', "..."); ``` ## Detailed design The language syntax specification is changed as follows in section [6.4.5.5](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/lexical-structure.md#6455-character-literals): ```diff fragment Simple_Escape_Sequence - : '\\\'' | '\\"' | '\\\\' | '\\0' | '\\a' | '\\b' | '\\f' | '\\n' | '\\r' | '\\t' | '\\v' + : '\\\'' | '\\"' | '\\\\' | '\\0' | '\\a' | '\\b' | '\\f' | '\\n' | '\\r' | '\\t' | '\\v' | '\\e' ; ``` As well as the addition of the **last line** to the following table in the specifications: > A simple escape sequence represents a Unicode character, as described in the table below. > > | **Escape sequence** | **Character name** | **Unicode code point** | > |---------------------|--------------------|--------------------| > | `\'` | Single quote | U+0027 | > | ... | ... | ... | > | `\e` | Escape character | U+001B | > > The type of a *Character_Literal* is `char`. ================================================ FILE: proposals/csharp-13.0/lock-object.md ================================================ # `Lock` object [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Special-case how `System.Threading.Lock` interacts with the `lock` keyword (calling its `EnterScope` method under the hood). Add static analysis warnings to prevent accidental misuse of the type where possible. ## Motivation [motivation]: #motivation .NET 9 is introducing a new [`System.Threading.Lock` type](https://github.com/dotnet/runtime/issues/34812) as a better alternative to existing monitor-based locking. The presence of the `lock` keyword in C# might lead developers to think they can use it with this new type. Doing so wouldn't lock according to the semantics of this type but would instead treat it as any other object and would use monitor-based locking. ```cs namespace System.Threading { public sealed class Lock { public void Enter(); public void Exit(); public Scope EnterScope(); public ref struct Scope { public void Dispose(); } } } ``` ## Detailed design [design]: #detailed-design Semantics of the lock statement ([§13.13](https://github.com/dotnet/csharpstandard/blob/9af5bdaa7af535f34fbb7923e5406e01db8489f7/standard/statements.md#1313-the-lock-statement)) are changed to special-case the `System.Threading.Lock` type: > A `lock` statement of the form `lock (x) { ... }` > > 1. **where `x` is an expression of type `System.Threading.Lock`, is precisely equivalent to:** > ```cs > using (x.EnterScope()) > { > ... > } > ``` > **and `System.Threading.Lock` must have the following shape:** > ```cs > namespace System.Threading > { > public sealed class Lock > { > public Scope EnterScope(); > > public ref struct Scope > { > public void Dispose(); > } > } > } > ``` > 2. where `x` is an expression of a *reference_type*, is precisely equivalent to: [...] Note that the shape might not be fully checked (e.g., there will be no errors nor warnings if the `Lock` type is not `sealed`), but the feature might not work as expected (e.g., there will be no warnings when converting `Lock` to a derived type, since the feature assumes there are no derived types). Additionally, new warnings are added to implicit reference conversions ([§10.2.8](https://github.com/dotnet/csharpstandard/blob/9af5bdaa7af535f34fbb7923e5406e01db8489f7/standard/conversions.md#1028-implicit-reference-conversions)) when upcasting the `System.Threading.Lock` type: > The implicit reference conversions are: > > - From any *reference_type* to `object` and `dynamic`. > - **A warning is reported when the *reference_type* is known to be `System.Threading.Lock`.** > - From any *class_type* `S` to any *class_type* `T`, provided `S` is derived from `T`. > - **A warning is reported when `S` is known to be `System.Threading.Lock`.** > - From any *class_type* `S` to any *interface_type* `T`, provided `S` implements `T`. > - **A warning is reported when `S` is known to be `System.Threading.Lock`.** > - [...] ```cs object l = new System.Threading.Lock(); // warning lock (l) { } // monitor-based locking is used here ``` Note that this warning occurs even for equivalent explicit conversions. The compiler avoids reporting the warning in some cases when the instance cannot be locked after converting to `object`: - when the conversion is implicit and part of an object equality operator invocation. ```cs var l = new System.Threading.Lock(); if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)` // ... ``` To escape out of the warning and force use of monitor-based locking, one can use - the usual warning suppression means (`#pragma warning disable`), - `Monitor` APIs directly, - indirect casting like `object AsObject(T l) => (object)l;`. ## Alternatives [alternatives]: #alternatives - Support a general pattern that other types can also use to interact with the `lock` keyword. This is a future work that might be implemented when `ref struct`s can participate in generics. Discussed in [LDM 2023-12-04](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-12-04.md#lock-statement-pattern). - To avoid ambiguity between the existing monitor-based locking and the new `Lock` (or pattern in the future), we could: - Introduce a new syntax instead of reusing the existing `lock` statement. - Require the new lock types to be `struct`s (since the existing `lock` disallows value types). There could be problems with default constructors and copying if the structs have lazy initialization. - The codegen could be hardened against thread aborts (which are themselves obsoleted). - We could warn also when `Lock` is passed as a type parameter, because locking on a type parameter always uses monitor-based locking: ```cs M(new Lock()); // could warn here void M(T x) // (specifying `where T : Lock` makes no difference) { lock (x) { } // because this uses Monitor } ``` However, that would cause warnings when storing `Lock`s in a list which is undesirable: ```cs List list = new(); list.Add(new Lock()); // would warn here ``` - We could include static analysis to prevent usage of `System.Threading.Lock` in `using`s with `await`s. I.e., we could emit either an error or a warning for code like `using (lockVar.EnterScope()) { await ... }`. Currently, this is not needed since `Lock.Scope` is a `ref struct`, so that code is illegal anyway. However, if we ever allowed `ref struct`s in `async` methods or changed `Lock.Scope` to not be a `ref struct`, this analysis would become beneficial. (We would also likely need to consider for this all lock types matching the general pattern if implemented in the future. Although there might need to be an opt-out mechanism as some lock types might be allowed to be used with `await`.) Alternatively, this could be implemented as an analyzer shipped as part of the runtime. - We could relax the restriction that value types cannot be `lock`ed - for the new `Lock` type (only needed if the API proposal changed it from `class` to `struct`), - for the general pattern where any type can participate when implemented in the future. - We could allow the new `lock` in `async` methods where `await` is not used inside the `lock`. - Currently, since `lock` is lowered to `using` with a `ref struct` as the resource, this results in a compile-time error. The workaround is to extract the `lock` into a separate non-`async` method. - Instead of using the `ref struct Scope`, we could emit `Lock.Enter` and `Lock.Exit` methods in `try`/`finally`. However, the `Exit` method must throw when it's called from a different thread than `Enter`, hence it contains a thread lookup which is avoided when using the `Scope`. - Best would be to allow compiling `using` on a `ref struct` in `async` methods if there is no `await` inside the `using` body. ## Design meetings - [LDM 2023-05-01](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-01.md#lock-statement-improvements): initial decision to support a `lock` pattern - [LDM 2023-10-16](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-16.md#lock-statement-pattern): triaged into the working set for .NET 9 - [LDM 2023-12-04](https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-12-04.md#lock-statement-pattern): rejected the general pattern, accepted only special-casing the `Lock` type + adding static analysis warnings ================================================ FILE: proposals/csharp-13.0/method-group-natural-type-improvements.md ================================================ # Method group natural type improvements [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary This proposal refines the determination of the natural type of a method group in a few ways: 1. Consider candidate methods scope-by-scope (instance methods first, then each scope subsequent scope of extension methods) 2. Prune candidates that have no chance of succeeding, so they don't interfere with determining a unique signature: - Prune generic instance methods when no type arguments are provided (`var x = M;`) - Prune generic extension methods based on being able to reduce the extension and on constraints ## Context on method group natural type In C# 10, method groups gained a weak natural type. That type is a "weak type" in that it only comes into play when the method group is not target-typed (ie. it plays no role in `System.Action a = MethodGroup;`). That weak natural type allows scenarios like `var x = MethodGroup;`. For reference: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#natural-function-type > A method group has a natural type if all candidate methods in the method group have a common signature. (If the method group may include extension methods, the candidates include the containing type and all extension method scopes.) In practice, this means that we: 1. Construct the set of all candidate methods: - methods on the relevant type are in the set if they are static and the receiver is a type, or if they are non-static and the receiver is a value - extension methods (across all scopes) that can be reduced are in the set 3. If the signatures of all the candidates do not match, then the method group doesn't have a natural type 4. If the arity of the resulting signature doesn't match the number of provided type arguments, then the method group doesn't have a natural type 5. Otherwise, the resulting signature is used as the natural type ## Proposal The principle is to go scope-by-scope and prune candidates that we know cannot succeed as early as possible (same principle used in overload resolution). 1. For each scope, we construct the set of all candidate methods: - for the initial scope, methods on the relevant type with arity matching the provided type arguments and satisfying constraints with the provided type arguments are in the set if they are static and the receiver is a type, or if they are non-static and the receiver is a value - for subsequent scopes, extension methods in that scope that can be substituted with the provided type arguments and reduced using the value of the receiver while satisfying contstraints are in the set 1. If we have no candidates in the given scope, proceed to the next scope. 2. If the signatures of all the candidates do not match, then the method group doesn't have a natural type 3. Otherwise, resulting signature is used as the natural type 2. If the scopes are exhausted, then the method group doesn't have a natural type ---- Relates to scope-by-scope proposal: https://github.com/dotnet/csharplang/issues/7364 Relates to proposal to better handle generic extension methods: https://github.com/dotnet/roslyn/issues/69222 ================================================ FILE: proposals/csharp-13.0/overload-resolution-priority.md ================================================ # Overload Resolution Priority [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary We introduce a new attribute, `System.Runtime.CompilerServices.OverloadResolutionPriority`, that can be used by API authors to adjust the relative priority of overloads within a single type as a means of steering API consumers to use specific APIs, even if those APIs would normally be considered ambiguous or otherwise not be chosen by C#'s overload resolution rules. ## Motivation [motivation]: #motivation API authors often run into an issue of what to do with a member after it has been obsoleted. For backwards compatibility purposes, many will keep the existing member around with `ObsoleteAttribute` set to error in perpetuity, in order to avoid breaking consumers who upgrade binaries at runtime. This particularly hits plugin systems, where the author of a plugin does not control the environment in which the plugin runs. The creator of the environment may want to keep an older method present, but block access to it for any newly developed code. However, `ObsoleteAttribute` by itself is not enough. The type or member is still visible in overload resolution, and may cause unwanted overload resolution failures when there is a perfectly good alternative, but that alternative is either ambiguous with the obsoleted member, or the presence of the obsoleted member causes overload resolution to end early without ever considering the good member. For this purpose, we want to have a way for API authors to guide overload resolution on resolving the ambiguity, so that they can evolve their API surface areas and steer users towards performant APIs without having to compromise the user experience. The Base Class Libraries (BCL) team has several examples of where this can prove useful. Some (hypothetical) examples are: * Creating an overload of `Debug.Assert` that uses `CallerArgumentExpression` to get the expression being asserted, so that it can be included in the message, and make it preferred over the existing overload. * Making `string.IndexOf(string, StringComparison = Ordinal)` preferred over `string.IndexOf(string)`. This would have to be discussed as a potential breaking change, but there is some thought that it is the better default, and more likely to be what the user intended. * A combination of this proposal and [`CallerAssemblyAttribute`](https://github.com/dotnet/csharplang/issues/4984) would allow methods that have an implicit caller identity to avoid expensive stack walks. `Assembly.Load(AssemblyName)` does this today, and it could be much more efficient. * `Microsoft.Extensions.Primitives.StringValues` exposes an implicit conversion to both `string` and `string[]`. This means that it is ambiguous when passed to a method with both `params string[]` and `params ReadOnlySpan` overloads. This attribute could be used to prioritize one of the overloads to prevent the ambiguity. ## Detailed Design [detailed-design]: #detailed-design ### Overload resolution priority We define a new concept, ***overload_resolution_priority***, which is used during the process of resolving a method group. ***overload_resolution_priority*** is a 32-bit integer value. All methods have an ***overload_resolution_priority*** of 0 by default, and this can be changed by applying [`OverloadResolutionPriorityAttribute`](#systemruntimecompilerservicesoverloadresolutionpriorityattribute) to a method. We update section [§12.6.4.1](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12641-general) of the C# specification as follows (change in **bold**): > Once the candidate function members and the argument list have been identified, the selection of the best function member is the same in all cases: > > - First, the set of candidate function members is reduced to those function members that are applicable with respect to the given argument list ([§12.6.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12642-applicable-function-member)). If this reduced set is empty, a compile-time error occurs. > - **Then, the reduced set of candidate members is grouped by declaring type. Within each group:** > - **Candidate function members are ordered by ***overload_resolution_priority***. If the member is an override, the ***overload_resolution_priority*** comes from the least-derived declaration of that member.** > - **All members that have a lower ***overload_resolution_priority*** than the highest found within its declaring type group are removed.** > - **The reduced groups are then recombined into the final set of applicable candidate function members.** > - Then, the best function member from the set of applicable candidate function members is located. If the set contains only one function member, then that function member is the best function member. Otherwise, the best function member is the one function member that is better than all other function members with respect to the given argument list, provided that each function member is compared to all other function members using the rules in [§12.6.4.3](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12643-better-function-member). If there is not exactly one function member that is better than all other function members, then the function member invocation is ambiguous and a binding-time error occurs. As an example, this feature would cause the following code snippet to print "Span", rather than "Array": ```cs using System.Runtime.CompilerServices; var d = new C1(); int[] arr = [1, 2, 3]; d.M(arr); // Prints "Span" class C1 { [OverloadResolutionPriority(1)] public void M(ReadOnlySpan s) => Console.WriteLine("Span"); // Default overload resolution priority public void M(int[] a) => Console.WriteLine("Array"); } ``` The effect of this change is that, like pruning for most-derived types, we add a final pruning for overload resolution priority. Because this pruning occurs at the very end of the overload resolution process, it does mean that a base type cannot make its members higher-priority than any derived type. This is intentional, and prevents an arms-race from occuring where a base type may try to always be better than a derived type. For example: ```cs using System.Runtime.CompilerServices; var d = new Derived(); d.M([1, 2, 3]); // Prints "Derived", because members from Base are not considered due to finding an applicable member in Derived class Base { [OverloadResolutionPriority(1)] public void M(ReadOnlySpan s) => Console.WriteLine("Base"); } class Derived : Base { public void M(int[] a) => Console.WriteLine("Derived"); } ``` Negative numbers are allowed to be used, and can be used to mark a specific overload as worse than all other default overloads. The **overload_resolution_priority** of a member comes from the least-derived declaration of that member. **overload_resolution_priority** is not inherited or inferred from any interface members a type member may implement, and given a member `Mx` that implements an interface member `Mi`, no warning is issued if `Mx` and `Mi` have different **overload_resolution_priorities**. > NB: The intent of this rule is to replicate the behavior of the `params` modifier. ### `System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute` We introduce the following attribute to the BCL: ```cs namespace System.Runtime.CompilerServices; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public sealed class OverloadResolutionPriorityAttribute(int priority) : Attribute { public int Priority => priority; } ``` All methods in C# have a default ***overload_resolution_priority*** of 0, unless they are attributed with `OverloadResolutionPriorityAttribute`. If they are attributed with that attribute, then their ***overload_resolution_priority*** is the integer value provided to the first argument of the attribute. It is an error to apply `OverloadResolutionPriorityAttribute` to the following locations: * Non-indexer properties * Property, indexer, or event accessors * Conversion operators * Lambdas * Local functions * Finalizers * Static constructors Attributes encountered on these locations in metadata are ignored by C#. It is an error to apply `OverloadResolutionPriorityAttribute` in a location it would be ignored, such as on an override of a base method, as the priority is read from the least-derived declaration of a member. > NB: This intentionally differs from the behavior of the `params` modifier, which allows respecifying or adding when ignored. ### Callability of members An important caveat for `OverloadResolutionPriorityAttribute` is that it can make certain members effectively uncallable from source. For example: ```cs using System.Runtime.CompilerServices; int i = 1; var c = new C3(); c.M1(i); // Will call C3.M1(long), even though there's an identity conversion for M1(int) c.M2(i); // Will call C3.M2(int, string), even though C3.M1(int) has less default parameters class C3 { public void M1(int i) {} [OverloadResolutionPriority(1)] public void M1(long l) {} [Conditional("DEBUG")] public void M2(int i) {} [OverloadResolutionPriority(1), Conditional("DEBUG")] public void M2(int i, [CallerArgumentExpression(nameof(i))] string s = "") {} public void M3(string s) {} [OverloadResolutionPriority(1)] public void M3(object o) {} } ``` For these examples, the default priority overloads effectively become vestigal, and only callable through a few steps that take some extra effort: * Converting the method to a delegate, and then using that delegate. * For some reference type variance scenarios, such as `M3(object)` that is prioritized over `M3(string)`, this strategy will fail. * Conditional methods, such as `M2`, would also not be callable with this strategy, as conditional methods cannot be converted to delegates. * Using the `UnsafeAccessor` runtime feature to call it via matching signature. * Manually using reflection to obtain a reference to the method and then invoking it. * Code that is not recompiled will continue to call old methods. * Handwritten IL can specify whatever it chooses. ## Open Questions ### Extension method grouping (answered) As currently worded, extension methods are ordered by priority _only within their own type_. For example: ```cs new C2().M([1, 2, 3]); // Will print Ext2 ReadOnlySpan static class Ext1 { [OverloadResolutionPriority(1)] public static void M(this C2 c, Span s) => Console.WriteLine("Ext1 Span"); [OverloadResolutionPriority(0)] public static void M(this C2 c, ReadOnlySpan s) => Console.WriteLine("Ext1 ReadOnlySpan"); } static class Ext2 { [OverloadResolutionPriority(0)] public static void M(this C2 c, ReadOnlySpan s) => Console.WriteLine("Ext2 ReadOnlySpan"); } class C2 {} ``` When doing overload resolution for extension members, should we not sort by declaring type, and instead consider all extensions within the same scope? #### Answer We will always group. The above example will print `Ext2 ReadOnlySpan` ### Attribute inheritance on overrides (answered) Should the attribute be inherited? If not, what is the priority of the overriding member? If the attribute is specified on a virtual member, should an override of that member be required to repeat the attribute? #### Answer The attribute will not be marked as inherited. We will look at the least-derived declaration of a member to determine its overload resolution priority. ### Application error or warning on override (answered) ```cs class Base { [OverloadResolutionPriority(1)] public virtual void M() {} } class Derived { [OverloadResolutionPriority(2)] public override void M() {} // Warn or error for the useless and ignored attribute? } ``` Which should we do on the application of a `OverloadResolutionPriorityAttribute` in a context where it is ignored, such as an override: 1. Do nothing, let it silently be ignored. 2. Issue a warning that the attribute will be ignored. 3. Issue an error that the attribute is not allowed. 3 is the most cautious approach, if we think there may be a space in the future where we might want to allow an override to specify this attribute. #### Answer We will go with 3, and block application on locations it would be ignored. ### Implicit interface implementation (answered) What should the behavior of an implicit interface implementation be? Should it be required to specify `OverloadResolutionPriority`? What should the behavior of the compiler be when it encounters an implicit implementation without a priority? This will nearly certainly happen, as an interface library may be updated, but not an implementation. Prior art here with `params` is to not specify, and not carry over the value: ```cs using System; var c = new C(); c.M(1, 2, 3); // error CS1501: No overload for method 'M' takes 3 arguments ((I)c).M(1, 2, 3); interface I { void M(params int[] ints); } class C : I { public void M(int[] ints) { Console.WriteLine("params"); } } ``` Our options are: 1. Follow `params`. `OverloadResolutionPriorityAttribute` will not be implicitly carried over or be required to be specified. 2. Carry over the attribute implicitly. 3. Do not carry over the attribute implicitly, require it to be specified at the call site. 1. This brings an extra question: what should the behavior be when the compiler encounters this scenario with compiled references? #### Answer We will go with 1. ### Further application errors (Answered) There are a few more locations like [this](#application-error-or-warning-on-override-answered) that need to be confirmed. They include: * Conversion operators - The spec never says that conversion operators go through overload resolution, so the implementation blocks application on these members. Should that be confirmed? * Lambdas - Similarly, lambdas are never subject to overload resolution, so the implementation blocks them. Should that be confirmed? * Destructors - again, currently blocked. * Static constructors - again, currently blocked. * Local functions - These are not currently blocked, because they _do_ undergo overload resolution, you just can't overload them. This is similar to how we don't error when the attribute is applied to a member of a type that is not overloaded. Should this behavior be confirmed? #### Answer All of the locations listed above are blocked. ### Langversion Behavior (Answered) The implementation currently only issues langversion errors when `OverloadResolutionPriorityAttribute` is applied, _not_ when it actually influences anything. This decision was made because there are APIs that the BCL will add (both now and over time) that will start using this attribute; if the user manually sets their language version back to C# 12 or prior, they may see these members and, depending our langversion behavior, either: * If we ignore the attribute in C# <13, run into an ambiguity error because the API is truly ambiguous without the attribute, or; * If we error when the attribute affected the outcome, run into an error that the API is unconsumable. This will be especially bad because `Debug.Assert(bool)` is being de-prioritized in .NET 9, or; * If we silently change resolution, encounter potentially different behavior between different compiler versions if one understands the attribute and another doesn't. The last behavior was chosen, because it results in the most forward-compatibility, but the changing result could be surprising to some users. Should we confirm this, or should we choose one of the other options? #### Answer We will go with option 1, silently ignoring the attribute in previous language versions. ## Alternatives [alternatives]: #alternatives A [previous](https://github.com/dotnet/csharplang/pull/7707) proposal tried to specify a `BinaryCompatOnlyAttribute` approach, which was very heavy-handed in removing things from visibility. However, that has lots of hard implementation problems that either mean the proposal is too strong to be useful (preventing testing old APIs, for example) or so weak that it missed some of the original goals (such as being able have an API that would otherwise be considered ambiguous call a new API). That version is replicated below.
BinaryCompatOnlyAttribute Proposal (obsolete) ### BinaryCompatOnlyAttribute #### Detailed design [design]: #bco-detailed-design ##### `System.BinaryCompatOnlyAttribute` We introduce a new reserved attribute: ```cs namespace System; // Excludes Assembly, GenericParameter, Module, Parameter, ReturnValue [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Delegate | AttributeTargets.Enum | AttributeTargets.Event | AttributeTargets.Field | AttributeTargets.Interface | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] public class BinaryCompatOnlyAttribute : Attribute {} ``` When applied to a type member, that member is treated as inaccessible in every location by the compiler, meaning that it does not contribute to member lookup, overload resolution, or any other similar process. ##### Accessibility Domains We update [§7.5.3 Accessibility domains](https://github.com/dotnet/csharpstandard/blob/720d921c5688190ea544682cdbdf8874fa716f2b/standard/basic-concepts.md#753-accessibility-domains) as **follows**: > The ***accessibility domain*** of a member consists of the (possibly disjoint) sections of program text in which access to the member is permitted. For purposes of defining the accessibility domain of a member, a member is said to be ***top-level*** if it is not declared within a type, and a member is said to be ***nested*** if it is declared within another type. Furthermore, the ***program text*** of a program is defined as all text contained in all compilation units of the program, and the program text of a type is defined as all text contained in the *type_declaration*s of that type (including, possibly, types that are nested within the type). > > The accessibility domain of a predefined type (such as `object`, `int`, or `double`) is unlimited. > > The accessibility domain of a top-level unbound type `T` ([§8.4.4](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/types.md#844-bound-and-unbound-types)) that is declared in a program `P` is defined as follows: > > - **If `T` is marked with `BinaryCompatOnlyAttribute`, the accessibility domain of `T` is completely inaccessible to the program text of `P` and any program that references `P`.** > - If the declared accessibility of `T` is public, the accessibility domain of `T` is the program text of `P` and any program that references `P`. > - If the declared accessibility of `T` is internal, the accessibility domain of `T` is the program text of `P`. > > *Note*: From these definitions, it follows that the accessibility domain of a top-level unbound type is always at least the program text of the program in which that type is declared. *end note* > > The accessibility domain for a constructed type `T` is the intersection of the accessibility domain of the unbound generic type `T` and the accessibility domains of the type arguments `A₁, ..., Aₑ`. > > The accessibility domain of a nested member `M` declared in a type `T` within a program `P`, is defined as follows (noting that `M` itself might possibly be a type): > > - **If `M` is marked with `BinaryCompatOnlyAttribute`, the accessibility domain of `M` is completely inaccessible to the program text of `P` and any program that references `P`.** > - If the declared accessibility of `M` is `public`, the accessibility domain of `M` is the accessibility domain of `T`. > - If the declared accessibility of `M` is `protected internal`, let `D` be the union of the program text of `P` and the program text of any type derived from `T`, which is declared outside `P`. The accessibility domain of `M` is the intersection of the accessibility domain of `T` with `D`. > - If the declared accessibility of `M` is `private protected`, let `D` be the intersection of the program text of `P` and the program text of `T` and any type derived from `T`. The accessibility domain of `M` is the intersection of the accessibility domain of `T` with `D`. > - If the declared accessibility of `M` is `protected`, let `D` be the union of the program text of `T`and the program text of any type derived from `T`. The accessibility domain of `M` is the intersection of the accessibility domain of `T` with `D`. > - If the declared accessibility of `M` is `internal`, the accessibility domain of `M` is the intersection of the accessibility domain of `T` with the program text of `P`. > - If the declared accessibility of `M` is `private`, the accessibility domain of `M` is the program text of `T`. The goal of these additions is to make it so that members marked with `BinaryCompatOnlyAttribute` are completely inaccessible to any location, they will not participate in member lookup, and cannot affect the rest of the program. Consequentely, this means they cannot implement interface members, they cannot call each other, and they cannot be overridden (virtual methods), hidden, or implemented (interface members). Whether this is too strict is the subject of several open questions below. #### Unresolved questions [unresolved]: #unresolved-questions ##### Virtual methods and overriding What do we do when a virtual method is marked as `BinaryCompatOnly`? Overrides in a derived class may not even be in the current assembly, and it could be that the user is looking to introduce a new version of a method that, for example, only differs by return type, something that C# does not normally allow overloading on. What happens to any overrides of that previous method on recompile? Are they allowed to override the `BinaryCompatOnly` member if they're also marked as `BinaryCompatOnly`? ##### Use within the same DLL This proposal states that `BinaryCompatOnly` members are not visible anywhere, not even in the assembly currently being compiled. Is that too strict, or do `BinaryCompatAttribute` members need to possibly chain to one another? ##### Implicitly implementing interface members Should `BinaryCompatOnly` members be able to implement interface members? Or should they be prevented from doing so. This would require that, when a user wants to turn an implicit interface implementation into `BinaryCompatOnly`, they would additionally have to provide an explicit interface implementation, likely cloning the same body as the `BinaryCompatOnly` member as the explicit interface implementation would not be able to see the original member anymore. ##### Implementing interface members marked `BinaryCompatOnly` What do we do when an interface member has been marked as `BinaryCompatOnly`? The type still needs to provide an implementation for that member; it may be that we must simply say that interface members cannot be marked as `BinaryCompatOnly`.
================================================ FILE: proposals/csharp-13.0/params-collections.md ================================================ # `params Collections` [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary In C# 12 language added support for creating instances of collection types beyond just arrays. See [collection expressions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md). This proposal extends `params` support to all such collection types. ## Motivation A `params` array parameter provides a convenient way to call a method that takes an arbitrary length list of arguments. Today `params` parameter must be an array type. However, it might be beneficial for a developer to be able to have the same convenience when calling APIs that take other collection types. For example, an `ImmutableArray`, `ReadOnlySpan`, or plain `IEnumerable`. Especially in cases when compiler is able to avoid an implicit array allocation for the purpose of creating the collection (`ImmutableArray`, `ReadOnlySpan`, etc). Today, in situations when an API takes a collection type, developers usually add a `params` overload that takes an array, construct the target collection and call the original overload with that collection, thus consumers of the API have to trade an extra array allocation for convenience. Another motivation is ability to add a params span overload and have it take precedence over the array version, just by recompiling existing source code. ## Detailed design ### Method parameters The [Method parameters](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/classes.md#1562-method-parameters) section is adjusted as follows. ```diff ANTLR formal_parameter_list : fixed_parameters - | fixed_parameters ',' parameter_array + | fixed_parameters ',' parameter_collection - | parameter_array + | parameter_collection ; -parameter_array +parameter_collection - : attributes? 'params' array_type identifier + : attributes? 'params' 'scoped'? type identifier ; ``` A *parameter_collection* consists of an optional set of *attributes*, a `params` modifier, an optional `scoped` modifier, a *type*, and an *identifier*. A parameter collection declares a single parameter of the given type with the given name. The *type* of a parameter collection shall be one of the following valid target types for a collection expression (see https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions): - A single dimensional *array type* `T[]`, in which case the *element type* is `T` - A *span type* - `System.Span` - `System.ReadOnlySpan` in which cases the *element type* is `T` - A *type* with an appropriate *[create method](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#create-methods)* that can be invoked with no additional arguments, which is at least as accessible as the declaring member, and with a corresponding *element type* resulting from that determination - A *struct* or *class type* that implements `System.Collections.IEnumerable` where: - The *type* has a constructor that can be invoked with no arguments, and the constructor is at least as accessible as the declaring member. - The *type* has an instance (not an extension) method `Add` where: - The method can be invoked with a single value argument. - If the method is generic, the type arguments can be inferred from the argument. - The method is at least as accessible as the declaring member. In which case the *element type* is the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/statements.md#1395-the-foreach-statement) of the *type*. - An *interface type* - `System.Collections.Generic.IEnumerable`, - `System.Collections.Generic.IReadOnlyCollection`, - `System.Collections.Generic.IReadOnlyList`, - `System.Collections.Generic.ICollection`, - `System.Collections.Generic.IList` in which cases the *element type* is `T` In a method invocation, a parameter collection permits either a single argument of the given parameter type to be specified, or it permits zero or more arguments of the collection's *element type* to be specified. Parameter collections are described further in *[Parameter collections](#parameter-collections)*. A *parameter_collection* may occur after an optional parameter, but cannot have a default value – the omission of arguments for a *parameter_collection* would instead result in the creation of an empty collection. ### Parameter collections The [Parameter arrays](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/classes.md#15626-parameter-arrays) section is renamed and adjusted as follows. A parameter declared with a `params` modifier is a parameter collection. If a formal parameter list includes a parameter collection, it shall be the last parameter in the list and it shall be of type specified in *[Method parameters](#method-parameters)* section. > *Note*: It is not possible to combine the `params` modifier with the modifiers `in`, `out`, or `ref`. *end note* A parameter collection permits arguments to be specified in one of two ways in a method invocation: - The argument given for a parameter collection can be a single expression that is implicitly convertible to the parameter collection type. In this case, the parameter collection acts precisely like a value parameter. - Alternatively, the invocation can specify zero or more arguments for the parameter collection, where each argument is an expression that is implicitly convertible to the parameter collection's *element type*. In this case, the invocation creates an instance of the parameter collection type according to the rules specified in [Collection expressions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md) as though the arguments were used as expression elements in a collection expression in the same order, and uses the newly created collection instance as the actual argument. When constructing the collection instance, the original *unconverted* arguments are used. Except for allowing a variable number of arguments in an invocation, a parameter collection is precisely equivalent to a value parameter of the same type. When performing overload resolution, a method with a parameter collection might be applicable, either in its normal form or in its expanded form. The expanded form of a method is available only if the normal form of the method is not applicable and only if an applicable method with the same signature as the expanded form is not already declared in the same type. A potential ambiguity arises between the normal form and the expanded form of the method with a single parameter collection argument when it can be used as the parameter collection itself and as the element of the parameter collection at the same time. The ambiguity presents no problem, however, since it can be resolved by inserting a cast or using a collection expression, if needed. ### Signatures and overloading All the rules around `params` modifier in [Signatures and overloading](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/basic-concepts.md#76-signatures-and-overloading) remain as is. ### Applicable function member The [Applicable function member](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12642-applicable-function-member) section is adjusted as follows. If a function member that includes a parameter collection is not applicable in its normal form, the function member might instead be applicable in its ***expanded form***: - If parameter collection is not an array, an expanded form is not applicable for language versions C# 12 and below. - The expanded form is constructed by replacing the parameter collection in the function member declaration with zero or more value parameters of the parameter collection's *element type* such that the number of arguments in the argument list `A` matches the total number of parameters. If `A` has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and is thus not applicable. - Otherwise, the expanded form is applicable if for each argument in `A`, one of the following is true: - the parameter-passing mode of the argument is identical to the parameter-passing mode of the corresponding parameter, and - for a fixed value parameter or a value parameter created by the expansion, an implicit conversion exists from the argument expression to the type of the corresponding parameter, or - for an `in`, `out`, or `ref` parameter, the type of the argument expression is identical to the type of the corresponding parameter. - the parameter-passing mode of the argument is value, and the parameter-passing mode of the corresponding parameter is input, and an implicit conversion exists from the argument expression to the type of the corresponding parameter ### Better function member The [Better function member](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12643-better-function-member) section is adjusted as follows. Given an argument list `A` with a set of argument expressions `{E₁, E₂, ..., Eᵥ}` and two applicable function members `Mᵥ` and `Mₓ` with parameter types `{P₁, P₂, ..., Pᵥ}` and `{Q₁, Q₂, ..., Qᵥ}`, `Mᵥ` is defined to be a ***better function member*** than `Mₓ` if - for each argument, the implicit conversion from `Eᵥ` to `Qᵥ` is not better than the implicit conversion from `Eᵥ` to `Pᵥ`, and - for at least one argument, the conversion from `Eᵥ` to `Pᵥ` is better than the conversion from `Eᵥ` to `Qᵥ`. In case the parameter type sequences `{P₁, P₂, ..., Pᵥ}` and `{Q₁, Q₂, ..., Qᵥ}` are equivalent (i.e., each `Pᵢ` has an identity conversion to the corresponding `Qᵢ`), the following tie-breaking rules are applied, in order, to determine the better function member. - If `Mᵢ` is a non-generic method and `Mₑ` is a generic method, then `Mᵢ` is better than `Mₑ`. - Otherwise, if `Mᵢ` is applicable in its normal form and `Mₑ` has a params collection and is applicable only in its expanded form, then `Mᵢ` is better than `Mₑ`. - Otherwise, if both methods have params collections and are applicable only in their expanded forms, and if the params collection of `Mᵢ` has fewer elements than the params collection of `Mₑ`, then `Mᵢ` is better than `Mₑ`. - Otherwise, if `Mᵥ` has more specific parameter types than `Mₓ`, then `Mᵥ` is better than `Mₓ`. Let `{R1, R2, ..., Rn}` and `{S1, S2, ..., Sn}` represent the uninstantiated and unexpanded parameter types of `Mᵥ` and `Mₓ`. `Mᵥ`’s parameter types are more specific than `Mₓ`s if, for each parameter, `Rx` is not less specific than `Sx`, and, for at least one parameter, `Rx` is more specific than `Sx`: - A type parameter is less specific than a non-type parameter. - Recursively, a constructed type is more specific than another constructed type (with the same number of type arguments) if at least one type argument is more specific and no type argument is less specific than the corresponding type argument in the other. - An array type is more specific than another array type (with the same number of dimensions) if the element type of the first is more specific than the element type of the second. - Otherwise if one member is a non-lifted operator and the other is a lifted operator, the non-lifted one is better. - If neither function member was found to be better, and all parameters of `Mᵥ` have a corresponding argument whereas default arguments need to be substituted for at least one optional parameter in `Mₓ`, then `Mᵥ` is better than `Mₓ`. - If for at least one parameter `Mᵥ` uses the ***better parameter-passing choice*** ([§12.6.4.4](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12644-better-parameter-passing-mode)) than the corresponding parameter in `Mₓ` and none of the parameters in `Mₓ` use the better parameter-passing choice than `Mᵥ`, `Mᵥ` is better than `Mₓ`. - **Otherwise, if both methods have params collections and are applicable only in their expanded forms then `Mᵢ` is better than `Mₑ` if the same set of arguments corresponds to the collection elements for both methods, and one of the following holds (this corresponds to https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md):** - **both params collections are not *span_type*s, and an implicit conversion exists from params collection of `Mᵢ` to params collection of `Mₑ`** - **params collection of `Mᵢ` is `System.ReadOnlySpan`, and params collection of `Mₑ` is `System.Span`, and an identity conversion exists from `Eᵢ` to `Eₑ`** - **params collection of `Mᵢ` is `System.ReadOnlySpan` or `System.Span`, and params collection of `Mₑ` is an *[array_or_array_interface__type](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution)* with *element type* `Eₑ`, and an identity conversion exists from `Eᵢ` to `Eₑ`** - Otherwise, no function member is better. The reason why the new tie-breaking rule is placed at the end of the list is the last sub item > - **both params collections are not *span_type*s, and an implicit conversion exists from params collection of `Mᵢ` to params collection of `Mₑ`** it is applicable to arrays and, therefore, performing the tie-break earlier will introduce a behavior change for existing scenarios. For example: ``` C# class Program { static void Main() { Test(1); } static void Test(in int x, params C2[] y) {} // There is an implicit conversion from `C2[]` to `C1[]` static void Test(int x, params C1[] y) {} // Better candidate because of "better parameter-passing choice" } class C1 {} class C2 : C1 {} ``` If any of the previous tie-breaking rules apply (including the "better arguments conversions" rule), the overload resolution result can be different by comparison to the case when an explicit collection expression is used as an argument instead. For example: ``` C# class Program { static void Test1() { M1(['1', '2', '3']); // IEnumerable overload is used because `char` is an exact match M1('1', '2', '3'); // IEnumerable overload is used because `char` is an exact match } static void M1(params IEnumerable value) {} static void M1(params System.ReadOnlySpan value) {} class MyChar { private readonly int _i; public MyChar(int i) { _i = i; } public static implicit operator MyChar(int i) => new MyChar(i); public static implicit operator char(MyChar c) => (char)c._i; } static void Test2() { M2([1]); // Span overload is used M2(1); // Array overload is used, not generic } static void M2(params System.Span y){} static void M2(params int[] y){} static void Test3() { M3("3", ["4"]); // Ambiguity, better-ness of argument conversions goes in opposite directions. M3("3", "4"); // Ambiguity, better-ness of argument conversions goes in opposite directions. // Since parameter types are different ("object, string" vs. "string, object"), tie-breaking rules do not apply } static void M3(object x, params string[] y) {} static void M3(string x, params Span y) {} } ``` However, our primary concern are scenarios where overloads differ only by params collection type, but the collection types have the same element type. The behavior should be consistent with explicit collection expressions for these cases. The "**if the same set of arguments corresponds to the collection elements for both methods**" condition is important for scenarios like: ``` C# class Program { static void Main() { Test(x: 1, y: 2); // Ambiguous } static void Test(int x, params System.ReadOnlySpan y) {} static void Test(int y, params System.Span x) {} } ``` It doesn't feel reasonable to "compare" collections that are built from different elements. >This section was reviewed at [LDM](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-29.md#better-function-member-changes) >and was approved. One effect of these rules is that when `params` of different element types are exposed, these will be ambiguous when called with an empty argument list. For example: ```cs class Program { static void Main() { // Old scenarios C.M1(); // Ambiguous since params arrays were introduced C.M1([]); // Ambiguous since params arrays were introduced // New scenarios C.M2(); // Ambiguous in C# 13 C.M2([]); // Ambiguous in C# 13 C.M3(); // Ambiguous in C# 13 C.M3([]); // Ambiguous in C# 13 } public static void M1(params int[] a) { } public static void M1(params int?[] a) { } public static void M2(params ReadOnlySpan a) { } public static void M2(params Span a) { } public static void M3(params ReadOnlySpan a) { } public static void M3(params ReadOnlySpan a) { } } ``` Given that we prioritize element type over all else, this seems reasonable; there's nothing to tell the language whether the user would prefer `int?` over `int` in this scenario. ### Dynamic Binding Expanded forms of candidates utilizing non-array params collections won't be considered as valid candidates by the current C# runtime binder. If the *primary_expression* does not have compile-time type `dynamic`, then the method invocation undergoes a limited compile-time check as described in [§12.6.5 Compile-time checking of dynamic member invocation](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#1265-compile-time-checking-of-dynamic-member-invocation). If only a single candidate passes the test, the invocation of the candidate is statically bound when all the following conditions are met: - the candidate is a local function - the candidate is either not generic, or its type arguments are explicitly specified; - there is no ambiguity between normal and expanded forms of the candidate that cannot be resolved at compile time. Otherwise, the *invocation_expression* is dynamically bound. If only a single candidate passed the test above: - if that candidate is a local function, a compile-time error occurs; - if that candidate is applicable only in expanded form utilizing non-array params collections, a compile-time error occurs. We also should consider reverting/fixing spec violation that affects local functions today, see https://github.com/dotnet/roslyn/issues/71399. >[LDM](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-29.md#dynamic-and-ref-local-function-bugfixing) >confirmed that we want to fix this spec violation. ### Expression trees Collection expressions are not supported in expression trees. Similarly, expanded forms of non-array params collections will not be supported in expression trees. We will not be changing how the compiler binds lambdas for expression trees with the goal to avoid usage of APIs utilizing expanded forms of non-array params collections. ### Order of evaluation with non-array collections in non-trivial scenarios > This section was reviewed at [LDM](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-31.md#params-collections-evaluation-orders) > and was approved. Despite the fact that array cases deviate from other collections, the official language specification > doesn't have to specify different rules for arrays. The deviations could simply be treated as an implementation artifact. > At the same time we do not intend to change the existing behavior around arrays. #### Named arguments A collection instance is created and populated after the lexically previous argument is evaluated, but before the lexically following argument is evaluated. For example: ``` C# class Program { static void Main() { Test(b: GetB(), c: GetC(), a: GetA()); } static void Test(int a, int b, params MyCollection c) {} static int GetA() => 0; static int GetB() => 0; static int GetC() => 0; } ``` The order of evaluation is the following: 1. `GetB` is called 2. `MyCollection` is created and populated, `GetC` is called in the process 3. `GetA` is called 4. `Test` is called Note, in params array case, the array is created right before the target method is invoked, after all arguments are evaluated in their lexical order. #### Compound assignment A collection instance is created and populated after the lexically previous index is evaluated, but before the lexically following index is evaluated. The instance is used to invoke getter and setter of the target indexer. For example: ``` C# class Program { static void Test(Program p) { p[GetA(), GetC()]++; } int this[int a, params MyCollection c] { get => 0; set {} } static int GetA() => 0; static int GetC() => 0; } ``` The order of evaluation is the following: 1. `GetA` is called and cached 2. `MyCollection` is created, populated and cached, `GetC` is called in the process 3. Indexer's getter is invoked with cached values for indexes 4. Result is incremented 5. Indexer's setter is invoked with cached values for indexes and the result of the increment An example with an empty collection: ``` C# class Program { static void Test(Program p) { p[GetA()]++; } int this[int a, params MyCollection c] { get => 0; set {} } static int GetA() => 0; } ``` The order of evaluation is the following: 1. `GetA` is called and cached 2. An empty `MyCollection` is created and cached 3. Indexer's getter is invoked with cached values for indexes 4. Result is incremented 5. Indexer's setter is invoked with cached values for indexes and the result of the increment #### Object Initializer A collection instance is created and populated after the lexically previous index is evaluated, but before the lexically following index is evaluated. The instance is used to invoke indexer's getter as many times as necessary, if any. For example: ``` C# class C1 { public int F1; public int F2; } class Program { static void Test() { _ = new Program() { [GetA(), GetC()] = { F1 = GetF1(), F2 = GetF2() } }; } C1 this[int a, params MyCollection c] => new C1(); static int GetA() => 0; static int GetC() => 0; static int GetF1() => 0; static int GetF2() => 0; } ``` The order of evaluation is the following: 1. `GetA` is called and cached 2. `MyCollection` is created, populated and cached, `GetC` is called in the process 3. Indexer's getter is invoked with cached values for indexes 4. `GetF1` is evaluated and assigned to `F1` field of `C1` retuned on the previous step 5. Indexer's getter is invoked with cached values for indexes 6. `GetF2` is evaluated and assigned to `F2` field of `C1` retuned on the previous step Note, in params array case, its elements are evaluated and cached, but a new instance of an array (with the same values inside) is used for each invocation of indexer's getter instead. For the example above, the order of evaluation is the following: 1. `GetA` is called and cached 2. `GetC` is called and cached 3. Indexer's getter is invoked with cached `GetA` and new array populated with cached `GetC` 4. `GetF1` is evaluated and assigned to `F1` field of `C1` retuned on the previous step 5. Indexer's getter is invoked with cached `GetA` and new array populated with cached `GetC` 6. `GetF2` is evaluated and assigned to `F2` field of `C1` retuned on the previous step An example with an empty collection: ``` C# class C1 { public int F1; public int F2; } class Program { static void Test() { _ = new Program() { [GetA()] = { F1 = GetF1(), F2 = GetF2() } }; } C1 this[int a, params MyCollection c] => new C1(); static int GetA() => 0; static int GetF1() => 0; static int GetF2() => 0; } ``` The order of evaluation is the following: 1. `GetA` is called and cached 2. An empty `MyCollection` is created and cached 3. Indexer's getter is invoked with cached values for indexes 4. `GetF1` is evaluated and assigned to `F1` field of `C1` retuned on the previous step 5. Indexer's getter is invoked with cached values for indexes 6. `GetF2` is evaluated and assigned to `F2` field of `C1` retuned on the previous step ### Ref safety The [collection expressions ref safety section](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#ref-safety) is applicable to the construction of parameter collections when APIs are invoked in their expanded form. Params parameters are implicitly `scoped` when their type is a ref struct. UnscopedRefAttribute can be used to override that. ### Metadata In metadata we could mark non-array `params` parameters with `System.ParamArrayAttribute`, as `params` arrays are marked today. However, it looks like we will be much safer to use a different attribute for non-array `params` parameters. For example, the current VB compiler will not be able to consume them decorated with `ParamArrayAttribute` neither in normal, nor in expanded form. Therefore, an addition of 'params' modifier is likely to break VB consumers, and very likely consumers from other languages or tools. Given that, non-array `params` parameters are marked with a new `System.Runtime.CompilerServices.ParamCollectionAttribute`. ``` C# namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] public sealed class ParamCollectionAttribute : Attribute { public ParamCollectionAttribute() { } } } ``` > This section was reviewed at [LDM](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#metadata-format) > and was approved. ## Open questions ### Stack allocations Here is a quote from https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions: "Stack allocations for huge collections might blow the stack. Should the compiler have a heuristic for placing this data on the heap? Should the language be unspecified to allow for this flexibility? We should follow the spec for [`params Span`](https://github.com/dotnet/csharplang/issues/1757)." It sounds like we have to answer the questions in context of this proposal. ### [Resolved] Implicitly `scoped` params There was a suggestion that, when `params` modifies a `ref struct` parameter, it should be considered as declared `scoped`. The argument is made that number of cases where you want the parameter to be scoped is virtually 100% when looking through the BCL cases. In a few cases that need that, the default could be overwritten with `[UnscopedRef]`. However, it might be undesirable to change the default simply based on presence of `params` modifier. Especially, that in overrides/implements scenarios `params` modifier doesn't have to match. #### Resolution: Params parameters are implicitly scoped - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements. ### [Resolved] Consider enforcing `scoped` or `params` across overrides We've previously stated that `params` parameters should be `scoped` by default. However, this introduces odd behavior in overriding, due to our existing rules around restating `params`: ```cs class Base { internal virtual Span M1(scoped Span s1, params Span s2) => throw null!; } class Derived : Base { internal override Span M1(Span s1, // Error, missing `scoped` on override Span s2 // Proposal: Error: parameter must include either `params` or `scoped` ) => throw null!; } ``` We have a difference in behavior between carrying the `params` and carrying the `scoped` across overrides here: `params` is inherited implicitly, and with it `scoped`, while `scoped` by itself is _not_ inherited implicitly and must be repeated at every level. **Proposal**: We should enforce that overrides of `params` parameters must explicitly state `params` or `scoped` if the original definition is a `scoped` parameter. In other words, `s2` in `Derived` must have `params`, `scoped`, or both. #### Resolution: We will require explicitly stating `scoped` or `params` on override of a `params` parameter when a non-`params` parameter would be required to do so - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides. ### [Resolved] Should presence of required members prevent declaration of `params` parameter? Consider the following example: ``` C# using System.Collections; using System.Collections.Generic; public class MyCollection1 : IEnumerable { IEnumerator IEnumerable.GetEnumerator() => throw null; IEnumerator IEnumerable.GetEnumerator() => throw null; public void Add(long l) => throw null; public required int F; // Collection has required member and constructor doesn't initialize it explicitly } class Program { static void Main() { Test(2, 3); // error CS9035: Required member 'MyCollection1.F' must be set in the object initializer or attribute constructor. } // Proposal: An error is reported for the parameter indicating that the constructor that is required // to be available doesn't initialize required members. In other words, one is able // to declare such a parameter under the specified conditions. static void Test(params MyCollection1 a) { } } ``` #### Resolution: We will validate `required` members against the constructor that is used to determine eligibility to be a `params` parameter at the declaration site - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters. ## Alternatives There is an alternative [proposal](https://github.com/dotnet/csharplang/blob/main/proposals/rejected/params-span.md) that extends `params` only for `ReadOnlySpan`. Also, one might say, that with [collection expressions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md) now in the language, there is no need to extend `params` support at all. For any collection type. To consume an API with collection type, a developer simply needs to add two characters, `[` before the expanded list of arguments, and `]` after it. Given that, extending `params` support might be an overkill, especially that other languages are unlikely to support consumption of non-array `params` parameters any time soon. ## Related proposals - https://github.com/dotnet/csharplang/issues/1757 - https://github.com/dotnet/csharplang/blob/main/proposals/rejected/format.md#extending-params ## Related design meetings - https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-08.md - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-10.md - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-29.md - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-01-31.md#params-collections-evaluation-orders - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-collections - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-22.md#effect-of-language-version-on-overload-resolution-in-presence-of-params-collections - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-04-24.md#adjust-dynamic-binding-rules-for-a-situation-of-a-single-applicable-candidate - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-01.md#adjust-binding-rules-in-the-presence-of-a-single-candidate - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-03.md#params-collections-and-dynamic - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md#params-span-breaks - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#params-span-breaks ================================================ FILE: proposals/csharp-13.0/partial-properties.md ================================================ # Partial properties [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ### Grammar The *property_declaration* grammar [(§14.7.1)](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/classes.md#1471-general) is updated as follows: ```diff property_declaration - : attributes? property_modifier* type member_name property_body + : attributes? property_modifier* 'partial'? type member_name property_body ; ``` **Remarks**: This is somewhat similar to how *method_header* [(§15.6.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1561-general) and *class_declaration* [(§15.2.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1521-general) are specified. (Note that [Issue #946](https://github.com/dotnet/csharplang/issues/946) proposes to relax the ordering requirement, and would probably apply to all declarations which allow the `partial` modifier. We intend to specify such an ordering relaxation in the near future, and implement it in the same release that this feature is implemented.) ### Defining and implementing declarations When a property declaration includes a *partial* modifier, that property is said to be a *partial property*. Partial properties may only be declared as members of partial types. A *partial property* declaration is said to be a *defining declaration* when its accessors all have semicolon bodies, and it lacks the `extern` modifier. Otherwise, it is an *implementing declaration*. ```cs partial class C { // Defining declaration public partial string Prop { get; set; } // Implementing declaration public partial string Prop { get => field; set => field = value; } } ``` Because we have reserved the syntactic form with semicolon accessor bodies for the *defining declaration*, a partial property cannot be *automatically implemented*. We therefore adjust [Automatically implemented properties (§15.7.4)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1574-automatically-implemented-properties) as follows: > An automatically implemented property (or auto-property for short), is a non-abstract, non-extern, **non-partial,** non-ref-valued property with semicolon-only accessor bodies. **Remarks**. It is useful for the compiler to be able to look at a single declaration in isolation and know whether it is a defining or an implementing declaration. Therefore we don't want to permit auto-properties by including two identical `partial` property declarations, for example. We don't think that the use cases for this feature involve implementing the partial property with an auto-property, but in cases where a trivial implementation is desired, we think the `field` keyword makes things simple enough. --- A partial property must have one *defining declaration* and one *implementing declaration*. **Remarks**. We also don't think it is useful to allow splitting the declaration across more than two parts, to allow different accessors to be implemented in different places, for example. Therefore we simply imitate the scheme established by partial methods. --- Only the defining declaration of a partial property participates in lookup, similar to how only the defining declaration of a partial method participates in overload resolution. **Remarks**. In the compiler, we would expect that only the symbol for the defining declaration appears in the member list, and the symbol for the implementing part can be accessed through the defining symbol. However, some features like nullable analysis might *see through* to the implementing declaration in order to provide more useful behavior. ```cs partial class C { public partial string Prop { get; set; } public partial string Prop { get => field; set => field = value; } public C() // warning CS8618: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. { } } ``` --- A partial property is not permitted to have the `abstract` modifier. A partial property cannot explicitly implement interface properties. ### Attribute merging Similar to partial methods, the attributes in the resulting property are the combined attributes of the parts are concatenated in an unspecified order, and duplicates are not removed. ### Caller-info attributes We adjust the following language from the [standard](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/attributes.md#22551-general): > It is an error to have the same caller-info attribute on a parameter of both the defining and implementing part of a partial ~~method~~ **member** declaration. Only caller-info attributes in the defining part are applied, whereas caller-info attributes occurring only in the implementing part are ignored. - The described error falls out from the definitions of these attributes not having `AllowMultiple = true`. Using them multiple times, including across partial declarations, results in an error. - When caller-info attributes are applied to a parameter in the implementation part of a partial method, the Roslyn compiler reports a warning. It will also report a warning for the same scenario in a partial property. ### Matching signatures The LDM meeting on [14th September 2020](https://github.com/dotnet/csharplang/blob/main/meetings/2020/LDM-2020-09-14.md#partial-method-signature-matching) defined a set of "strict" requirements for signature matching of partial methods, which were introduced in a warning wave. Partial properties have analogous requirements to partial methods for signature matching as much as is possible, except that all of the diagnostics for mismatch are reported by default, and are not held behind a warning wave. Signature matching requirements include: 1. Type and ref kind differences between partial property declarations which are significant to the runtime result in a compile-time error. 2. Differences in tuple element names within partial property declarations results in a compile-time error, same as for partial methods. 3. The property declarations and their accessor declarations must have the same modifiers, though the modifiers may appear in a different order. - Exception: this does not apply to the `extern` modifier, which may only appear on an *implementing declaration*. 4. All other syntactic differences in the signatures of partial property declarations result in a compile-time warning, with the following exceptions: - Attribute lists on or within partial property declarations do not need to match. Instead, merging of attributes in corresponding positions is performed, per [Attribute merging](#attribute-merging). - Nullable context differences do not cause warnings. In other words, a difference where one of the types is nullable-oblivious and the other type is either nullable-annotated or not-nullable-annotated does not result in any warnings. - Default parameter values do not need to match. A warning is reported when the implementation part of a partial indexer has default parameter values. This is similar to an existing warning which occurs when the implementation part of a partial method has default parameter values. 5. A warning occurs when parameter names differ across defining and implementing declarations. The parameter names from the definition part are used at use sites and in emit. 6. Nullability differences which do not involve oblivious nullability result in warnings. When analyzing an accessor body, the implementation part signature is used. The definition part signature is used when analyzing use sites and in emit. This is consistent with partial methods. ```cs partial class C1 { public partial string Prop { get; private set; } // Error: accessor modifier mismatch in 'set' accessor of 'Prop' public partial string Prop { get => field; set => field = value; } } partial class C2 { public partial string Prop { get; init; } // Error: implementation of 'Prop' must have an 'init' accessor to match definition public partial string Prop { get => field; set => field = value; } } partial class C3 { public partial string Prop { get; } // Error: implementation of 'Prop' cannot have a 'set' accessor because the definition does not have a 'set' accessor. public partial string Prop { get => field; set => field = value; } } partial class C4 { public partial string this[string s = "a"] { get; set; } public partial string this[string s] { get => s; set { } } // ok public partial string this[int i, string s = "a"] { get; set; } public partial string this[int i, string s = "a"] { get => s; set { } } // CS1066: The default value specified for parameter 's' will have no effect because it applies to a member that is used in contexts that do not allow optional arguments } ``` ### Documentation comments We want the behavior of doc comments on partial properties to be consistent with what we shipped for partial methods. That behavior is detailed in https://github.com/dotnet/csharplang/issues/5193. It is permitted to include doc comments on either the definition or implementation part of a partial property. (Note that doc comments are not supported on property accessors.) When doc comments are present on only one of the parts of the property, those doc comments are used normally (surfaced through `ISymbol.GetDocumentationCommentXml()`, written out to the documentation XML file, etc.). When doc comments are present on both parts, all the doc comments on the definition part are dropped, and only the doc comments on the implementation part are used. For example, the following program: ```cs /// /// My type /// partial class C { /// Definition part comment /// Return value comment public partial int Prop { get; set; } /// Implementation part comment public partial int Prop { get => 1; set { } } } ``` Results in the following XML documentation file: ```xml ConsoleApp1 My type Implementation part comment ``` When parameter names differ between partial declarations, `` elements use the parameter names from the declaration associated with the documentation comment in source code. For example, a paramref on a doc comment placed on an implementing declaration refers to the parameter symbols on the implementing declaration using their parameter names. This is consistent with partial methods. ```cs /// /// My type /// partial class C { public partial int this[int x] { get; set; } /// /// // warning CS1734: XML comment on 'C.this[int]' has a paramref tag for 'x', but there is no parameter by that name /// // ok. 'Go To Definition' will go to 'int y'. /// public partial int this[int y] { get => 1; set { } } // warning CS9256: Partial property declarations 'int C.this[int x]' and 'int C.this[int y]' have signature differences. } ``` Results in the following XML documentation file: ```xml ConsoleApp1 My type // warning CS1734: XML comment on 'C.this[int]' has a paramref tag for 'x', but there is no parameter by that name // ok. 'Go To Definition' will go to 'int y'. ``` This can be confusing, because the metadata signature will use parameter names from the definition part. It is recommended to ensure that parameter names match across parts to avoid this confusion. ### Indexers Per [LDM meeting on 2nd November 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-11-02.md#partial-properties), indexers will be supported with this feature. The indexers grammar is modified as follows: ```diff indexer_declaration - : attributes? indexer_modifier* indexer_declarator indexer_body + : attributes? indexer_modifier* 'partial'? indexer_declarator indexer_body - | attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body + | attributes? indexer_modifier* 'partial'? ref_kind indexer_declarator ref_indexer_body ; ``` Partial indexer parameters must match across declarations per the same rules as [Matching signatures](#matching-signatures). [Attribute merging](#attribute-merging) is performed across partial indexer parameters. ```cs partial class C { public partial int this[int x] { get; set; } public partial int this[int x] { get => this._store[x]; set => this._store[x] = value; } } // attribute merging partial class C { public partial int this[[Attr1] int x] { [Attr2] get; set; } public partial int this[[Attr3] int x] { get => this._store[x]; [Attr4] set => this._store[x] = value; } // results in a merged member emitted to metadata: public int this[[Attr1, Attr3] int x] { [Attr2] get => this._store[x]; [Attr4] set => this._store[x] = value; } } ``` ## Open Issues ### Other member kinds A community member opened a discussion to request support for [partial events](https://github.com/dotnet/csharplang/discussions/8064). In the [LDM meeting on 2nd November 2022](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-11-02.md#partial-properties), we decided to punt on support for events, in part because nobody at the time had requested it. We may want to revisit this question, since this request has now come in, and it has been over a year since we last discussed it. We could also go even further in permitting partial declarations of constructors, operators, fields, and so on, but it's unclear if the design burden of these is justified, just because we are already doing partial properties. ================================================ FILE: proposals/csharp-13.0/ref-struct-interfaces.md ================================================ # Ref Struct Interfaces [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary This proposal will expand the capabilities of `ref struct` such that they can implement interfaces and participate as generic type arguments. ## Motivation The inability for `ref struct` to implement interfaces means they cannot participate in fairly fundamental abstraction techniques of .NET. A `Span`, even though it has all the attributes of a sequential list cannot participate in methods that take `IReadOnlyList`, `IEnumerable`, etc ... Instead specific methods must be coded for `Span` that have virtually the same implementation. Allowing `ref struct` to implement interfaces will allow operations to be abstracted over them as they are for other types. ## Detailed Design ### ref struct interfaces The language will allow for `ref struct` types to implement interfaces. The syntax and rules are the same as for normal `struct` with a few exceptions to account for the limitations of `ref struct` types. The ability to implement interfaces does not impact the existing limitations against boxing `ref struct` instances. That means even if a `ref struct` implements a particular interface, it cannot be directly cast to it as that represents a boxing action. ```csharp ref struct File : IDisposable { private SafeHandle _handle; public void Dispose() { _handle.Dispose(); } } File f = ...; // Error: cannot box `ref struct` type `File` IDisposable d = f; ``` The ability to implement interfaces is only useful when combined with the ability for `ref struct` to participate in generic arguments (as [laid out later][ref-struct-generics]). To allow for interfaces to cover the full expressiveness of a `ref struct` and the lifetime issues they can present, the language will allow `[UnscopedRef]` to appear on interface methods and properties. This is necessary as it allows for interfaces that abstract over `struct` to have the same flexibility as using a `struct` directly. Consider the following example: ```csharp interface I1 { [UnscopedRef] ref int P1 { get; } ref int P2 { get; } } struct S1 { [UnscopedRef] internal ref int P1 { get {...} } internal ref int P2 { get {...} } } ref int M(T t, S1 s) where T : I1, allows ref struct { // Error: may return ref to t return ref t.P1; // Error: may return ref to t return ref s.P1; // Okay return ref t.P2; // Okay return ref s.P2; } ``` When a `struct` / `ref struct` member implements an interface member with a `[UnscopedRef]` attribute, the implementing member may also be decorated with `[UnscopedRef]` but it is not required. However a member with `[UnscopedRef]` may not be used to implement a member that lacks the attribute ([details][unscoped-ref-impl]). ```csharp interface I1 { [UnscopedRef] ref int P1 { get; } ref int P2 { get; } } struct S1 { internal ref int P1 { get {...} } internal ref int P2 { get {...} } } struct S2 { [UnscopedRef] internal ref int P1 { get {...} } internal ref int P2 { get {...} } } struct S3 : I1 { internal ref int P1 { get {...} } // Error: P2 is marked with [UnscopedRef] and cannot implement I1.P2 as is not marked // with [UnscopedRef] [UnscopedRef] internal ref int P2 { get {...} } } class C1 : I1 { internal ref int P1 { get {...} } internal ref int P2 { get {...} } } ``` Default interface methods pose a problem for `ref struct` as there are no protections against the default implementation boxing the `this` member. ```csharp interface I1 { void M() { // Danger: both of these box if I1 is implemented by a ref struct I1 local1 = this; object local2 = this; } } // Error: I1.M cannot implement interface member I1.M() for ref struct S ref struct S : I1 { } ``` To handle this a `ref struct` will be forced to implement all members of an interface, even if they have default implementations. The runtime will also be updated to throw an exception if a default interface member is called on a `ref struct` type. To avoid an exception at runtime the compiler will report an error for an invocation of a non-virtual instance method (or property) on a type parameter that allows ref struct. Here is an example: ```csharp public interface I1 { sealed void M3() {} } class C { static void Test2(T x) where T : I1, allows ref struct { #line 100 x.M3(); // (100,9): error: A non-virtual instance interface member cannot be accessed on a type parameter that allows ref struct. } } ``` There is also an open design question about reporting a [warning][warn-DIM] for an invocation of a virtual (not abstract) instance method (or property) on a type parameter that allows ref struct. Detailed Notes: - A `ref struct` can implement an interface - A `ref struct` cannot participate in default interface members - A `ref struct` cannot be cast to interfaces it implements as that is a boxing operation ### ref struct Generic Parameters ```ANTLR type_parameter_constraints_clause : 'where' type_parameter ':' type_parameter_constraints ; type_parameter_constraints : restrictive_type_parameter_constraints | allows_type_parameter_constraints_clause | restrictive_type_parameter_constraints ',' allows_type_parameter_constraints_clause restrictive_type_parameter_constraints : primary_constraint | secondary_constraints | constructor_constraint | primary_constraint ',' secondary_constraints | primary_constraint ',' constructor_constraint | secondary_constraints ',' constructor_constraint | primary_constraint ',' secondary_constraints ',' constructor_constraint ; primary_constraint : class_type | 'class' | 'struct' | 'unmanaged' ; secondary_constraints : interface_type | type_parameter | secondary_constraints ',' interface_type | secondary_constraints ',' type_parameter ; constructor_constraint : 'new' '(' ')' ; allows_type_parameter_constraints_clause : 'allows' allows_type_parameter_constraints allows_type_parameter_constraints : allows_type_parameter_constraint | allows_type_parameter_constraints ',' allows_type_parameter_constraint allows_type_parameter_constraint : ref_struct_clause ref_struct_clause : 'ref' 'struct' ``` The language will allow for generic parameters to opt into supporting `ref struct` as arguments by using the `allows ref struct` syntax inside a `where` clause: ```csharp T Identity(T p) where T : allows ref struct => p; // Okay Span local = Identity(new Span(new int[10])); ``` This is similar to other items in a `where` clause in that it specifies the capabilities of the generic parameter. The difference is other syntax items limit the set of types that can fulfill a generic parameter while `allows ref struct` expands the set of types. This is effectively an anti-constraint as it removes the implicit constraint that `ref struct` cannot satisfy a generic parameter. As such this is given a new syntax prefix, `allows`, to make that clearer. A type parameter bound by `allows ref struct` has all of the behaviors of a `ref struct` type: 1. Instances of it cannot be boxed 2. Instances participate in lifetime rules like a normal `ref struct` 3. The type parameter cannot be used in `static` fields, elements of an array, etc ... 4. Instances can be marked with `scoped` Examples of these rules in action: ```csharp interface I1 { } I1 M1(T p) where T : I1, allows ref struct { // Error: cannot box potential ref struct return p; } T M2(T p) where T : allows ref struct { Span span = stackalloc int[42]; // The safe-to-escape of the return is current method because one of the inputs is // current method T t = M3(span); // Error: the safe-to-escape is current method. return t; // Okay return default; return p; } R M3(Span span) where R : allows ref struct { return default; } ``` The anti-constraint is not "inherited" from a type parameter type constraint. For example, `S` in the code below cannot be substituted with a ref struct: ```csharp class C where T : allows ref struct where S : T {} ``` Detailed notes: - A `where T : allows ref struct` generic parameter cannot - Have `where T : U` where `U` is a known reference type - Have `where T : class` constraint - Cannot be used as a generic argument unless the corresponding parameter is also `where T: allows ref struct` - The `allows ref struct` must be the last constraint in the `where` clause - A type parameter `T` which has `allows ref struct` has all the same limitations as a `ref struct` type. ### Representation in metadata Type parameters allowing ref structs will be encoded in metadata as described in the [byref-like generics doc][byref-like-generics]. Specifically by using the `CorGenericParamAttr.gpAllowByRefLike(0x0020)` or `System.Reflection.GenericParameterAttributes.AllowByRefLike(0x0020)` flag value. Whether runtime supports the feature can be determined by checking presence of `System.Runtime.CompilerServices.RuntimeFeature.ByRefLikeGenerics` field. The APIs were added in https://github.com/dotnet/runtime/pull/98070. ### `using` statement A `using` statement will recognize and use implementation of `IDisposable` interface when resource is a ref struct. ```csharp ref struct S2 : System.IDisposable { void System.IDisposable.Dispose() { } } class C { static void Main() { using (new S2()) { } // S2.System.IDisposable.Dispose is called } } ``` Note that preference is given to a `Dispose` method that implements the pattern, and only if one is not found, `IDisposable` implementation is used. A `using` statement will recognize and use implementation of `IDisposable` interface when resource is a type parameter that `allows ref struct` and `IDisposable` is in its effective interfaces set. ```csharp class C { static void Test(T t) where T : System.IDisposable, allows ref struct { using (t) { } } } ``` Note that a pattern `Dispose` method will not be recognized on a type parameter that `allows ref struct` because an interface (and this is the only place where we could possibly look for a pattern) is not a ref struct. ```csharp interface IMyDisposable { void Dispose(); } class C { static void Test(T t, IMyDisposable s) where T : IMyDisposable, allows ref struct { using (t) // Error, the pattern is not recognized { } using (s) // Error, the pattern is not recognized { } } } ``` ### `await using` statement Currently language disallows using ref structs as resources in `await using` statement. The same limitation will be applied to a type parameter that `allows ref struct`. There is a proposal to lift general restrictions around usage of ref structs in async methods - https://github.com/dotnet/csharplang/pull/7994. The remainder of the section describes behavior after the general limitation for `await using` statement will be lifted, if/when that will happen. An `await using` statement will recognize and use implementation of `IAsyncDisposable` interface when resource is a ref struct. ```csharp ref struct S2 : IAsyncDisposable { ValueTask IAsyncDisposable.DisposeAsync() { } } class C { static async Task Main() { await using (new S2()) { } // S2.IAsyncDisposable.DisposeAsync } } ``` Note that preference is given to a `DisposeAsync` method that implements the pattern, and only if one is not found, `IAsyncDisposable` implementation is used. A pattern `DisposeAsync` method will be recognized on a type parameter that `allows ref struct` as it is recognized on type parameters without that constraint today. ```csharp interface IMyAsyncDisposable { ValueTask DisposeAsync(); } class C { static async Task Test() where T : IMyAsyncDisposable, new(), allows ref struct { await using (new T()) { } // IMyAsyncDisposable.DisposeAsync } } ``` A `using` statement will recognize and use implementation of `IAsyncDisposable` interface when resource is a type parameter that `allows ref struct`, the process of looking for `DisposeAsync` pattern method failed, and `IAsyncDisposable` is in type parameter's effective interfaces set. ```csharp interface IMyAsyncDisposable1 { ValueTask DisposeAsync(); } interface IMyAsyncDisposable2 { ValueTask DisposeAsync(); } class C { static async Task Test() where T : IMyAsyncDisposable1, IMyAsyncDisposable2, IAsyncDisposable, new(), allows ref struct { await using (new T()) { System.Console.Write(123); } // IAsyncDisposable.DisposeAsync } } ``` ### `foreach` statement The https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement section should be updated accordingly to incorporate the following. A `foreach` statement will recognize and use implementation of ```IEnumerable```/```IEnumerable``` interface when collection is a ref struct. ```csharp ref struct S : IEnumerable { IEnumerator IEnumerable.GetEnumerator() {...} System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {...} } class C { static void Main() { foreach (var i in new S()) // IEnumerable.GetEnumerator { } } } ``` A pattern `GetEnumerator` method will be recognized on a type parameter that `allows ref struct` as it is recognized on type parameters without that constraint today. ```csharp interface IMyEnumerable { IEnumerator GetEnumerator(); } class C { static void Test(T t) where T : IMyEnumerable, allows ref struct { foreach (var i in t) // IMyEnumerable.GetEnumerator { } } } ``` A `foreach` statement will recognize and use implementation of ```IEnumerable```/```IEnumerable``` interface when collection is a type parameter that `allows ref struct`, the process of looking for `GetEnumerator` pattern method failed, and ```IEnumerable```/```IEnumerable``` is in type parameter's effective interfaces set. ```csharp interface IMyEnumerable1 { IEnumerator GetEnumerator(); } interface IMyEnumerable2 { IEnumerator GetEnumerator(); } class C { static void Test(T t) where T : IMyEnumerable1, IMyEnumerable2, IEnumerable, allows ref struct { foreach (var i in t) // IEnumerable.GetEnumerator { } } } ``` An `enumerator` pattern will be recognized on a type parameter that `allows ref struct` as it is recognized on type parameters without that constraint today. ```csharp interface IGetEnumerator where TEnumerator : allows ref struct { TEnumerator GetEnumerator(); } class C { static void Test1(TEnumerable t) where TEnumerable : IGetEnumerator, allows ref struct where TEnumerator : IEnumerator, IDisposable, allows ref struct { foreach (var i in t) // IEnumerator.MoveNext/Current { } } static void Test2(TEnumerable t) where TEnumerable : IGetEnumerator, allows ref struct where TEnumerator : IEnumerator, allows ref struct { foreach (var i in t) // IEnumerator.MoveNext/Current { } } static void Test3(TEnumerable t) where TEnumerable : IGetEnumerator, allows ref struct where TEnumerator : IMyEnumerator, allows ref struct { foreach (var i in t) // IMyEnumerator.MoveNext/Current { } } } interface IMyEnumerator : System.IDisposable { T Current {get;} bool MoveNext(); } ``` A `foreach` statement will recognize and use implementation of `IDisposable` interface when enumerator is a ref struct. ```csharp struct S1 { public S2 GetEnumerator() { return new S2(); } } ref struct S2 : System.IDisposable { public int Current {...} public bool MoveNext() {...} void System.IDisposable.Dispose() {...} } class C { static void Main() { foreach (var i in new S1()) { } // S2.System.IDisposable.Dispose() } } ``` Note that preference is given to a `Dispose` method that implements the pattern, and only if one is not found, `IDisposable` implementation is used. A `foreach` statement will recognize and use implementation of `IDisposable` interface when enumerator is a type parameter that `allows ref struct` and `IDisposable` is in its effective interfaces set. ```csharp interface ICustomEnumerator { int Current {get;} bool MoveNext(); } interface IGetEnumerator where TEnumerator : allows ref struct { TEnumerator GetEnumerator(); } class C { static void Test(TEnumerable t) where TEnumerable : IGetEnumerator where TEnumerator : ICustomEnumerator, System.IDisposable, allows ref struct { foreach (var i in t) { } // System.IDisposable.Dispose() } } ``` Note that a pattern `Dispose` method will not be recognized on a type parameter that `allows ref struct` because an interface (and this is the only place where we could possibly look for a pattern) is not a ref struct. Also, since runtime doesn't provide a way to check whether at runtime a type parameter that `allows ref struct` implements `IDisposable` interface, a type parameter enumerator that `allows ref struct` will be disallowed, unless `IDisposable` is in its effective interfaces set. ```csharp interface ICustomEnumerator { int Current {get;} bool MoveNext(); } interface IMyDisposable { void Dispose(); } interface IGetEnumerator where TEnumerator : allows ref struct { TEnumerator GetEnumerator(); } class C { static void Test(TEnumerable t) where TEnumerable : IGetEnumerator where TEnumerator : ICustomEnumerator, IMyDisposable, allows ref struct { // error CS9507: foreach statement cannot operate on enumerators of type 'TEnumerator' // because it is a type parameter that allows ref struct and // it is not known at compile time to implement IDisposable. foreach (var i in t) { } } } ``` ### `await foreach` statement The https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement section should be updated accordingly to incorporate the following. An `await foreach` statement will recognize and use implementation of ```IAsyncEnumerable``` interface when collection is a ref struct. ```csharp ref struct S : IAsyncEnumerable { IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken token) {...} } class C { static async Task Main() { await foreach (var i in new S()) // S.IAsyncEnumerable.GetAsyncEnumerator { } } } ``` A pattern `GetAsyncEnumerator` method will be recognized on a type parameter that `allows ref struct` as it is recognized on type parameters without that constraint today. ```csharp interface IMyAsyncEnumerable { IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default); } class C { static async Task Test() where T : IMyAsyncEnumerable, allows ref struct { await foreach (var i in default(T)) // IMyAsyncEnumerable.GetAsyncEnumerator { } } } ``` An `await foreach` statement will recognize and use implementation of ```IAsyncEnumerable``` interface when collection is a type parameter that `allows ref struct`, the process of looking for `GetAsyncEnumerator` pattern method failed, and ```IAsyncEnumerable``` is in type parameter's effective interfaces set. ```csharp interface IMyAsyncEnumerable1 { IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default); } interface IMyAsyncEnumerable2 { IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default); } class C { static async Task Test() where T : IMyAsyncEnumerable1, IMyAsyncEnumerable2, IAsyncEnumerable, allows ref struct { await foreach (var i in default(T)) // IAsyncEnumerable.GetAsyncEnumerator { System.Console.Write(i); } } } ``` An `await foreach` statement will continue disallowing a ref struct enumerator and a type parameter enumerator that `allows ref struct`. The reason is the fact that the enumerator must be preserved across `await MoveNextAsync()` calls. ### Delegate type for the anonymous function or method group The https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#delegate-types section states: >The compiler may allow more signatures to bind to `System.Action<>` and `System.Func<>` types in the future (if `ref struct` types are allowed type arguments for instance). `Action<>` and `Func<>` types with `allows ref struct` constraints on their type parameters will be used in more scenarios involving ref struct types in the delegate's signature. If target runtime supports `allows ref struct` constraints, generic anonymous delegate types will include `allows ref struct` constraint for their type parameters. This will enable substitution of those type parameters with ref struct types and other type parameters with `allows ref struct` constraint. ### Inline arrays The https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/inline-arrays.md#detailed-design section states: >Language will provide a type-safe/ref-safe way for accessing elements of inline array types. The access will be span based. >This limits support to inline array types with element types that can be used as a type argument. When span types are changed to support spans of ref structs, the limitation should be lifted for inline arrays of ref structs. ## Soundness We would like to verify the soundness of both the `ref struct` anti-constraint in particular and the anti-constraint concept in general. To do so we'd like to take advantage of the existing soundness proofs provided for the C# type system. This task is made easier by defining a new language that is similar to C#, but more regular in construction. We will verify the safety of that model, and then specify a sound translation to this language. Because this new language is centered around constraints, we'll call this language "constraint-C#". The primary ref struct safety invariant that must be preserved is that variables of ref struct type must not appear on the heap. We can encode this restriction via a constraint. Because constraints permit substitution, not forbid it, we will technically define the inverse constraint: `heap`. The `heap` constraint specifies that a type may appear on the heap. In "constraint-C#" all types satisfy the `heap` constraint except for ref-structs. Moreover, all existing type parameters in C# will be lowered to type parameters with the `heap` constraint in "constraint-C#". Now, assuming that existing C# is safe, we can transfer the C# ref-struct rules to "constraint-C#". 1. Fields of classes cannot have a ref-struct type. 2. Static fields cannot have a ref-struct type. 3. Variables of ref-struct type cannot be converted to non-ref structs. 4. Variables of ref-struct type cannot be substituted as type arguments. 5. Variables of ref-struct type cannot implement interfaces. The new rules apply to the `heap` constraint: 1. Fields of classes must have types that satisfy the `heap` constraint. 2. Static fields must have types that satisfy the `heap` constraint. 3. Types with the `heap` constraint have only the identity conversion. 4. Variables of ref-struct type can only be substituted for type parameters without the `heap` constraint. 5. Ref-struct types may only implement interfaces without default-interface-members. Rules (4) and (5) are slightly altered. Note that rule (4) does not need to be transferred exactly because we have a notion of type parameters without the `heap` contraint. Rule (5) is complicated. Implementing interfaces is not universally unsound, but default interface methods imply a receiver of interface type, which is a non-value type and violates rule (3). Thus, default-interface-members are disallowed. With these rules, "constraint-C#" is ref-struct safe, supports type substitution, and supports interface implementation. The next step is to translate the language defined in this proposal, which we may call "allow-C#", into "constraint-C#". Fortunately, this is trivial. The lowering is a straightforward syntactic transformation. The syntax `where T : allows ref struct` in "allow-C#" is equivalent in "constraint-C#" to no constraint and the absence of "allow clauses" is equivalent to the `heap` constraint. Since the abstract semantics and typing are equivalent, "allow-C#" is also sound. There is one last property which we might consider: whether all typed terms in C# are also typed in "constraint-C#". In other words, we want to know if, for all terms `t` in C#, whether the corresponding term `t'` after lowering to "constraint-C#" is well-typed. This is not a soundness constraint -- making terms ill-typed in our target language would never allow unsafety -- rather, it concerns backwards-compatibility. If we decide to use the typing of "constraint-C#" to validate "allow-C#", we would like to confirm that we are not making any existing C# code illegal. Since all C# terms start as valid "constraint-C#" terms, we can validate preservation by examining each of our new "constraint-C#" restrictions. First, the addition of the `heap` constraint. Since all type parameters in C# would acquire the `heap` constraint, all existing terms must satisfy said constraint. This is true for all concrete types except ref structs, which is appropriate since ref structs may not appear as type arguments today. It is also true for all type parameters, since they would all themselves acquire the `heap` constraint. Moreover, since the `heap` constraint is a valid combination with all other constraints, this would not present any problems. Rules (1-5) would not present any problems since they directly correspond to existing C# rules, or are relaxations thereof. Therefore, all typeable terms in C# should be typeable in "constraint-C#" and we should not introduce any typing breaking changes. ## Open Issues ### Anti-Constraint syntax **Decision**: use `where T: allows ref struct` This proposal chose to expose the `ref struct` anti-constraint by augmenting the existing `where` syntax to include `allows ref struct`. This both succinctly describes the feature and is also expandable to include other anti-constraints in the future like pointers. There are other solutions considered that are worth discussing. The first is simply picking another syntax to use within the `where` clause. Other proposed options included: - `~ref struct`: the `~` serves as a marker that the syntax that follows is an anti-constraint. - `include ref struct`: using `includes` instead of `allows` ```csharp void M(T p) where T : IDisposable, ~ref struct { p.Dispose(); } ``` The second is to use a new clause entirely to make it clear that what follows is expanding the set of allowed types. Proponents of this feel that using syntax within `where` could lead to confusion when reading. The initial proposal used the following syntax: `allow T: ref struct`: ```csharp void M(T p) where T : IDisposable allow T : ref struct { p.Dispose(); } ``` The `where T: allows ref struct` syntax had a slightly stronger preference in LDM discussions. ### Co and contra variance **Decision**: no new issues To be maximally useful type parameters that are `allows ref struct` must be compatible with generic variance. Specifically it must be legal for a parameter to be both co/contravariant and also `allows ref struct`. Lacking that they would not be usable in many of the most popular `delegate` and `interface` types in .NET like `Func`, `Action`, `IEnumerable`, etc ... After discussion it was concluded this is a non-issue. The `allows ref struct` constraint is just another way that `struct` can be used as generic arguments. Just as a normal `struct` argument removes the variance of an API so will a `ref struct`. ### Auto-applying to delegate members **Decision**: do not auto-apply For many generic `delegate` members the language could automatically apply `allows ref struct` as it's purely an upside change. Consider that for `Func<> / Action<>` style delegates and most interface definitions there is no downside to expanding to allowing `ref struct`. The language can outline rules where it is safe to automatically apply this anti-constraint. This removes the manual process and would speed up the adoption of this feature. This auto application of `allows ref struct` poses a few problems though. The first is in multi-targeted scenarios. Code would compile in one target framework but fail in another and there is no syntactic indicator of why the APIs should behave differently. ```csharp // Works in net9.0 but fails in all other TF Func> func; ``` This is likely to lead to customer confusion and looking at changes in `Func` in the `net9.0` source wouldn't give customers any clue as to what changed. The other issue is that very subtle changes in code can cause _spooky action at a distance_ problems. Consider the following code: ```csharp interface I1 { } ``` This interface would be eligible for auto-application of `allows ref struct`. If a developer comes around later though and adds a default interface method then suddenly it would not be and it would break any consumers that had already created invocations like `I1>`. This is a very subtle change that would be hard to track down. ### Binary breaking change Adding `allows ref struct` to an existing API is not a source breaking change. It is purely expanding the set of allowed types for an API. Need to track down if this is a binary breaking change or not. Unclear if updating the attributes of a generic parameter constitute a binary breaking change. ### Warn on DIM invocation Should the compiler warn on the following invocation of `M` as it creates the opportunity for a runtime exception? ```csharp interface I1 { // Virtual method with default implementation void M() { } } // Invocation of a virtual instance method with default implementation in a generic method that has the `allows ref struct` // anti-constraint void M(T p) where T : allows ref struct, I1 { p.M(); // Warn? } ``` This, however, could be noisy and not very helpful in majority of scenarios. C# will require ref structs to implement all virtual APIs. Therefore, assuming that other players follow the same rule, the only situation when this might cause an exception is when the method is added after the fact. The author of the consuming code often has no knowledge of all these details and often has no control over ref structs that will be consumed by the code. Therefore, the only action the author can really take is to suppress the warning. ## Considerations ### Runtime support This feature requires several pieces of support from the runtime / libraries team: - Preventing default interface methods from applying to `ref struct` - API in `System.Reflection.Metadata` for encoding the `gpAcceptByRefLike` value - Support for generic parameters being a `ref struct` Most of this support is likely already in place. The general `ref struct` as generic parameter support is already implemented as described [here][byref-like-generics]. It's possible the DIM implementation already account for `ref struct`. But each of these items needs to be tracked down. ### API versioning #### allows ref struct anti-constraint The `allows ref struct` anti-constraint can be safely applied to a large number of generic definitions that do not have implementations. That means most delegates, interfaces and `abstract` methods can safely apply `allows ref struct` to their parameters. These are just API definitions without implementations and hence expanding the set of allowed types is only going to result in errors if they're used as type arguments where `ref struct` are not allowed. API owners can rely on a simple rule of "if it compiles, it's safe". The compiler will error on any unsafe uses of `allows ref struct`, just as it does for other `ref struct` uses. At the same time though there are versioning considerations API authors should consider. Essentially API owners should avoid adding `allows ref struct` to type parameters where the owning type / member may change in the future to be incompatible with `allows ref struct`. For example: - An `abstract` method which may later change to a `virtual` method - An `abstract` type which may later add implementations In such cases an API author should be careful about adding `allows ref struct` unless they are certain the type / member evolution will not using `T` in a way that breaks `ref struct` rules. Removing the `allows ref struct` anti-constraint is always a breaking change: source and binary. #### Default Interface Methods API authors need to be aware that adding DIMS will break `ref struct` implementors until they are recompiled. This is similar to [existing DIM behavior][dim-diamond] where by adding a DIM to an interface will break existing implementations until they are recompiled. That means API authors need to consider the likelihood of `ref struct` implementations when adding DIMs. There are three code components that are needed to create this situation: ```csharp interface I1 { // 1. The addition of a DIM method to an _existing_ interface void M() { } } // 2. A ref struct implementing the interface but not explicitly defining the DIM // method ref struct S : I1 { } // 3. The invocation of the DIM method in a generic method that has the `allows ref struct` // anti-constraint void M(T p) where T : allows ref struct, I1 { p.M(); } ``` All of three of these components are needed to create this particular issue. Further at least (1) and (2) must be in different assemblies. If they were in the same assembly then a compilation error would occur. #### UnscopedRef Adding or removing `[UnscopedRef]` from `interface` members is a source breaking change (and potentially creating runtime issues). The attribute should be applied when defining an interface member and not added or removed later. ### Span<Span<T>> This combination of features does not allow for constructs such as `Span>`. This is made a bit clearer by looking at the definition of `Span`: ```csharp readonly ref struct Span { public readonly ref T _data; public readonly int _length; public Span(T[] array) { ... } public static implicit operator Span(T[]? array) { } public static implicit operator Span(ArraySegment segment) { } } ``` If this type definition were to include `allows ref struct` then all `T` instances in the definition would need be treated as if they were potentially a `ref struct` type. That presents two classes of problems. The first is for APIs like `Span(T[] array)` and the implicit operators the `T` cannot be a `ref struct`: it's either used as an array element or as generic parameter which cannot be `allows ref struct`. There are a handful of public APIs on `Span` that have uses of `T` that cannot be compatible with a `ref struct`. These are public API that cannot be deleted and hence must be rationalized by the language. The most likely path forward is the compiler will special case `Span` and issue an error code ever bound to one of these APIs when the argument for `T` is _potentially_ a `ref struct`. The second is that the language does not support `ref` fields that are `ref struct`. There is a [design proposal][ref-struct-ref-fields] for allowing that feature. It's unclear if that will be accepted into the language or if it's expressive enough to handle the full set of scenarios around `Span`. Both of these issues are beyond the scope of this proposal. ### UnscopedRef Implementation Logic The rationale behind the `[UnscopedRef]` rules for interface implementation is easiest to understand when visualizing the `this` parameter as an explicit, rather than implicit, argument to the methods. Consider for example the following `struct` where `this` is visualized as an implicit parameter (similar to how Python handles it): ```csharp struct S { public void M(scoped ref S this) { } } ``` The `[UnscopedRef]` on an interface member is specifying that `this` lacks `scoped` for lifetime purposes at the call site. Allowing `[UnscopedRef]` to be ommitted on the implementing member is effectively allowing a parameter that is `ref T` to be implemented by a parameter that is `scoped ref T`. The language already allows this: ```csharp interface I1 { void M(ref Span span); } struct S : I1 { public void M(scoped ref Span span) { } } ``` ## Related Items Related Items: - https://github.com/dotnet/csharplang/issues/7608 - https://github.com/dotnet/csharplang/pull/7555 - https://github.com/dotnet/runtime/blob/main/docs/design/features/byreflike-generics.md - https://github.com/dotnet/runtime/pull/67783 - https://github.com/dotnet/runtime/issues/27229#issuecomment-1537274804 - https://github.com/dotnet/runtime/issues/68002 [ref-struct-ref-fields]: https://github.com/dotnet/csharplang/blob/main/proposals/expand-ref.md [ref-struct-generics]: #ref-struct-generic-parameters [byref-like-generics]: https://github.com/dotnet/runtime/blob/main/docs/design/features/byreflike-generics.md [dim-diamond]: https://github.com/dotnet/csharplang/blob/main/meetings/2018/LDM-2018-10-17.md#diamond-inheritance [unscoped-ref-impl]: #unscopedref-implementation-logic [warn-DIM]: #warn-on-dim-invocation ================================================ FILE: proposals/csharp-13.0/ref-unsafe-in-iterators-async.md ================================================ # Allow ref and unsafe in iterators and async [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Unify behavior between iterators and async methods. Specifically: - Allow `ref`/`ref struct` locals and `unsafe` blocks in iterators and async methods provided they are used in code segments without any `yield` or `await`. - Warn about `yield` inside `lock`. ## Motivation [motivation]: #motivation It is not necessary to disallow `ref`/`ref struct` locals and `unsafe` blocks in async/iterator methods if they are not used across `yield` or `await`, because they do not need to be hoisted. ```cs async void M() { await ...; ref int x = ...; // error previously, proposed to be allowed x.ToString(); await ...; // x.ToString(); // still error } ``` ## Breaking changes [break]: #breaking-changes There are no breaking changes in the language specification, but there is one breaking change in the Roslyn implementation (due to a spec violation). Roslyn violates the part of the spec which states that iterators introduce a safe context ([§13.3.1][blocks-general]). For example, if there is an `unsafe class` with an iterator method which contains a local function then that local function inherits the unsafe context from the class, although it should have been in a safe context per the spec due to the iterator method. In fact, the whole iterator method inherited the unsafe context in Roslyn, it was just disallowed to use any unsafe constructs in iterators. In `LangVersion >= 13`, iterators will correctly introduce a safe context because we want to allow unsafe constructs in iterators. ```cs unsafe class C // unsafe context { System.Collections.Generic.IEnumerable M() // an iterator { yield return 1; local(); async void local() { int* p = null; // allowed in C# 12; error in C# 13 (breaking change) await Task.Yield(); // error in C# 12, allowed in C# 13 } } } ``` Note: - The break can be worked around simply by adding the `unsafe` modifier to the local function. - This does not affect lambdas as they "inherit" the "iterator context" and therefore it was impossible to use unsafe constructs inside them. ## Detailed design [design]: #detailed-design The following changes are tied to LangVersion, i.e., C# 12 and lower will continue to disallow ref-like locals and `unsafe` blocks in async methods and iterators, and C# 13 will lift these restrictions as described below. However, spec clarifications which match the existing Roslyn implementation should hold across all LangVersions. [§13.3.1 Blocks > General][blocks-general]: > A *block* that contains one or more `yield` statements ([§13.15][yield-statement]) is called an iterator block, > **even if those `yield` statements are contained only indirectly in nested blocks (excluding nested lambdas and local functions).** > > [...] > > ~~It is a compile-time error for an iterator block to contain an unsafe context ([§23.2][unsafe-contexts]). > An iterator block always defines a safe context, even when its declaration is nested in an unsafe context.~~ > **The iterator block used to implement an iterator ([§15.14][iterators]) > always defines a safe context, even when the iterator declaration is nested in an unsafe context.** From this spec it also follows: - If an iterator declaration is marked with the `unsafe` modifier, the signature is in an unsafe scope but the iterator block used to implement that iterator still defines a safe scope. - The `set` accessor of an iterator property or indexer (i.e., its `get` accessor is implemented via an iterator block) "inherits" its safe/unsafe scope from the declaration. - This does not affect partial declarations without implementation as they are only signatures and cannot have an iterator body. Note that in C# 12 it is an error to have an iterator method marked with the `unsafe` modifier, but that is allowed in C# 13 due to the spec change. For example: ```cs using System.Collections.Generic; using System.Threading.Tasks; class A : System.Attribute { } unsafe partial class C1 { // unsafe context [/* unsafe context */ A] IEnumerable M1( /* unsafe context */ int*[] x) { // safe context (this is the iterator block implementing the iterator) yield return 1; } IEnumerable M2() { // safe context (this is the iterator block implementing the iterator) unsafe { // unsafe context { // unsafe context (this is *not* the block implementing the iterator) yield return 1; // error: `yield return` in unsafe context } } } [/* unsafe context */ A] unsafe IEnumerable M3( /* unsafe context */ int*[] x) { // safe context yield return 1; } [/* unsafe context */ A] IEnumerable this[ /* unsafe context */ int*[] x] { // unsafe context get { // safe context yield return 1; } set { /* unsafe context */ } } [/* unsafe context */ A] unsafe IEnumerable this[ /* unsafe context */ long*[] x] { // unsafe context (the iterator declaration is unsafe) get { // safe context yield return 1; } set { /* unsafe context */ } } IEnumerable M4() { yield return 1; var lam1 = async () => { // safe context // spec violation: in Roslyn, this is an unsafe context in LangVersion 12 and lower await Task.Yield(); // error in C# 12, allowed in C# 13 int* p = null; // error in both C# 12 and C# 13 (unsafe in iterator) }; unsafe { var lam2 = () => { // unsafe context, lambda cannot be an iterator yield return 1; // error: yield cannot be used in lambda }; } async void local() { // safe context // spec violation: in Roslyn, this is an unsafe context in LangVersion 12 and lower await Task.Yield(); // error in C# 12, allowed in C# 13 int* p = null; // allowed in C# 12, error in C# 13 (breaking change in Roslyn) } local(); } public partial IEnumerable M5() // unsafe context (inherits from parent) { // safe context yield return 1; } } partial class C1 { public partial IEnumerable M5(); // safe context (inherits from parent) } class C2 { // safe context [/* unsafe context */ A] unsafe IEnumerable M( /* unsafe context */ int*[] x) { // safe context yield return 1; } unsafe IEnumerable this[ /* unsafe context */ int*[] x] { // unsafe context get { // safe context yield return 1; } set { /* unsafe context */ } } } ``` [§13.6.2.4 Ref local variable declarations][ref-local]: > ~~It is a compile-time error to declare a ref local variable, or a variable of a `ref struct` type, > within a method declared with the *method_modifier* `async`, or within an iterator ([§15.14][iterators]).~~ > **It is a compile-time error to declare and use (even implicitly in compiler-synthesized code) > a ref local variable, or a variable of a `ref struct` type across `await` expressions or `yield return` statements. > More precisely, the error is driven by the following mechanism: > after an `await` expression ([§12.9.8][await-expressions]) or a `yield return` statement ([§13.15][yield-statement]), > all ref local variables and variables of a `ref struct` type in scope > are considered definitely unassigned ([§9.4][definite-assignment]).** Note that this error is not downgraded to a warning in `unsafe` contexts like [some other ref safety errors][ref-safety-unsafe-warnings]. That is because these ref-like locals cannot be manipulated in `unsafe` contexts without relying on implementation details of how the state machine rewrite works, hence this error falls outside the boundaries of what we want to downgrade to warnings in `unsafe` contexts. [§15.14.1 Iterators > General][iterators]: > When a function member is implemented using an iterator block, > it is a compile-time error for the formal parameter list of the function member to specify any > `in`, `ref readonly`, `out`, or `ref` parameters, or an parameter of a `ref struct` type **or a pointer type**. No change in the spec is needed to allow `unsafe` blocks which do not contain `await`s in async methods, because the spec has never disallowed `unsafe` blocks in async methods. However, the spec should have always disallowed `await` inside `unsafe` blocks (it had already disallowed `yield` in `unsafe` in [§13.3.1][blocks-general] as cited above), so we propose the following change to the spec: [§15.15.1 Async Functions > General][async-funcs-general]: > It is a compile-time error for the formal parameter list of an async function to specify > any `in`, `out`, or `ref` parameters, or any parameter of a `ref struct` type. > > **It is a compile-time error for an unsafe context ([§23.2][unsafe-contexts]) to contain > an `await` expression ([§12.9.8][await-expressions]) or a `yield return` statement ([§13.15][yield-statement]).** [§23.6.5 The address-of operator][address-of]: > **A compile-time error will be reported for taking an address of a local or a parameter in an iterator.** Currently, taking an address of a local or a parameter in an async method is [a warning in C# 12 warning wave][async-pointer]. --- Note that more constructs can work thanks to `ref` allowed inside segments without `await` and `yield` in async/iterator methods even though no spec change is needed specifically for them as it all falls out from the aforementioned spec changes: ```cs using System.Threading.Tasks; ref struct R { public ref int Current { get { ... }}; public bool MoveNext() => false; public void Dispose() { } } class C { public R GetEnumerator() => new R(); async void M() { await Task.Yield(); using (new R()) { } // allowed under this proposal foreach (var x in new C()) { } // allowed under this proposal foreach (ref int x in new C()) { } // allowed under this proposal lock (new System.Threading.Lock()) { } // allowed under this proposal await Task.Yield(); } } ``` ## Alternatives [alternatives]: #alternatives - `ref`/`ref struct` locals could be allowed only in blocks ([§13.3.1][blocks-general]) which do not contain `await`/`yield`: ```cs // error always since `x` is declared/used both before and after `await` { ref int x = ...; await Task.Yield(); x.ToString(); } // allowed as proposed (`x` does not need to be hoisted as it is not used after `await`) // but alternatively could be an error (`await` in the same block) { ref int x = ...; x.ToString(); await Task.Yield(); } ``` - `yield return` inside `lock` could be an error (like `await` inside `lock` is) or a warning-wave warning, but that would be a breaking change: https://github.com/dotnet/roslyn/issues/72443. Note that [the new `Lock`-object-based `lock`][lock-object] reports compile-time errors for `yield return`s in its body, because such `lock` statement is equivalent to a `using` on a `ref struct` which disallows `yield return`s in its body. - Variables inside async or iterator methods should not be "fixed" but rather "moveable" if they need to be hoisted to fields of the state machine (similarly to captured variables). Note that this is a pre-existing bug in the spec independent of the rest of the proposal because `unsafe` blocks inside `async` methods were always allowed. There is currently [a warning for this in C# 12 warning wave][async-pointer] and making it an error would be a breaking change. [§23.4 Fixed and moveable variables][fixed-vars]: > In precise terms, a fixed variable is one of the following: > > - A variable resulting from a *simple_name* ([§12.8.4][simple-names]) that refers to a local variable, value parameter, or parameter array, > unless the variable is captured by an anonymous function ([§12.19.6.2][captured-vars]) **or a local function ([§13.6.4][local-funcs]) > or the variable needs to be hoisted as part of an async ([§15.15][async-funcs]) or an iterator ([§15.14][iterators]) method**. > - [...] - Currently, we have an existing warning in C# 12 warning wave for address-of in async methods and a proposed error for address-of in iterators reported for LangVersion 13+ (does not need to be reported in earlier versions because it was impossible to use unsafe code in iterators). We could relax both of these to apply only to variables that are actually hoisted, not all locals and parameters. - It could be possible to use `fixed` to get the address of a hoisted or captured variable although the fact that those are fields is an implementation detail so in other implementations it might not be possible to use `fixed` on them. Note that we only propose to consider also hoisted variables as "moveable", but captured variables were already "moveable" and `fixed` was not allowed for them. - We could allow `await`/`yield` inside `unsafe` except inside `fixed` statements (compiler cannot pin variables across method boundaries). That might result in some unexpected behavior, for example around `stackalloc` as described in the nested bullet point below. Otherwise, hoisting pointers is supported even today in some scenarios (there is an example below related to pointers as arguments), so there should be no other limitations in allowing this. - We could disallow the unsafe variant of `stackalloc` in async/iterator methods, because the stack-allocated buffer does not live across `await`/`yield` statements. It does not feel necessary because unsafe code by design does not prevent "use after free". Note that we could also allow unsafe `stackalloc` provided it is not used across `await`/`yield`, but that might be difficult to analyze (the resulting pointer can be passed around in any pointer variable). Or we could require it being `fixed` in async/iterator methods. That would *discourage* using it across `await`/`yield` but would not match the semantics of `fixed` because the `stackalloc` expression is not a moveable value. (Note that it would not be *impossible* to use the `stackalloc` result across `await`/`yield` similarly as you can save any `fixed` pointer today into another pointer variable and use it outside the `fixed` block.) - Iterator and async methods could be allowed to have pointer parameters. They would need to be hoisted, but that should not be a problem as hoisting pointers is supported even today, for example: ```cs unsafe public void* M(void* p) { var d = () => p; return d(); } ``` - The proposal currently keeps (and extends/clarifies) the pre-existing spec that iterator methods begin a safe context even if they are in an unsafe context. For example, an iterator method is not an unsafe context even if it is defined in a class which has the `unsafe` modifier. Alternatively, we could make iterators "inherit" the `unsafe` modifier like other methods do. - Advantage: removes complexity from the spec and implementation. - Advantage: aligns iterators with async methods (one of the motivations of the feature). - Disadvantage: iterators inside unsafe classes could not contain `yield return` statements, such iterators would have to be defined in a separate partial class declaration without the `unsafe` modifier. - Disadvantage: this would be a breaking change in LangVersion=13 (iterators in unsafe classes are allowed in C# 12). - Instead of an iterator defining a safe context for the body only, the whole signature could be a safe context. That is inconsistent with the rest of the language in that bodies normally do not affect declarations but here a declaration would be either safe or unsafe depending on whether the body is an iterator or not. It would be also a breaking change in LangVersion=13 as in C# 12 iterator signatures are unsafe (they can contain pointer array parameters, for example). - Applying the `unsafe` modifier to an iterator: - Could affect the body as well as the signature. Such iterators would not be very useful though because their unsafe bodies could not contain `yield return`s, they could have only `yield break`s. - Could be an error in `LangVersion >= 13` as it is in `LangVersion <= 12` because it is not very useful to have an unsafe iterator member as it only allows one to have pointer array parameters or unsafe setters without additional unsafe block. But normal pointer arguments could be allowed in the future. - Roslyn breaking change: - We could preserve the current behavior (and even modify the spec to match it) for example by introducing the safe context in the iterator method but then reverting to the unsafe context in the local function. - Or we could break all LangVersions, not just 13 and newer. - It is also possible to more drastically simplify the rules by making iterators inherit unsafe context like all other methods do. Discussed above. Could be done across all LangVersions or just for `LangVersion >= 13`. ## Design meetings [ldm]: #design-meetings - [2024-06-03](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-03.md): post-implementation review of the speclet [definite-assignment]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/variables.md#94-definite-assignment [simple-names]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/expressions.md#1284-simple-names [await-expressions]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/expressions.md#1298-await-expressions [captured-vars]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/expressions.md#121962-captured-outer-variables [blocks-general]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/statements.md#1331-general [ref-local]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/statements.md#13624-ref-local-variable-declarations [local-funcs]: https://github.com/dotnet/csharpstandard/blob/d11d5a1a752bff9179f8207e86d63d12782c31ff/standard/statements.md#1364-local-function-declarations [lock-statement]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/statements.md#1313-the-lock-statement [using-statement]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/statements.md#1314-the-using-statement [yield-statement]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/statements.md#1315-the-yield-statement [iterators]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/classes.md#1514-iterators [async-funcs]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/classes.md#1515-async-functions [async-funcs-general]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/classes.md#15151-general [unsafe-contexts]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/unsafe-code.md#232-unsafe-contexts [fixed-vars]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/unsafe-code.md#234-fixed-and-moveable-variables [address-of]: https://github.com/dotnet/csharpstandard/blob/ee38c3fa94375cdac119c9462b604d3a02a5fcd2/standard/unsafe-code.md#2365-the-address-of-operator [lock-object]: ./lock-object.md [ref-safety-unsafe-warnings]: https://github.com/dotnet/csharplang/issues/6476 [async-pointer]: https://github.com/dotnet/roslyn/pull/66915 ================================================ FILE: proposals/csharp-14.0/extension-operators.md ================================================ # Extension operators [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] ***Note:*** *Does not cover user-defined implicit and explicit conversion operators, which are not yet designed or planned.* ## Declaration Like all extension members, extension operators are declared within an extension block: ``` c# public static class Operators { extension(TElement[] source) where TElement : INumber { public static TElement[] operator *(TElement[] vector, TElement scalar) { ... } public static TElement[] operator *(TElement scalar, TElement[] vector) { ... } public void operator *=(TElement scalar) { ... } } } ``` Extension operator declarations generally follow the rules for non-extension [user-defined operators in the Standard](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1510-operators) as well as the soon-to-be-added [user-defined compound assignment operators](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/user-defined-compound-assignment.md). Generally speaking, the rules pertaining to the *containing type* of the declaration instead apply to the *extended type*. A static extension operator for the extended type `T` must take at least one parameter of type `S`, where `S` and `T` are identity convertible. Note, that, unlike for regular user-defined operators, ```Nullable``` is not a valid type for `S`. For operators that require pair-wise declaration, the two declarations are allowed to occur in separate extension blocks for extended types `S` and `T` respectively, as long as `S` and `T` are identity convertible and the extension blocks occur in the same static class. Instance compound assignment and increment operators are supposed to mutate the instance. Therefore, the following restrictions are applied for such extension operators: - Receiver type must be known to be either a reference type or a value type. I.e. cannot be an unconstrained type parameter. - If receiver type is a value type, the receiver parameter must be a 'ref' parameter. - If receiver type is a reference type, the receiver parameter must be a value parameter. Like other extension members, extension operators cannot use the `abstract`, `virtual`, `override` or `sealed` modifiers. ``` c# public static class Operators { extension(int[]) { public static int[] operator +(int[] vector, int scalar) { ... } // OK; parameter and extended type agree public static int operator +(int scalar1, int scalar2) { ... } // ERROR: extended type not used as parameter type public static bool operator ==(int[] vector1, int[] vector2){ ... } // ERROR: '!=' declaration missing public static bool operator <(int[] vector1, int[] vector2){ ... } // OK: `>` is declared below } extension(int[]) { public static bool operator >(int[] vector1, int[] vector2){ ... } // OK: `<` is declared above } } ``` ## Overload resolution Like other extension members, extension operators are only considered if no applicable predefined or non-extension user-defined operators were found. The search then proceeds outwards scope by scope, starting with the innermost namespace within which the operator application occurs, until at least one applicable extension operator declaration is found. For compound assignment operators, this involves looking for first user-defined compound assignment operators and then, if none found, non-assignment operators, before moving on to the next scope. At each scope, overload resolution works the same as other extension members: - The set of operator declarations for the given name (i.e. operator) is determined - Type inference is attempted for each, based on operand expressions - Declarations that fail type inference are removed - Declarations that are not applicable to the operands are removed - If there are no remaining candidates, proceed to the enclosing scope (or, if looking for compound-assignment operators, the corresponding simple operator) - Applying overload resolution between the candidate, select the unique best candidate, if it exists - If no unique best candidate can be found, overload resolution fails with an ambiguity error. Using `*` and `*=` declarations above: ``` c# int[] numbers = { 1, 2, 3 }; var i = 2 * 3; // predefined operator *(int, int) var v = numbers * 4; // extension operator *(int[], int) v *= 5; // extension operator *=(int) ``` ## Extension user-defined conditional logical operators The following [restrictions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12143-user-defined-conditional-) are specified for regular user-defined operators: >When the operands of `&&` or `||` are of types that declare an applicable user-defined `operator &` or `operator |`, both of the following shall be true, where `T` is the type in which the selected operator is declared: > >- The return type and the type of each parameter of the selected operator shall be `T`. In other words, the operator shall compute the logical AND or the logical OR of two operands of type `T`, and shall return a result of type `T`. >- `T` shall contain declarations of `operator true` and `operator false`. By analogy, the following restrictions are used for extension operators: When the operands of `&&` or `||` are of types that declare an applicable user-defined extension `operator &` or extension `operator |`, both of the following shall be true: - The operator shall compute the logical AND or the logical OR of two operands of type `T`, and shall return a result of type `T`. - The enclosing static class shall contain declarations of extension `operator true` and extension `operator false` applicable to an instance of type `T`. For example, these declarations satisfy the restrictions, given that lifted form of `operator &` is used: ``` c# S1? s11 = new S1(); S1? s12 = new S1(); _ = s11 && s12; public static class Extensions { extension(S1) { public static S1 operator &(S1 x, S1 y) => x; } extension(S1?) { public static bool operator false(S1? x) => false; public static bool operator true(S1? x) => true; } } public struct S1 {} ``` ## Use of extension operators in Linq Expression Trees When an expression utilizing a user-defined operator is used in a lambda that is converted to a Linq Expression tree, an expression node that compiler creates includes a `MethodInfo` pointing to the operator method. For example: ``` c# public class C1 { public static C1 operator +(C1 x, int y) => x; } public class Program { static void Main() { Expression> x = (c1) => c1 + 1; } } ``` uses [Expression.Add(Expression left, Expression right, MethodInfo? method)](https://learn.microsoft.com/dotnet/api/system.linq.expressions.expression.add#system-linq-expressions-expression-add(system-linq-expressions-expression-system-linq-expressions-expression-system-reflection-methodinfo)) factory. When the operator is an extension operator, a MethodInfo referring to the corresponding implementation method in the enclosing class will be used. Note, `&&`/`||` operators utilizing extension operators will be blocked in Linq Expression trees due to limitations of factory methods. The [issue](https://github.com/dotnet/runtime/issues/115674) illustrates the failure mode for the factory methods. ## Open design questions ### [Resolved] Should extension operators on Nullable of extended type be disallowed? It is allowed to declare a regular user-defined operator on Nullable of containing type. Such operator can be consumed on an instance of containing type, as well as on an instance of Nullable of containing type. ``` c# S1? s1 = new S1(); s1 = +s1; // Ok s1 = +s1.Value; // Ok struct S1 { public static S1? operator +(S1? x) => x; } ``` Similarly, it is allowed to declare an extension user-defined operator on Nullable of extended type. However, such operator can be consumed only on an instance of extended type. Consumption on an instance of Nullable of extended type is not allowed because a conversion from ```Nullable``` to ```T``` is not a valid extension receiver type conversion. ``` c# S1? s1 = new S1(); s1 = +s1; // Error: no matching operator s1 = +s1.Value; // Ok struct S1; public static class Extensions { extension(S1) { public static S1? operator +(S1? x) => x; } } ``` This makes such operator declarations somewhat useless, they won't ever be consumed on `null` instances, and, therefore, no real reason to have nullable parameter types. Should declarations like this be disallowed? [Resolution:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-04.md#should-extension-operators-on-nullable-of-extended-type-be-disallowed) >Restriction accepted: extension operators can only be declared for the type being extended in an extension block, not for the nullable underlying type. ### [Resolved] Applicability of bitwise operators during evaluation of user-defined conditional logical operators Language adds extra [restrictions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12143-user-defined-conditional-) to bitwise operators suitable for evaluation of user-defined conditional logical operators: >When the operands of `&&` or `||` are of types that declare an applicable user-defined `operator &` or `operator |`, both of the following shall be true, where `T` is the type in which the selected operator is declared: > >- The return type and the type of each parameter of the selected operator shall be `T`. In other words, the operator shall compute the logical AND or the logical OR of two operands of type `T`, and shall return a result of type `T`. >- `T` shall contain declarations of `operator true` and `operator false`. However, the specification isn't clear whether the restriction should eliminate a candidate bitwise operator as inapplicable when the requirements are not satisfied, or the requirements should be applied to the best bitwise operator after overload resolution among the candidates is complete. Right now, compiler implements the latter, which leads to the following behavior: ``` c# S1 s1 = new S1(); S2 s2 = new S2(); // error CS0217: In order to be applicable as a short circuit operator // a user-defined logical operator ('S1.operator &(S1, S2)') // must have the same return type and parameter types _ = s1 && s2; struct S1 { // If this operator is removed, candidate from S2 is successfully used public static S2 operator &(S1 x, S2 y) => y; } struct S2 { public static S2 operator &(S2 x, S2 y) => y; public static bool operator true(S2 x) => false; public static bool operator false(S2 x) => true; public static implicit operator S2(S1 x) => default; } ``` Here is another example that could benefit from the former approach: ``` C# S1 s1 = new S1(); S2 s2 = new S2(); // error CS0034: Operator '&&' is ambiguous on operands of type 'S1' and 'S2' _ = s1 && s2; struct S1 { // If this operator is removed, candidate from S2 is successfully used public static S1 operator &(S1 x, S1 y) => y; public static implicit operator S1(S2 x) => default; } struct S2 { public static S2 operator &(S2 x, S2 y) => y; public static bool operator true(S2 x) => false; public static bool operator false(S2 x) => true; public static implicit operator S2(S1 x) => default; } ``` The precise applicability rules are even more important for extension operators. Extension operators are considered only when there are no applicable regular user-defined operators. And, only when there are no applicable extension operators in the given extension scope, candidates from next extension scope are considered. Hence the question, should the restrictions be part of a candidate applicability check during overload resolution rather than a post validation after the overload resolution is complete? [Resolution:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-04.md#applicability-of-bitwise-operators-during-evaluation-of-user-defined-conditional-logical-operators) > We're not doing anything here for now. Rejected until we see use cases. ### [Resolved] Extension user-defined conditional logical operators The following [restrictions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12143-user-defined-conditional-) are specified for regular user-defined operators: >When the operands of `&&` or `||` are of types that declare an applicable user-defined `operator &` or `operator |`, both of the following shall be true, where `T` is the type in which the selected operator is declared: > >- The return type and the type of each parameter of the selected operator shall be `T`. In other words, the operator shall compute the logical AND or the logical OR of two operands of type `T`, and shall return a result of type `T`. >- `T` shall contain declarations of `operator true` and `operator false`. By analogy, the following restrictions are proposed for extension operators: When the operands of `&&` or `||` are of types that declare an applicable user-defined extension `operator &` or extension `operator |`, both of the following shall be true: - The operator shall compute the logical AND or the logical OR of two operands of type `T`, and shall return a result of type `T`. - The enclosing static class shall contain declarations of extension `operator true` and extension `operator false` applicable to an instance of type `T`. For example, these declarations satisfy the restrictions, given that lifted form of `operator &` is used: ``` c# S1? s11 = new S1(); S1? s12 = new S1(); _ = s11 && s12; public static class Extensions { extension(S1) { public static S1 operator &(S1 x, S1 y) => x; } extension(S1?) { public static bool operator false(S1? x) => false; public static bool operator true(S1? x) => true; } } public struct S1 {} ``` [Resolution:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-04.md#extension-user-defined-conditional-logical-operators) > Proposal accepted. ### [Resolved] Extension compound assignment operators Compound assignment operators are instance operators and are supposed to mutate the instance. Therefore, the following restrictions are proposed for extension compound assignment operators: - Receiver type must be known to be either a reference type or a value type. I.e. cannot be an unconstrained type parameter. - If receiver type is a value type, the receiver parameter must be a 'ref' parameter. - If receiver type is a reference type, the receiver parameter must be a value parameter. [Resolution:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-04.md#extension-compound-assignment-operators) > Restriction adopted, we can look at it again in the future when there is more bandwidth. ### [Resolved] Dynamic evaluation Extension operators are not used by dynamic evaluation. This might lead to compile time errors. For example: ``` c# dynamic s1 = new object(); var s2 = new object(); // error CS7083: Expression must be implicitly convertible to Boolean or its type 'object' must define operator 'false'. _ = s2 && s1; public static class Extensions { extension(object) { public static object operator &(object x, object y) => x; public static bool operator false(object x) => false; public static bool operator true(object x) => throw null; } } ``` An attempt to do a compile time optimization using non-dynamic static type of 's2' ignores true/false extensions. One might say this is desirable because runtime binder wouldn't be able to use them as well. [Resolution:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-11.md#dynamic-resolution-of-operator-truefalse) Restriction accepted. Extension operator `true`/`false` are not used for `dynamic` `&&` or `||`. ### [Resolved] Use of extension operators in Linq Expression Trees When an expression utilizing a user-defined operator is used in a lambda that is converted to a Linq Expression tree, an expression node that compiler creates includes a `MethodInfo` pointing to the operator method. For example: ``` c# public class C1 { public static C1 operator +(C1 x, int y) => x; } public class Program { static void Main() { Expression> x = (c1) => c1 + 1; } } ``` uses [Expression.Add(Expression left, Expression right, MethodInfo? method)](https://learn.microsoft.com/dotnet/api/system.linq.expressions.expression.add#system-linq-expressions-expression-add(system-linq-expressions-expression-system-linq-expressions-expression-system-reflection-methodinfo)) factory. The question is what should we do when the operator is an extension operator. We cannot use a MethodInfo referencing the extension operator itself because IL is not allowed to refer to any declaration from an extension block. Proposal: Use MethodInfo referring to a corresponding implementation method in the enclosing class. A quick smoke test confirmed that an expression tree like that can be compiled, executed, and execution calls the implementation method. [Resolution:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-11.md#extension-operators-in-linq-expression-trees) Accepted. ### [Resolved] Is the rule "an extension operator may not have the same signature as a predefined operator." worth having as specified? If our goal is to prevent users from declaring an operator that would be shadowed by a predefined operator, then it doesn't look like the rule as specified achieves it. For example, the rule is going to prevent a user from defining ```operator –(int x)``` and ```operator –(long x)```, but it is not going to prevent declaration of ```operator –(byte x)```. However, in the following code it would be shadowed by predefined ```operator –(int x)```. ``` c# byte x = 0; var y = -x; ``` Perhaps the rule should be changed to something like the following instead? > If operator overload resolution with an argument list consisting of expressions with types > matching declared parameters in the same order against the set of predefined operators of > the same kind succeeds, then the signature of declared extension operator is considered illegal. For ```operator –(byte x)```, we would perform an operator overload resolution with an argument list [byte] against predefined unary `-` operators. It would succeed with predefined ```operator –(int x)``` as the result. Therefore, an error would be reported for extension ```operator –(byte x)```. For ```operator +(byte x, byte y)```, we would perform an operator overload resolution with an argument list [byte, byte] against predefined binary `+` operators. It would succeed with predefined ```operator +(int x, int y)``` as the result. Therefore, an error would be reported for extension ```operator +(byte x, byte y)```. When considering signatures of instance compound assignment operators, the receiver parameter is going to contribute to the argument list. For ```extension(byte).operator+=(short)```, we would perform an operator overload resolution with an argument list [byte, short] against predefined binary `+` operators. It would succeed with predefined ```operator +(int x, int y)``` as the result. Therefore, an error would be reported for ```extension(byte).operator+=(short)```. [Resolution:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-11.md#built-in-operator-protection-rules) Rule is abandoned. We will not have built-in errors or warnings here. ## Design meetings - https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-04.md - https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-06-11.md ================================================ FILE: proposals/csharp-14.0/extensions.md ================================================ # Extension members [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: https://github.com/dotnet/csharplang/issues/8697 ## Declaration ### Syntax ```antlr class_body : '{' class_member_declaration* '}' ';'? | ';' ; class_member_declaration : constant_declaration | field_declaration | method_declaration | property_declaration | event_declaration | indexer_declaration | operator_declaration | constructor_declaration | finalizer_declaration | static_constructor_declaration | type_declaration | extension_declaration // add ; extension_declaration // add : 'extension' type_parameter_list? '(' receiver_parameter ')' type_parameter_constraints_clause* extension_body ; extension_body // add : '{' extension_member_declaration* '}' ';'? ; extension_member_declaration // add : method_declaration | property_declaration | operator_declaration ; receiver_parameter // add : attributes? parameter_modifiers? type identifier? ; ``` Extension declarations shall only be declared in non-generic, non-nested static classes. It is an error for a type to be named `extension`. ### Scoping rules The type parameters and receiver parameter of an extension declaration are in scope within the body of the extension declaration. It is an error to refer to the receiver parameter from within a static member, except within a `nameof` expression. It is an error for members to declare type parameters or parameters (as well as local variables and local functions directly within the member body) with the same name as a type parameter or receiver parameter of the extension declaration. ``` c# public static class E { extension(T[] ts) { public bool M1(T t) => ts.Contains(t); // `T` and `ts` are in scope public static bool M2(T t) => ts.Contains(t); // Error: Cannot refer to `ts` from static context public void M3(int T, string ts) { } // Error: Cannot reuse names `T` and `ts` public void M4(string s) { } // Error: Cannot reuse names `T` and `ts` } } ``` It is not an error for the members themselves to have the same name as the type parameters or receiver parameter of the enclosing extension declaration. Member names are not directly found in a simple name lookup from within the extension declaration; lookup will thus find the type parameter or receiver parameter of that name, rather than the member. Members do give rise to static methods being declared directly on the enclosing static class, and those can be found via simple name lookup; however, an extension declaration type parameter or receiver parameter of the same name will be found first. ``` c# public static class E { extension(T[] ts) { public void T() { M(ts); } // Generated static method M(T[]) is found public void M() { T(ts); } // Error: T is a type parameter } } ``` ### Static classes as extension containers Extensions are declared inside top-level non-generic static classes, just like extension methods today, and can thus coexist with classic extension methods and non-extension static members: ``` c# public static class Enumerable { // New extension declaration extension(IEnumerable source) { ... } // Classic extension method public static IEnumerable Cast(this IEnumerable source) { ... } // Non-extension member public static IEnumerable Range(int start, int count) { ... } } ``` ### Extension declarations An extension declaration is anonymous, and provides a _receiver specification_ with any associated type parameters and constraints, followed by a set of extension member declarations. The receiver specification may be in the form of a parameter, or - if only static extension members are declared - a type: ``` c# public static class Enumerable { extension(IEnumerable source) // extension members for IEnumerable { public bool IsEmpty { get { ... } } } extension(IEnumerable source) // extension members for IEnumerable { public IEnumerable Where(Func predicate) { ... } public IEnumerable Select(Func selector) { ... } } extension(IEnumerable) // static extension members for IEnumerable where TElement : INumber { public static IEnumerable operator +(IEnumerable first, IEnumerable second) { ... } } } ``` The type in the receiver specification is referred to as the _receiver type_ and the parameter name, if present, is referred to as the _receiver parameter_. If the _receiver parameter_ is named, the _receiver type_ may not be static. The _receiver parameter_ is not allowed to have modifiers if it is unnamed, and it is only allowed to have the refness modifiers listed below and `scoped` otherwise. The _receiver parameter_ bears the same restrictions as the first parameter of a classic extension method. The `[EnumeratorCancellation]` attribute is ignored if it is placed on the _receiver parameter_. ### Extension members Extension member declarations are syntactically identical to corresponding instance and static members in class and struct declarations (with the exception of constructors). Instance members refer to the receiver with the receiver parameter name: ``` c# public static class Enumerable { extension(IEnumerable source) { // 'source' refers to receiver public bool IsEmpty => !source.GetEnumerator().MoveNext(); } } ``` It is an error to specify an instance extension member if the enclosing extension declaration does not specify a receiver parameter: ``` c# public static class Enumerable { extension(IEnumerable) // No parameter name { public bool IsEmpty => true; // Error: instance extension member not allowed } } ``` It is an error to specify the following modifiers on a member of an extension declaration: `abstract`, `virtual`, `override`, `new`, `sealed`, `partial`, and `protected` (and related accessibility modifiers). It is an error to specify the `readonly` modifier on a member of an extension declaration. Properties in extension declarations may not have `init` accessors. The instance members are disallowed if the _receiver parameter_ is unnamed. All members shall have names that differ from the name of the static enclosing class and the name of the extended type if it has one. It is an error to decorate an extension member with the `[ModuleInitializer]` attribute. ### Refness By default the receiver is passed to instance extension members by value, just like other parameters. However, an extension declaration receiver in parameter form can specify `ref`, `ref readonly` and `in`, as long as the receiver type is known to be a value type. ### Nullability and attributes Receiver types can be or contain nullable reference types, and receiver specifications that are in the form of parameters can specify attributes: ``` c# public static class NullableExtensions { extension(string? text) { public string AsNotNull => text is null ? "" : text; } extension([NotNullWhen(false)] string? text) { public bool IsNullOrEmpty => text is null or []; } extension ([NotNull] T t) where T : class? { public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(t); } } ``` ### Compatibility with classic extension methods Instance extension methods generate artifacts that match those produced by classic extension methods. Specifically the generated static method has the attributes, modifiers and name of the declared extension method, as well as type parameter list, parameter list and constraints list concatenated from the extension declaration and the method declaration in that order: ``` c# public static class Enumerable { extension(IEnumerable source) // Generate compatible extension methods { public IEnumerable Where(Func predicate) { ... } public IEnumerable Select(Func selector) { ... } } } ``` Generates: ``` c# [Extension] public static class Enumerable { [Extension] public static IEnumerable Where(IEnumerable source, Func predicate) { ... } [Extension] public static IEnumerable Select(IEnumerable source, Func selector) { ... } } ``` ### Operators Although extension operators have explicit operand types, they still need to be declared within an extension declaration: ``` c# public static class Enumerable { extension(IEnumerable) where TElement : INumber { public static IEnumerable operator *(IEnumerable vector, TElement scalar) { ... } public static IEnumerable operator *(TElement scalar, IEnumerable vector) { ... } } } ``` This allows type parameters to be declared and inferred, and is analogous to how a regular user-defined operator must be declared within one of its operand types. ## Checking __Inferrability:__ For each non-method extension member, all the type parameters of its extension block must be used in the combined set of parameters from the extension and the member. __Uniqueness:__ Within a given enclosing static class, the set of extension member declarations with the same receiver type (modulo identity conversion and type parameter name substitution) are treated as a single declaration space similar to the members within a class or struct declaration, and are subject to the same [rules about uniqueness](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#153-class-members). ``` c# public static class MyExtensions { extension(IEnumerable) // Error! T1 not inferrable { ... } extension(IEnumerable) { public bool IsEmpty { get ... } } extension(IEnumerable?) { public bool IsEmpty { get ... } // Error! Duplicate declaration } } ``` The application of this uniqueness rule includes classic extension methods within the same static class. For the purposes of comparison with methods within extension declarations, the `this` parameter is treated as a receiver specification along with any type parameters mentioned in that receiver type, and the remaining type parameters and method parameters are used for the method signature: ``` c# public static class Enumerable { public static IEnumerable Cast(this IEnumerable source) { ... } extension(IEnumerable source) { IEnumerable Cast() { ... } // Error! Duplicate declaration } } ``` ## Consumption When an extension member lookup is attempted, all extension declarations within static classes that are `using`-imported contribute their members as candidates, regardless of receiver type. Only as part of resolution are candidates with incompatible receiver types discarded. A full generic type inference is attempted between the type of the arguments (including the actual receiver) and any type parameters (combining those in the extension declaration and in the extension member declaration). When explicit type arguments are provided, they are used to substitute the type parameters of the extension declaration and the extension member declaration. ``` c# string[] strings = ...; var query = strings.Select(s => s.Length); // extension invocation var query2 = strings.Select(s => s.Length); // ... with explicit full set of type arguments var query3 = Enumerable.Select(strings, s => s.Length); // static method invocation var query4 = Enumerable.Where(strings, s => s.Length); // ... with explicit full set of type arguments public static class Enumerable { extension(IEnumerable source) { public IEnumerable Select(Func predicate) { ... } } } ``` Similarly to classic extension methods, the emitted implementation methods can be invoked statically. This allows the compiler to disambiguate between extension members with the same name and arity. ```csharp object.M(); // ambiguous E1.M(); new object().M2(); // ambiguous E1.M2(new object()); _ = _new object().P; // ambiguous _ = E1.get_P(new object()); static class E1 { extension(object) { public static void M() { } public void M2() { } public int P => 42; } } static class E2 { extension(object) { public static void M() { } public void M2() { } public int P => 42; } } ``` Static extension methods will be resolved like instance extension methods (we will consider an extra argument of the receiver type). Extension properties will be resolved like extension methods, with a single parameter (the receiver parameter) and a single argument (the actual receiver value). ### `using static` directives A **using_static_directive** makes members of extension blocks in the type declaration available for extension access. ```cs using static N.E; new object().M(); object.M2(); _ = new object().Property; _ = object.Property2; C c = null; _ = c + c; c += 1; namespace N { static class E { extension(object o) { public void M() { } public static void M2() { } public int Property => 0; public static int Property2 => 0; } extension(C c) { public static C operator +(C c1, C c2) => throw null; public void operator +=(int i) => throw null; } } } class C { } ``` As before, the accessible static members (except extension methods) contained directly in the declaration of the given type can be referenced directly. This means that implementation methods (except those that are extension methods) can be used directly as static methods: ```cs using static E; M(); System.Console.Write(get_P()); set_P(43); _ = op_Addition(0, 0); _ = new object() + new object(); static class E { extension(object) { public static void M() { } public static int P { get => 42; set { } } public static object operator +(object o1, object o2) { return o1; } } } ``` A **using_static_directive** still does not import extension methods directly as static methods, so the implementation method for non-static extension methods cannot be invoked directly as a static method. ```cs using static E; M(1); // error: The name 'M' does not exist in the current context static class E { extension(int i) { public void M() { } } } ``` ### OverloadResolutionPriorityAttribute Extension members within an enclosing static class are subject to prioritization according to ORPA values. The enclosing static class is considered the "containing type" which ORPA rules consider. Any ORPA attribute present on an extension property is copied onto the implementation methods for the property's accessors, so that the prioritization is respected when those accessors are used via disambiguation syntax. ### Entry points Methods of extension blocks do not qualify as entry point candidates (see "7.1 Application startup"). Note: the implementation method may still be a candidate. ## Lowering The lowering strategy for extension declarations is not a language level decision. However, beyond implementing the language semantics it must satisfy certain requirements: - The format of generated types, members and metadata should be clearly specified in all cases so that other compilers can consume and generate it. - The generated artifacts should be stable, in the sense that reasonable later modifications should not break consumers who compiled against earlier versions. These requirements need more refinement as implementation progresses, and may need to be compromised in corner cases in order to allow for a reasonable implementation approach. ### Metadata for declarations #### Goals The design below allows: - roundtripping of extension declaration symbols through metadata (full and reference assemblies), - stable references to extension members (xml docs), - local determination of emitted names (helpful for EnC), - public API tracking. For xml docs, the docID for an extension member is the docID for the extension member in metadata. For example, the docID used in `cref="Extension.extension(object).M(int)"` is `M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32)` and that docID is stable across re-compilations and re-orderings of extension blocks. Ideally, it would also remain stable when constraints on the extension block change, but we didn't find a design that would achieve that without detrimental effect on language design for member conflicts. For EnC, it is useful to know locally (just by looking at a modified extension member) where the updated extension member is emitted in metadata. For public API tracking, more stable names reduce noise. But technically the extension grouping type names should not come into play in such scenarios. When looking at extension member `M`, it doesn't matter what is the name of the extension grouping type, what matters is the signature of the extension block it belongs to. The public API signature should not be seen as `Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32)` but rather as `Extension.extension(object).M(int)`. In other words, extension members should be seen as having two sets of type parameters and two sets of parameters. #### Overview Extension blocks are grouped by their CLR-level signature. Each CLR equivalency group is emitted as an **extension grouping type** with a content-based name. Extension blocks within a CLR equivalency group are then sub-grouped by C# equivalency. Each C# equivalency group is emitted as an **extension marker type** with a content-based name, nested in its corresponding extension grouping type. An extension marker type contains a single **extension marker method** which encodes an extension parameter. The extension marker method with its containing extension marker type encode the signature of an extension block with full fidelity. Declaration of each extension member is emitted in the right extension grouping type, refers back to an extension marker type by its name via an attribute, and is accompanied by a top-level static **implementation method** with a modified signature. Here's a schematized overview of metadata encoding: ``` [Extension] static class EnclosingStaticClass { [Extension] public sealed class ExtensionGroupingType1 // has type parameters with minimal constraints sufficient to keep extension member declarations below valid { public static class ExtensionMarkerType1 // has re-declared type parameters with full fidelity of C# constraints { public static void $(... extension parameter ...) // extension marker method } ... ExtensionMarkerType2, etc ... ... extension members for ExtensionGroupingType1, each points to its corresponding extension marker type ... } ... ExtensionGroupingType2, etc ... ... implementation methods ... } ``` The enclosing static class is emitted with an `[Extension]` attribute. ### CLR-level signature vs. C#-level signature The CLR-level signature of an extension block results from: - normalizing type parameter names to `T0`, `T1`, etc ... - removing attributes - erasing the parameter name - erasing parameter modifiers (like `ref`, `in`, `scoped`, ...) - erasing tuple names - erasing nullability annotations - erasing `notnull` constraints Note: other constraints are preserved, such as `new()`, `struct`, `class`, `allows ref struct`, `unmanaged`, and type constraints. #### Extension grouping types An extension grouping type is emitted to metadata for each set of extension blocks in source with a the same CLR-level signature. - Its name is unspeakable and determined based on the contents of the CLR-level signature. More details below. - Its type parameters have normalized names (`T0`, `T1`, ...) and have no attributes. - It is public and sealed. - It is marked with the `specialname` flag and an `[Extension]` attribute. The content-based name of the extension grouping type is based on the CLR-level signature and includes the following: - The fully qualified CLR name of the type of the extension parameter. - Referenced type parameter names will be normalized to `T0`, `T1`, etc ... based on the order they appear in the type declaration. - The fully qualified name will not include the containing assembly. It is common for types to be moved between assemblies and that should not break the xml doc references. - Constraints of type parameters will be included and sorted such that reordering them in source code does not change the name. Specifically: - Type parameter constraints will be listed in declaration order. The constraints for the Nth type parameter will occur before the Nth+1 type parameter. - Type constraints will be sorted by comparing the full names ordinally. - Non-type constraints are ordered deterministically and are handled so as to avoid any ambiguity or collision with type constraints. - Since this does not include attributes, it intentionally ignores C#-isms like tuple names, nullability, etc ... Note: The name is guaranteed to remain stable across re-compilations, re-orderings and changes of C#-isms (ie. that don't affect the CLR-level signature). #### Extension marker types The marker type re-declares the type parameters of its containing grouping type (an extension grouping type) to get full fidelity of the C# view of extension blocks. An extension marker type is emitted to metadata for each set of extension blocks in source with a the same C#-level signature. - Its name is unspeakable and determined based on the contents of the C#-level signature of the extension block. More details below. - It redeclares the type parameters for its containing grouping type to be those declared in source (including name and attributes). - It is public and static. - It is marked with the `specialname` flag. The content-based name of the extension marker type is based on the following: - The names of type parameters will be included in the order they appear in the extension declaration - The attributes of type parameters will be included and sorted such that reordering them in source code does not change the name. - Constraints of type parameters will be included and sorted such that reordering them in source code does not change the name. - The fully qualified C# name of the extended type - This will include items like nullable annotations, tuple names, etc ... - The fully qualified name will not include the containing assembly - The name of the extension parameter - The modifiers of the extension parameter (`ref`, `ref readonly`, `scoped`, ...) in a deterministic order - The fully qualified name and attribute arguments for any attributes applied to the extension parameter in a deterministic order Note: The name is guaranteed to remain stable across re-compilation and re-orderings. Note: extension marker types and extension marker methods are emitted as part of reference assemblies. #### Extension marker method The purpose of the marker method is to encode the extension parameter of the extension block. Because it is a member of the extension marker type, it can refer to the re-declared type parameters of the extension marker type. Each extension marker type contains a single method, the extension marker method. - It is static, non-generic, void-returning, and is called `$`. - Its single parameter has the attributes, refness, type and name from the extension parameter. If the extension parameter doesn't specify a name, then the parameter name is empty. - It is marked with the `specialname` flag. Accessibility of the marker method will be the least restrictive accessibility among corresponding declared extension members, `private` is used if none are declared. #### Extension members Method/property declarations in an extension block in source are represented as members of the extension grouping type in metadata. - The signatures of the original methods are maintained (including attributes), but their bodies are replaced with `throw NotImplementedException()`. - Those should not be referenced in IL. - Methods, properties and their accessors are marked with `[ExtensionMarkerName("...")]` referring to the name of the extension marker type corresponding to the extension block for that member. #### Implementation methods The method bodies for method/property declarations in an extension block in source are emitted as static implementation methods in the top-level static class. - An implementation method has the same name as the original method. - It has type parameters derived from the extension block prepended to the type parameters of the original method (including attributes). - It has the same accessibility and attributes as the original method. - If it implements a static method, it has the same parameters and return type. - It if implements an instance method, it has a prepended parameter to the signature of the original method. This parameter's attributes, refness, type, and name are derived from the extension parameter declared in the relevant extension block. - The parameters in implementation methods refer to type parameters owned by implementation method, instead of those of an extension block. - If the original member is an instance ordinary method, the implementation method is marked with an `[Extension]` attribute. #### ExtensionMarkerName attribute The `ExtensionMarkerNameAttribute` type is for compiler use only - it is not permitted in source. The type declaration is synthesized by the compiler if not already included in the compilation. ```csharp namespace System.Runtime.CompilerServices; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] public sealed class ExtensionMarkerNameAttribute : Attribute { public ExtensionMarkerNameAttribute(string name) => Name = name; public string Name { get; } } ``` Note: Although some attribute targets are included for future-proofing (extension nested types, extension fields, extension events), AttributeTargets.Constructor is not included as extension constructors would not be constructors. #### Example Note: we use simplified content-based names for the example, for readability. Note: since C# cannot represent type parameter re-declaration, the code representing metadata is not valid C# code. Here's an example illustrating how grouping works, without members: ``` class E { extension(IEnumerable source) { ... member in extension(IEnumerable source) } extension(ref IEnumerable p) { ... member in extension(ref IEnumerable p) } extension(IEnumerable source) where T : IEquatable { ... member in extension(IEnumerable source) where T : IEquatable } } ``` is emitted as ``` [Extension] class E { [Extension, SpecialName] public sealed class <>E__ContentName_For_IEnumerable_T { [SpecialName] public static class <>E__ContentName1 // note: re-declares type parameter T0 as T { [SpecialName] public static void $(IEnumerable source) { } } [SpecialName] public static class <>E__ContentName2 // note: re-declares type parameter T0 as U { [SpecialName] public static void $(ref IEnumerable p) { } } [ExtensionMarkerName("<>E__ContentName1")] ... member in extension(IEnumerable source) [ExtensionMarkerName("<>E__ContentName2")] ... member in extension(ref IEnumerable p) } [Extension, SpecialName] public sealed class <>ContentName_For_IEnumerable_T_With_Constraint where T0 : IEquatable { [SpecialName] public static class <>E__ContentName3 // note: re-declares type parameter T0 as U { [SpecialName] public static void $(IEnumerable source) { } } [ExtensionMarkerName("ContentName3")] public static bool IsPresent(U value) => throw null!; } ... implementation methods } ``` Here's an example illustrating how members are emitted: ``` static class IEnumerableExtensions { extension(IEnumerable source) where T : notnull { public void Method() { ... } internal static int Property { get => ...; set => ...; } public int Property2 { get => ...; set => ...; } } extension(IAsyncEnumerable values) { public async Task SumAsync() { ... } } public static void Method2() { ... } } ``` is emitted as ``` [Extension] static class IEnumerableExtensions { [Extension, SpecialName] public sealed class <>E__ContentName_For_IEnumerable_T { // Extension marker type is emitted as a nested type and re-declares its type parameters to include C#-isms // In this example, the type parameter `T0` is re-declared as `T` with a `notnull` constraint: // .class <>E__IEnumerableOfT.<>E__ContentName_For_IEnumerable_T_Source // .typeparam T // .custom instance void NullableAttribute::.ctor(uint8) = (...) [SpecialName] public static class <>E__ContentName_For_IEnumerable_T_Source { [SpecialName] public static $(IEnumerable source) => throw null; } [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")] public void Method() => throw null; [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")] internal static int Property { [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")] get => throw null; [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")] set => throw null; } [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")] public int Property2 { [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")] get => throw null; [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")] set => throw null; } } [Extension, SpecialName] public sealed class <>E__ContentName_For_IAsyncEnumerable_Int { [SpecialName] public static class <>E__ContentName_For_IAsyncEnumerable_Int_Values { [SpecialName] public static $(IAsyncEnumerable values) => throw null; } [ExtensionMarkerName("<>E__ContentName_For_IAsyncEnumerable_Int_Values")] public Task SumAsync() => throw null; } // Implementation for Method [Extension] public static void Method(IEnumerable source) { ... } // Implementation for Property internal static int get_Property() { ... } internal static void set_Property(int value) { ... } // Implementation for Property2 public static int get_Property2(IEnumerable source) { ... } public static void set_Property2(IEnumerable source, int value) { ... } // Implementation for SumAsync [Extension] public static int SumAsync(IAsyncEnumerable values) { ... } public static void Method2() { ... } } ``` Whenever extension members are used in source, we will emit those as reference to implementation methods. For example: an invocation of `enumerableOfInt.Method()` would be emitted as a static call to `IEnumerableExtensions.Method(enumerableOfInt)`. ## XML docs The doc comments on the extension block are emitted for the marker type (the DocID for the extension block is `E.<>E__MarkerContentName_For_ExtensionOfT'1` in the example below). They are allowed to reference the extension parameter and type parameters using `` and `` respectively). Note: you may not document the extension parameter or type parameters (with `` and ``) on an extension member. If two extension blocks are emitted as one marker type, their doc comments are merged as well. Tools consuming the xml docs are responsible for copying the `` and `` from the extension block onto the extension members as appropriate (ie. the parameter information should only be copied for instance members). An `` is emitted on implementation methods and it refers to the relevant extension member with a `cref`. For example, the implementation method for a getter refers to the documentation of the extension property. If the extension member has not doc comments, then the `` is omitted. For extension blocks and extension members, we don't presently warn if: - the extension parameter is documented, but the parameters on the extension member aren't - or vice-versa - or in the equivalent scenarios with undocumented type parameters For instance, the following doc comments: ``` /// Summary for E static class E { /// Summary for extension block /// Description for T /// Description for t extension(T t) { /// Summary for M, which may refer to and /// Description for U /// Description for u public void M(U u) => throw null!; /// Summary for P public int P => 0; } } ``` yield the following xml: ``` Test Summary for E Summary for extension block Description for T Description for t Summary for M, which may refer to and Description for U Description for u Summary for P ``` ### CREF references We can treat extension blocks like nested types, that can be addressed by their signature (as if it were a method with a single extension parameter). Example: `E.extension(ref int).M()`. But a cref cannot address an extension block itself. `E.extension(int)` could refer to a method named "extension" in type `E`. ```csharp static class E { extension(ref int i) { void M() { } // can be addressed by cref="E.extension(ref int).M()" or cref="extension(ref int).M()" within E, but not cref="M()" } extension(ref int i) { void M(int i2) { } // can be addressed by cref="E.extension(ref int).M(int)" or cref="extension(ref int).M(int)" within E } } ``` The lookup knowns to look in all matching extension blocks. As we disallow unqualified references to extension members, cref would also disallow them. The syntax would be: ```antlr member_cref : conversion_operator_member_cref | extension_member_cref // added | indexer_member_cref | name_member_cref | operator_member_cref ; extension_member_cref // added : 'extension' type_argument_list? cref_parameter_list '.' member_cref ; qualified_cref : type '.' member_cref ; cref : member_cref | qualified_cref | type_cref ; ``` It's an error to use `extension_member_cref` at top-level (`extension(int).M`) or nested in another extension (`E.extension(int).extension(string).M`). ## Breaking changes Types and aliases may not be named "extension". ## Open issues
Temporary section of the document related to open issues, including discussion of unfinalized syntax and alternative designs - Should we adjust receiver requirements when accessing an extension member? ([comment](https://github.com/dotnet/roslyn/pull/78685#discussion_r2126534632)) - ~~Confirm `extension` vs. `extensions` as the keyword~~ (answer: `extension`, LDM 2025-03-24) - ~~Confirm that we want to disallow `[ModuleInitializer]`~~ (answer: yes, disallow, LDM 2025-06-11) - ~~Confirm that we're okay to discard extension blocks as entry point candidates~~ (answer: yes, discard, LDM 2025-06-11) - ~~Confirm LangVer logic (skip new extensions, vs. consider and report them when picked)~~ (answer: bind unconditionally and report LangVer error except for instance extension methods, LDM 2025-06-11) - ~~Should `partial` be required for extension blocks that merge and have their doc comments merged?~~ (answer: doc comments are merged silently when blocks get merged, no `partial` needed, confirmed by email 2025-09-03) - ~~Confirm that members should not be named after the containing or the extended types.~~ (answer: yes, confirmed by email 2025-09-03) ### ~~Revisit grouping/conflict rules in light of portability issue: https://github.com/dotnet/roslyn/issues/79043~~ (answer: this scenario was resolved as part of new metadata design with content-based type names, it is allowed) The current logic is to group extension blocks that have the same receiver type. This doesn't account for constraints. This causes a portability issue with this scenario: ``` static class E { extension(ref T) where T : struct void M() extension(T) where T : class void M() } ``` The proposal is to use the same grouping logic that we're planning for the extension grouping type design, namely to account for CLR-level constraints (ie. ignoring notnull, tuple names, nullability annotations). ### Should refness be encoded in the grouping type name? - ~~Review proposal that `ref` not be included in extension grouping type name (needs further discussion after WG revisits grouping/conflict rules, LDM 2025-06-23)~~ (answer: confirmed by email 2025-09-03) ``` public static class E { extension(ref int) { public static void M() } } ``` It is emitted as: ``` public static class E { public static class <>ExtensionTypeXYZ { .. marker method ... void M() } } ``` And third-party CREF reference for `E.extension(ref int).M` are emitted as `M:E.<>ExtensionGroupingTypeXYZ.M()` If `ref` is removed or added to an extension parameter, we probably don't want the CREF to break. We don't much care for this scenario, as any usage as extension would be an ambiguity: ``` public static class E { extension(ref int) static void M() extension(int) static void M() } ``` But we care about this scenario (for portability and usefulness), and this should work with proposed metadata design after we adjust conflict rules: ``` static class E { extension(ref T) where T : struct void M() extension(T) where T : class void M() } ``` Not accounting for refness has a downside, as we loose portability in this scenario: ``` static class E { extension(ref T) void M() extension(T) void M() } // portability issue: since we're grouping without accounting for refness, the emitted extension members conflict (not implementation members). Mitigation: keep as classic extensions or split to another static class ``` ### nameof - ~~Should we disallow extension properties in nameof like we do classic and new extension methods?~~ (answer: we'd like to use `nameof(EnclosingStaticClass.ExtensionMember). Needs design, likely punt from .NET 10. LDM 2025-06-11) ### pattern-based constructs #### Methods - ~~Where should new extension methods come into play?~~ (answer: same places where classic extension methods come into play, LDM 2025-05-05) This includes: - `GetEnumerator`/`GetAsyncEnumerator` in `foreach` - `Deconstruct` in deconstruction, in positional pattern and foreach - `Add` in collection initializers - `GetPinnableReference` in `fixed` - `GetAwaiter` in `await` This excludes: - `Dispose`/`DisposeAsync` in `using` and `foreach` - `MoveNext`/`MoveNextAsync` in `foreach` - `Slice` and `int` indexers in implicit indexers (and possibly list-patterns?) - `GetResult` in `await` #### Properties and indexers - ~~Where should extension properties and indexers come into play?~~ (answer: let's start with the four, LDM 2025-05-05) We'd include: - object initializer: `new C() { ExtensionProperty = ... }` - dictionary intializer: `new C() { [0] = ... }` - `with`: `x with { ExtensionProperty = ... }` - property patterns: `x is { ExtensionProperty: ... }` We'd exclude: - `Current` in `foreach` - `IsCompleted` in `await` - `Count`/`Length` properties and indexers in list-pattern - `Count`/`Length` properties and indexers in implicit indexers ##### Delegate-returning properties - ~~Confirm that extension properties of this shape should only come into play in LINQ queries, to match what instance properties do.~~ (answer: makes sense, LDM 2025-04-06) ##### List and spread pattern - Confirm that extension `Index`/`Range` indexers should play in list-patterns (answer: not relevant for C# 14) ##### Revisit where `Count`/`Length` extension properties come into play #### [Collection expressions](../csharp-12.0/collection-expressions.md) - Extension `Add` works - Extension `GetEnumerator` works for spread - Extension `GetEnumerator` does not affect the determination of the element type (must be instance) - Static `Create` extension methods should not count as a blessed **create** method - Should extension countable properties affect collection expressions? #### [`params` collections](../csharp-13.0/params-collections.md) - Extensions `Add` does not affect what types are allowed with `params` #### [dictionary expressions](https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md) - Confirm that extension indexers don't play in dictionary expressions, as the presence of the indexer is an integral part of what defines a dictionary type. (answer: not relevant for C# 14) ### `extern` - ~~we're planning to allow `extern` for portability: https://github.com/dotnet/roslyn/issues/78572~~ (answer: approved, LDM 2025-06-23) ### Naming/numbering scheme for extension type [Issue](https://github.com/dotnet/roslyn/issues/78416) The current numbering system causes problems with the [validation of public APIs](https://learn.microsoft.com/dotnet/fundamentals/apicompat/package-validation/overview#validator-types) which ensures that public APIs match between reference-only assemblies and implementation assemblies. ~~Should we make one of the following changes?~~ (answer: we're adopting a content-based naming scheme to increase public API stability, and the tools will still need to be updated to account for marker methods) 1. adjust the tool 2. use some content-based naming scheme (TBD) 3. let the name be controlled via some syntax ### New generic extension Cast method still can't work in LINQ [Issue](https://github.com/dotnet/roslyn/issues/78415) In earlier designs of roles/extensions, it was possible to only specify the type arguments of the method explicitly. But now that we're focusing on seemless transition from classic extension methods, all the type arguments must be given explicitly. This fails to address a problem with extension Cast method usage in LINQ. ~~Should we make a change to extensions feature to accomodate this scenario?~~ (answer: no, this does not cause us to revisit the extension resolution design, LDM 2025-05-05) ### Constraining the extension parameter on an extension member ~~Should we allow the following?~~ (answer: no, this could be added later) ```csharp static class E { extension(T t) { public void M(U u) where T : C { } // error: 'E.extension(T).M(U)' does not define type parameter 'T' } } public class C { } ``` ### Nullability - ~~Confirm the current design, ie. maximal portability/compatibility~~ (answer: yes, LDM 2025-04-17) ```csharp extension([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool b) { public void AssertTrue() => throw null!; } ``` ```csharp extension([System.Diagnostics.CodeAnalysis.NotNullIfNotNull("o")] ref int? i) { public void M(object? o) => throw null!; } ``` ### Metadata - ~~Should skeleton methods throw `NotSupportedException` or some other standard exception (right now we do `throw null;`)?~~ (answer: yes, LDM 2025-04-17) - ~~Should we accept more than one parameter in marker method in metadata (in case new versions add more info)?~~ (answer: we can remain strict, LDM 2025-04-17) - ~~Should the extension marker or speakable implementation methods be marked with special name?~~ (answer: the marker method should be marked with special name and we should check it, but not implementation methods, LDM 2025-04-17) - ~~Should we add `[Extension]` attribute on the static class even when there is no instance extension method inside?~~ (answer: yes, LDM 2025-03-10) - ~~Confirm we should add `[Extension]` attribute to implementation getters and setters too.~~ (answer: no, LDM 2025-03-10) - ~~Confirm that the extension types should be marked with special name and the compiler will require this flag in metadata (this is a breaking change from preview)~~ (answer: approved, LDM 2025-06-23) #### static factory scenario - ~~What are the conflict rules for static methods?~~ (answer: use existing C# rules for the enclosing static type, no relaxation, LDM 2025-03-17) ### Lookup - ~~How to resolve instance method invocations now that we have speakable implementation names?~~ We prefer the skeleton method to its corresponding implementation method. - ~~How to resolve static extension methods?~~ (answer: just like instance extension methods, LDM 2025-03-03) - ~~How to resolve properties?~~ (answered in broad strokes LDM 2025-03-03, but needs follow-up for betterness) - ~~Scoping and shadowing rules for extension parameter and type parameters~~ (answer: in scope of extension block, shadowing disallowed, LDM 2025-03-10) - ~~How should ORPA apply to new extension methods?~~ (answer: treat extension blocks as transparent, the "containing type" for ORPA is the enclosing static class, LDM 2025-04-17) ``` public static class Extensions { extension(Type1) { [OverloadResolutionPriority(1)] public void Overload(...) } extension(Type2) { public void Overload(...) } } ``` - ~~Should ORPA apply to new extension properties?~~ (answer: yes and ORPA should be copied onto implementation methods, LDM 2025-04-23) ``` public static class Extensions { extension(int[] i) { public P { get => } } extension(ReadOnlySpan r) { [OverloadResolutionPriority(1)] public P { get => } } } ``` - How to retcon the classic extension resolution rules? Do we 1. update the standard for classic extension methods, and use that to also describe new extension methods, 2. keep the existing language for classic extension methods, use that to also describe new extension methods, but have a known spec deviation for both, 3. keep the existing language for classic extension methods, but use different language for new extension methods, and only have a known spec deviation for classic extension methods? - ~~Confirm that we want to disallow explicit type arguments on a property access~~ (answer: no property access with explicit type arguments, discussed in WG) ```csharp string s = "ran"; _ = s.P; // error static class E { extension(T t) { public int P => 0; } } ``` - ~~Confirm that we want betterness rules to apply even when the receiver is a type~~ (answer: a type-only extension parameter should be considered when resolving static extension members, LDM 2025-06-23) ```csharp int.M(); static class E1 { extension(int) { public static void M() { } } } static class E2 { extension(in int i) { public static void M() => throw null; } } ``` - ~~Confirm that we're okay with having an ambiguity when both methods and properties are applicable~~ (answer: we should design a proposal to do better than the status quo, punting out of .NET 10, LDM 2025-06-23) - ~~Confirm that we don't want some betterness across all members before we determine the winning member kind~~ (answer: punting out of .NET 10, WG 2025-07-02) ``` string s = null; s.M(); // error static class E { extension(string s) { public System.Action M => throw null; } extension(object o) { public string M() => throw null; } } ``` - ~~Do we have an implicit receiver within extension declarations?~~ (answer: no, was previously discussed in LDM) ```csharp static class E { extension(object o) { public void M() { M2(); } public void M2() { } } } ``` - ~~Should we allow lookup on type parameter?~~ ([discussion](https://github.com/dotnet/csharplang/discussions/8696#discussioncomment-12817547)) (answer: no, we're going to wait on feedback, LDM 2025-04-16) ### Accessibility - ~~What is the meaning of accessibility within an extension declaration?~~ (answer: extension declarations do not count as an accessibility scope, LDM 2025-03-17) - ~~Should we apply the "inconsistent accessibility" check on the receiver parameter even for static members?~~ (answer: yes, LDM 2025-04-17) ```csharp public static class Extensions { extension(PrivateType p) { // We report inconsistent accessibility error, // because we generate a `public static void M(PrivateType p)` implementation in enclosing type public void M() { } public static void M2() { } // should we also report here, even though not technically necessary? } private class PrivateType { } } ``` ### Extension declaration validation - ~~Should we relax the type parameter validation (inferrability: all the type parameters must appear in the type of the extension parameter) where there are only methods?~~ (answer: yes, LDM 2025-04-06) This would allow porting 100% of classic extension methods. If you have `TResult M(this TSource source)`, you could port it as `extension(TSource source) { TResult M() ... }`. - ~~Confirm whether init-only accessors should be allowed in extensions~~ (answer: okay to disallow for now, LDM 2025-04-17) - ~~Should the only difference in receiver ref-ness be allowed `extension(int receiver) { public void M2() {} }` `extension(ref int receiver) { public void M2() {} }`?~~ (answer: no, keep spec'ed rule, LDM 2025-03-24) - ~~Should we complain about a conflict like this `extension(object receiver) { public int P1 => 1; }` `extension(object receiver) { public int P1 {set{}} }`?~~ (answer: yes, keep spec'ed rule, LDM 2025-03-24) - ~~Should we complain about conflicts between skeleton methods that aren't conflicts between implementation methods?~~ (answer: yes, keep spec'ed rule, LDM 2025-03-24) ```csharp static class E { extension(object) { public void Method() { } public static void Method() { } } } ``` The current conflict rules are: 1. check no conflict within similar extensions using class/struct rules, 2. check no conflict between implementation methods across various extensions declarations. - ~~Do we stil need the first part of the rules?~~ (answer: yes, we're keeping this structure as it helps with consumption of the APIs, LDM 2025-03-24) ### XML docs - ~~Is `paramref` to receiver parameter supported on extension members? Even on static? How is it encoded in the output? Probably standard way `` would work for a human, but there is a risk that some existing tools won't be happy to not find it among the parameters on the API.~~ (answer: yes paramref to extension parameter is allowed on extension members, LDM 2025-05-05) - ~~Are we supposed to copy doc comments to the implementation methods with speakable names?~~ (answer: no copying, LDM 2025-05-05) - ~~Should `` element corresponding to receiver parameter be copied from extension container for instance methods? Anything else should be copied from container to implementation methods (`` etc.) ?~~ (answer: no copying, LDM 2025-05-05) - ~~Should `` for extension parameter be allowed on extension members as an override?~~ (answer: no, for now, LDM 2025-05-05) - Will the summary on extension blocks would appear anywhere? #### CREF - ~~Confirm syntax~~ (answer: proposal is good, LDM 2025-06-09) - ~~Should it be possible to refer to an extension block (`E.extension(int)`)?~~ (answer: no, LDM 2025-06-09) - ~~Should it be possible to refer to a member using an unqualified syntax: `extension(int).Member`?~~ (answer: yes, LDM 2025-06-09) - Should we use different characters for unspeakable name, to avoid XML escaping? (answer: defer to WG, LDM 2025-06-09) - ~~Confirm it's okay that both references to skeleton and implementation methods are possible: `E.M` vs. `E.extension(int).M`. Both seem necessary (extension properties and portability of classic extension methods).~~ (answer: yes, LDM 2025-06-09) - ~~Are extension metadata names problematic for versioning docs?~~ (answer: yes, we're going to move away from ordinals and use a content-based stable naming scheme) ### Add support for more member kinds We do not need to implement all of this design at once, but can approach it one or a few member kinds at a time. Based on known scenarios in our core libraries, we should work in the following order: 1. Properties and methods (instance and static) 2. Operators 3. Indexers (instance and static, may be done opportunistically at an earlier point) 4. Anything else How much do we want to front-load the design for other kinds of members? ```antlr extension_member_declaration // add : constant_declaration | field_declaration | method_declaration | property_declaration | event_declaration | indexer_declaration | operator_declaration | constructor_declaration | finalizer_declaration | static_constructor_declaration | type_declaration ; ``` #### Nested types If we do choose to move forward with extension nested types, here are some notes from previous discussions: - There would be a conflict if two extension declarations declared nested extension types with same names and arity. We do not have a solution for representing this in metadata. - The rough approach we discussed for metadata: 1. we would emit a skeleton nested type with original type parameters and no members 2. we would emit an implementation nested type with prepended type parameters from the extension declaration and all the member implementations as they appear in source (modulo references to type parameters) #### Constructors Constructors are generally described as an instance member in C#, since their body has access to the newly created value through the `this` keyword. This does not work well for the parameter-based approach to instance extension members, though, since there is no prior value to pass in as a parameter. Instead, extension constructors work more like static factory methods. They are considered static members in the sense that they don't depend on a receiver parameter name. Their bodies need to explicitly create and return the construction result. The member itself is still declared with constructor syntax, but cannot have `this` or `base` initializers and does not rely on the receiver type having accessible constructors. This also means that extension constructors can be declared for types that have no constructors of their own, such as interfaces and enum types: ``` c# public static class Enumerable { extension(IEnumerable) { public static IEnumerable(int start, int count) => Range(start, count); } public static IEnumerable Range(int start, int count) { ... } } ``` Allows: ``` var range = new IEnumerable(1, 100); ``` ### Shorter forms The proposed design avoids per-member repetition of receiver specifications, but does end up with extension members being nested two-deep in a static class _and_ and extension declaration. It will likely be common for static classes to contain only one extension declaration or for extension declarations to contain only one member, and it seems plausible for us to allow syntactic abbreviation of those cases. __Merge static class and extension declarations:__ ``` c# public static class EmptyExtensions : extension(IEnumerable source) { public bool IsEmpty => !source.GetEnumerator().MoveNext(); } ``` This ends up looking more like what we've been calling a "type-based" approach, where the container for extension members is itself named. __Merge extension declaration and extension member:__ ``` c# public static class Bits { extension(ref ulong bits) public bool this[int index] { get => (bits & Mask(index)) != 0; set => bits = value ? bits | Mask(index) : bits & ~Mask(index); } static ulong Mask(int index) => 1ul << index; } public static class Enumerable { extension(IEnumerable source) public IEnumerable Where(Func predicate) { ... } } ``` This ends up looking more like what we've been calling a "member-based" approach, where each extension member contains its own receiver specification. ================================================ FILE: proposals/csharp-14.0/field-keyword.md ================================================ # `field` keyword in properties [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Extend all properties to allow them to reference an automatically generated backing field using the new contextual keyword `field`. Properties may now also contain an accessor _without_ a body alongside an accessor _with_ a body. ## Motivation Auto properties only allow for directly setting or getting the backing field, giving some control only by placing access modifiers on the accessors. Sometimes there is a need to have additional control over what happens in one or both accessors, but this confronts users with the overhead of declaring a backing field. The backing field name must then be kept in sync with the property, and the backing field is scoped to the entire class which can result in accidental bypassing of the accessors from within the class. There are several common scenarios. Within the getter, there is lazy initialization, or default values when the property has never been given. Within the setter, there is applying a constraint to ensure the validity of a value, or detecting and propagating updates such as by raising the `INotifyPropertyChanged.PropertyChanged` event. In these cases by now you always have to create an instance field and write the whole property yourself. This not only adds a fair amount of code, but it also leaks the backing field into the rest of the type's scope, when it is often desirable to only have it be available to the bodies of the accessors. ## Glossary - **Auto property**: Short for "automatically implemented property" ([§15.7.4](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1574-automatically-implemented-properties)). Accessors on an auto property have no body. The implementation and backing storage are both provided by the compiler. Auto properties have `{ get; }`, `{ get; set; }`, or `{ get; init; }`. - **Auto accessor**: Short for "automatically implemented accessor." This is an accessor that has no body. The implementation and backing storage are both provided by the compiler. `get;`, `set;` and `init;` are auto accessors. - **Full accessor**: This is an accessor that has a body. The implementation is not provided by the compiler, though the backing storage may still be (as in the example `set => field = value;`). - **Field-backed property**: This is either a property using the `field` keyword within an accessor body, or an auto property. - **Backing field**: This is the variable denoted by the `field` keyword in a property's accessors, which is also implicitly read or written in automatically implemented accessors (`get;`, `set;`, or `init;`). ## Detailed design For properties with an `init` accessor, everything that applies below to `set` would apply instead to the `init` accessor. There are two syntax changes: 1. There is a new contextual keyword, `field`, which may be used within property accessor bodies to access a backing field for the property declaration ([LDM decision](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-15.md#should-field-and-value-be-keywords-in-property-or-accessor-signatures-what-about-nameof-in-those-spaces)). 2. Properties may now mix and match auto accessors with full accessors ([LDM decision](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-24.md#mixing-auto-accessors)). "Auto property" will continue to mean a property whose accessors have no bodies. None of the examples below will be considered auto properties. Examples: ```cs { get; set => Set(ref field, value); } ``` ```cs { get => field ?? parent.AmbientValue; set; } ``` Both accessors may be full accessors with either one or both making use of `field`: ```cs { get => field; set => field = value; } ``` ```cs { get => field; set => throw new InvalidOperationException(); } ``` ```cs { get => overriddenValue; set => field = value; } ``` ```cs { get; set { if (field == value) return; field = value; OnXyzChanged(); } } ``` Expression-bodied properties and properties with only a `get` accessor may also use `field`: ```cs public string LazilyComputed => field ??= Compute(); ``` ```cs public string LazilyComputed { get => field ??= Compute(); } ``` Set-only properties may also use `field`: ```cs { set { if (field == value) return; field = value; OnXyzChanged(new XyzEventArgs(value)); } } ``` ### Breaking changes The existence of the `field` contextual keyword within property accessor bodies is a potentially breaking change. Since `field` is a keyword and not an identifier, it can only be "shadowed" by an identifier using the normal keyword-escaping route: `@field`. All identifiers named `field` declared within property accessor bodies can safeguard against breaks when upgrading from C# versions prior to 14 by adding the initial `@`. If a variable named `field` is declared in a property accessor, an error is reported. In language version 14 or later, a warning is reported if a *primary expression* `field` refers to the backing field, but would have referred to a different symbol in an earlier language version. ### Field-targeted attributes As with auto properties, any property that uses a backing field in one of its accessors will be able to use field-targeted attributes: ```cs [field: Xyz] public string Name => field ??= Compute(); [field: Xyz] public string Name { get => field; set => field = value; } ``` A field-targeted attribute will remain invalid unless an accessor uses a backing field: ```cs // ❌ Error, will not compile [field: Xyz] public string Name => Compute(); ``` ### Property initializers Properties with initializers may use `field`. The backing field is directly initialized rather than the setter being called ([LDM decision](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-02.md#open-questions-in-field)). Calling a setter for an initializer is not an option; initializers are processed before calling base constructors, and it is illegal to call any instance method before the base constructor is called. This is also important for default initialization/definite assignment of structs. This yields flexible control over initialization. If you want to initialize without calling the setter, you use a property initializer. If you want to initialize by calling the setter, you use assign the property an initial value in the constructor. Here's an example of where this is useful. We believe the `field` keyword will find a lot of its use with view models because of the elegant solution it brings for the `INotifyPropertyChanged` pattern. View model property setters are likely to be databound to UI and likely to cause change tracking or trigger other behaviors. The following code needs to initialize the default value of `IsActive` without setting `HasPendingChanges` to `true`: ```cs class SomeViewModel { public bool HasPendingChanges { get; private set; } public bool IsActive { get; set => Set(ref field, value); } = true; private bool Set(ref T location, T value) { if (EqualityComparer.Default.Equals(location, value)) return false; location = value; HasPendingChanges = true; return true; } } ``` This difference in behavior between a property initializer and assigning from the constructor can also be seen with virtual auto properties in previous versions of the language: ```cs using System; // Nothing is printed; the property initializer is not // equivalent to `this.IsActive = true`. _ = new Derived(); class Base { public virtual bool IsActive { get; set; } = true; } class Derived : Base { public override bool IsActive { get => base.IsActive; set { base.IsActive = value; Console.WriteLine("This will not be reached"); } } } ``` ### Constructor assignment As with auto properties, assignment in the constructor calls the (potentially virtual) setter if it exists, and if there is no setter it falls back to directly assigning to the backing field. ```cs class C { public C() { P1 = 1; // Assigns P1's backing field directly P2 = 2; // Assigns P2's backing field directly P3 = 3; // Calls P3's setter P4 = 4; // Calls P4's setter } public int P1 => field; public int P2 { get => field; } public int P4 { get => field; set => field = value; } public int P3 { get => field; set; } } ``` ### Definite assignment in structs Even though they can't be referenced in the constructor, backing fields denoted by the `field` keyword are subject to default-initialization and disabled-by-default warnings under the same conditions as any other struct fields ([LDM decision 1](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-02.md#property-assignment-in-structs), [LDM decision 2](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-05-02.md#definite-assignment-of-manually-implemented-setters)). For example (these diagnostics are silent by default): ```cs public struct S { public S() { // CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit // assignments of 'default' to non-explicitly assigned fields. _ = P1; } public int P1 { get => field; } } ``` ```cs public struct S { public S() { // CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit // assignments of 'default' to non-explicitly assigned fields. P2 = 5; } public int P2 { get => field; set => field = value; } } ``` ### Ref-returning properties Like with auto properties, the `field` keyword will not be available for use in ref-returning properties. Ref-returning properties cannot have set accessors, and without a set accessor, the get accessor and the property initializer would be the only things able to access the backing field. Absent use cases for this, now is not the time for ref-returning properties to start to be able to be written as auto properties. ### Nullability A principle of the the Nullable Reference Types feature was to understand existing idiomatic coding patterns in C# and to require as little ceremony as possible around those patterns. The `field` keyword proposal enables simple, idiomatic patterns to address widely asked-for scenarios, such as lazily initialized properties. It's important for the Nullable Reference Types to mesh well with these new coding patterns. Goals: - A reasonable level of null-safety should be ensured for various usage patterns of the `field` keyword feature. - Patterns that use the `field` keyword should feel as though they've always been part of the language. Avoid making the user jump through hoops to enable Nullable Reference Types in code that is perfectly idiomatic for the `field` keyword feature. One of the key scenarios is lazily initialized properties: ```cs public class C { public C() { } // It would be undesirable to warn about 'Prop' being uninitialized here string Prop => field ??= GetPropValue(); } ``` The following nullability rules will apply not just to properties that use the `field` keyword, but also to existing auto properties. #### Nullability of the *backing field* See [Glossary](#glossary) for definitions of new terms. The *backing field* has the same type as the property. However, its nullable annotation may differ from the property. To determine this nullable annotation, we introduce the concept of *null-resilience*. *Null-resilience* intuitively means that the property's `get` accessor preserves null-safety even when the field contains the `default` value for its type. A *field-backed property* is determined to be *null-resilient* or not by performing a special nullable analysis of its `get` accessor. Note that this analysis is **only** performed when the property may be of reference type and it is *not-annotated*. - Two separate nullable analysis passes are performed: one where the `field`'s nullable annotation is *not-annotated*, and one where its nullable annotation is *annotated*. The nullable diagnostics resulting from each analysis are recorded. - If there is a nullable diagnostic in the *annotated* pass, which was not present in the *not-annotated* pass, then the property is not null-resilient. Otherwise, it is null-resilient. - The implementation can optimize by first performing the *annotated* pass. If this results in no diagnostics at all, then the property is null-resilient and the *not-annotated* pass can be skipped. - If the property does not have a get accessor, it is (vacuously) null-resilient. - If the get accessor is auto-implemented, the property is not null-resilient. Null-forgiving (`!`) operators, directives like `#nullable disable` and `#pragma disable warning`, and conventional project-level settings like ``, are all respected when deciding if a nullable analysis has diagnostics. [DiagnosticSuppressors](https://github.com/dotnet/roslyn/blob/a91d7700db4a8b5da626d272d371477c6975f10e/docs/analyzers/DiagnosticSuppressorDesign.md) are ignored when deciding if a nullable analysis has diagnostics. The nullable annotation of the backing field is determined as follows: - If the associated property's nullable annotation is *annotated* or *oblivious*, then the nullable annotation is the same as the nullable annotation of the associated property. - If the associated property's nullable annotation is *not-annotated*, then: - If the property is *null-resilient*, the nullable annotation is *annotated*. - If the property is not *null-resilient*, the nullable annotation is *not-annotated*. #### Constructor analysis Currently, an auto property is treated very similarly to an ordinary field in [nullable constructor analysis](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/nullable-constructor-analysis.md). We extend this treatment to *field-backed properties*, by treating every *field-backed property* as a proxy to its backing field. We update the following spec language from the previous [proposed approach](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/nullable-constructor-analysis.md#proposed-approach) to accomplish this (new language in **bold**): > At each explicit or implicit 'return' in a constructor, we give a warning for each member whose flow state is incompatible with its annotations and nullability attributes. **If the member is a field-backed property, the nullable annotation of the backing field is used for this check. Otherwise, the nullable annotation of the member itself is used.** A reasonable proxy for this is: if assigning the member to itself at the return point would produce a nullability warning, then a nullability warning will be produced at the return point. > [!NOTE] > A key trait of a *null-resilient* property is that it returns a value with "correct" nullability even when the backing field's value is `default`. Given this, we could consider not even tracking a flow state for such properties. However, this seems like a larger change in nullable analysis of properties, which we don't have any motivating scenario for changing at this time. This is essentially a constrained interprocedural analysis. We anticipate that in order to analyze a constructor, it will be necessary to do binding and "null-resilience" analysis on all potentially applicable get accessors in the same type, which use the `field` contextual keyword and have *not-annotated* nullability. We speculate that this is not prohibitively expensive because getter bodies are usually not very complex, and that the "null-resilience" analysis only needs to be performed once regardless of how many constructors are in the type. #### Setter analysis For simplicity, we use the terms "setter" and "set accessor" to refer to either a `set` or `init` accessor. There is a need to check that setters of non-*null-resilient* field-backed properties actually initialize the backing field. ```cs class C { string Prop { get => field; // getter is not null-resilient, so `field` is not-annotated. // We should warn here that `field` may be null when exiting. set { } } public C() { Prop = "a"; // ok } public static void Main() { new C().Prop.ToString(); // no warning, NRE at runtime } } ``` The initial flow state of the *backing field* in the setter of a *field-backed property* is determined as follows: - If the property has an initializer, then the initial flow state is the same as the flow state after visiting the initializer. - Otherwise, the initial flow state is the same as the flow state given by `field = default;`. At each explicit or implicit 'return' in the setter, a warning is reported if the flow state of the *backing field* is incompatible with its annotations and nullability attributes. #### Remarks This formulation is intentionally similar to ordinary fields in constructors. Essentially, because only the property accessors can actually refer to the backing field, the setter is treated as a "mini-constructor" for the backing field. Much like with ordinary fields, we usually know the property was initialized in the constructor because it was set, but not necessarily. Simply returning within a branch where `Prop != null` was true is also good enough for our constructor analysis, since we understand that untracked mechanisms may have been used to set the property. Alternatives were considered; see the [Nullability alternatives](#nullability-alternatives) section. ### `nameof` In places where `field` is a keyword, `nameof(field)` will fail to compile ([LDM decision](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-15.md#usage-in-nameof)), like `nameof(nint)`. It is not like `nameof(value)`, which is the thing to use when property setters throw ArgumentException as some do in the .NET core libraries. In contrast, `nameof(field)` has no expected use cases. ### Overrides Overriding properties may use `field`. Such usages of `field` refer to the backing field for the overriding property, separate from the backing field of the base property if it has one. There is no ABI for exposing the backing field of a base property to overriding classes since this would break encapsulation. Like with auto properties, properties which use the `field` keyword and override a base property must override all accessors ([LDM decision](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-05-02.md#partial-overrides-of-virtual-properties)). ### Captures `field` should be able to be captured in local functions and lambdas, and references to `field` from inside local functions and lambdas are allowed even if there are no other references ([LDM decision 1](https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-21.md#open-question-in-semi-auto-properties), [LDM decision 2](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-15.md#should-field-and-value-be-considered-keywords-in-lambdas-and-local-functions-within-property-accessors)): ```cs public class C { public static int P { get { Func f = static () => field; return f(); } } } ``` ## Field usage warnings When the `field` keyword is used in an accessor, the compiler's existing analysis of unassigned or unread fields will include that field. - CS0414: The backing field for property 'Xyz' is assigned but its value is never used - CS0649: The backing field for property 'Xyz' is never assigned to, and will always have its default value ## Specification changes ### Syntax When compiling with language version 14 or higher, `field` is considered a keyword when used as a *primary expression* ([LDM decision](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-07-15.md)) in the following locations ([LDM decision](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-15.md#field-and-value-as-contextual-keywords)): - In method bodies of `get`, `set`, and `init` accessors in properties *but not* indexers - In attributes applied to those accessors - In nested lambda expressions and local functions, and in LINQ expressions in those accessors In all other cases, including when compiling with language version 12 or lower, `field` is considered an identifier. ```diff primary_no_array_creation_expression : literal + | 'field' | interpolated_string_expression | ... ; ``` ### Properties [§15.7.1](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1571-general) *Properties - General* > A *property_initializer* may only be given for ~~an automatically implemented property, and~~ **a property that has a backing field that will be emitted. The *property_initializer*** causes the initialization of the underlying field of such properties with the value given by the *expression*. [§15.7.4](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1574-automatically-implemented-properties) *Automatically implemented properties* > An automatically implemented property (or auto-property for short), is a non-abstract, non-extern, non-ref-valued > property with ~~semicolon-only accessor bodies. Auto-properties shall have a get accessor and may optionally have a set accessor.~~ **either or both of:** > 1. **an accessor with a semicolon-only body** > 2. **usage of the `field` contextual keyword within the accessors or** > **expression body of the property** > > When a property is specified as an automatically implemented property, a hidden **unnamed** backing field is automatically > available for the property ~~, and the accessors are implemented to read from and write to that backing field~~. > **For auto-properties, any semicolon-only `get` accessor is implemented to read from, and any semicolon-only** > **`set` accessor to write to its backing field.** > > ~~The hidden backing field is inaccessible, it can be read and written only through the automatically implemented property accessors, even within the containing type.~~ > **The backing field can be referenced directly using the `field` keyword** > **within all accessors and within the property expression body. Because the field is unnamed, it cannot be used in a** > **`nameof` expression.** > > If the auto-property has ~~no set accessor~~ **only a semicolon-only get accessor**, the backing field is considered `readonly` ([§15.5.3](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1553-readonly-fields)). > Just like a `readonly` field, a read-only auto-property **(without a set accessor or an init accessor)** may also be assigned to in the body of a constructor > of the enclosing class. Such an assignment assigns directly to the ~~read-only~~ backing field of the property. > > **An auto-property is not allowed to only have a single semicolon-only `set` accessor without a `get` accessor.** > > An auto-property may optionally have a *property_initializer*, which is applied directly to the backing field as a *variable_initializer* ([§17.7](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/arrays.md#177-array-initializers)). The following example: ```csharp // No 'field' symbol in scope. public class Point { public int X { get; set; } public int Y { get; set; } } ``` is equivalent to the following declaration: ```csharp // No 'field' symbol in scope. public class Point { public int X { get { return field; } set { field = value; } } public int Y { get { return field; } set { field = value; } } } ``` which is equivalent to: ```csharp // No 'field' symbol in scope. public class Point { private int __x; private int __y; public int X { get { return __x; } set { __x = value; } } public int Y { get { return __y; } set { __y = value; } } } ``` The following example: ```csharp // No 'field' symbol in scope. public class LazyInit { public string Value => field ??= ComputeValue(); private static string ComputeValue() { /*...*/ } } ``` is equivalent to the following declaration: ```csharp // No 'field' symbol in scope. public class Point { private string __value; public string Value { get { return __value ??= ComputeValue(); } } private static string ComputeValue() { /*...*/ } } ``` ## Alternatives ### Nullability alternatives In addition to the *null-resilience* approach outlined in the [Nullability](#nullability) section, the working group suggested the following alternatives for the LDM's consideration: #### Do nothing We could introduce no special behavior at all here. In effect: - Treat a field-backed property the same way auto-properties are treated today--must be initialized in constructor except when marked required, etc. - No special treatment of the field variable when analyzing property accessors. It is simply a variable with the same type and nullability as the property. Note that this would result in nuisance warnings for "lazy property" scenarios, in which case users would likely need to assign `null!` or similar to silence constructor warnings. A "sub-alternative" we can consider is to also completely ignore properties using `field` keyword for nullable constructor analysis. In that case, there would be no warnings anywhere about the user needing to initialize anything, but also no nuisance for the user, regardless of what initialization pattern they may be using. Because we are only planning to ship the `field` keyword feature under the Preview LangVersion in .NET 9, we expect to have some ability to change the nullable behavior for the feature in .NET 10. Therefore, we could consider adopting a "lower-cost" solution like this one in the short term, and growing up to one of the more complex solutions in the long term. #### `field`-targeted nullability attributes We could introduce the following defaults, achieving a reasonable level of null safety, without involving any interprocedural analysis at all: 1. The `field` variable always has the same nullable annotation as the property. 2. Nullability attributes `[field: MaybeNull, AllowNull]` etc. can be used to customize the nullability of the backing field. 3. field-backed properties are checked for initialization in constructors based on the field's nullable annotation and attributes. 4. setters in field-backed properties check for initialization of `field` similarly to constructors. This would mean the "little-l lazy scenario" would look like this instead: ```cs class C { public C() { } // no need to warn about initializing C.Prop, as the backing field is marked nullable using attributes. [field: AllowNull, MaybeNull] public string Prop => field ??= GetPropValue(); } ``` One reason we shied away from using nullability attributes here is that the ones we have are really oriented around describing inputs and outputs of signatures. They are cumbersome to use to describe the nullability of long-lived variables. - In practice, `[field: MaybeNull, AllowNull]` is required to make the field behave "reasonably" as a nullable variable, which gives maybe-null initial flow state, and allows possible null values to be written to it. This feels cumbersome to ask users to do for relatively common "little-l lazy" scenarios. - If we pursued this approach, we would consider adding a warning when `[field: AllowNull]` is used, suggesting to also add `MaybeNull`. This is because AllowNull by itself doesn't do what users need out of a nullable variable: it assumes the field is initially not-null when we never saw anything write to it yet. - We could also consider adjusting the behavior of `[field: MaybeNull]` on the `field` keyword, or even fields in general, to allow nulls to also be written to the variable, as if `AllowNull` were implicitly also present. #### Infer the initial flow state of the `field` Instead of defining the behavior in terms of the `field`'s nullable annotation, we could just define the initial flow state of the `field` in the 'get' accessor. This would reflect the reality that it's ok to assign a possible null value to the `field`. The other forms of analysis we have would succeed in identifying any resulting null safety issues from doing that. However, this came about in a phase of implementation where the churn of making this change was not thought to be worthwhile. This is something we could conceivably adjust under the hood in the future, with very few users perceiving any break--this alternative approach being generally more permissive than the one we actually implemented. ## Answered LDM questions ### Syntax locations for keywords In accessors where `field` and `value` could bind to a synthesized backing field or an implicit setter parameter, in which syntax locations should the identifiers be considered keywords? 1. always 1. *primary expressions* only 1. never The first two cases are breaking changes. If the identifiers are *always* considered keywords, that is a breaking change for the following for instance: ```csharp class MyClass { private int field; public int P => this.field; // error: expected identifier private int value; public int Q { set { this.value = value; } // error: expected identifier } } ``` If the identifiers are keywords when used as *primary expressions* only, the breaking change is smaller. The most common break may be unqualified use of an existing member named `field`. ```csharp class MyClass { private int field; public int P => field; // binds to synthesized backing field rather than 'this.field' } ``` There is also a break when `field` or `value` is redeclared in a nested function. This may be the only break for `value` for *primary expressions*. ```csharp class MyClass { private IEnumerable _fields; public bool HasNotNullField { get => _fields.Any(field => field is { }); // 'field' binds to synthesized backing field } public IEnumerable Fields { get { return _fields; } set { _fields = value.Where(value => Filter(value)); } // 'value' binds to setter parameter } } ``` If the identifiers are *never* considered keywords, the identifiers will only bind to a synthesized backing field or the implicit parameter when the identifiers do not bind to other members. There is no breaking change for this case. #### Answer `field` is a keyword in appropriate accessors when used as a *primary expression* only; `value` is never considered a keyword. ### Scenarios similar to `{ set; }` `{ set; }` is currently disallowed and this makes sense: the field which this creates can never be read. There are now new ways to end up in a situation where the setter introduces a backing field that is never read, such as the expansion of `{ set; }` into `{ set => field = value; }`. Which of these scenarios should be allowed to compile? Assume that the "field is never read" warning would apply just like with a manually declared field. 1. `{ set; }` - Disallowed today, continue disallowing 1. `{ set => field = value; }` 1. `{ get => unrelated; set => field = value; }` 1. `{ get => unrelated; set; }` 1. ```cs { set { if (field == value) return; field = value; SendEvent(nameof(Prop), value); } } ``` 1. ```cs { get => unrelated; set { if (field == value) return; field = value; SendEvent(nameof(Prop), value); } } ``` #### Answer Only disallow what is already disallowed today in auto properties, the bodyless `set;`. ### `field` in event accessor Should `field` be a keyword in an event accessor, and should the compiler generate a backing field? ```csharp class MyClass { public event EventHandler E { add { field += value; } remove { field -= value; } } } ``` **Recommendation**: `field` is *not* a keyword within an event accessor, and no backing field is generated. #### Answer Recommendation taken. `field` is *not* a keyword within an event accessor, and no backing field is generated. ### Nullability of `field` Should the proposed nullability of `field` be accepted? See the [Nullability](#nullability) section, and the open question within. #### Answer General proposal is adopted. Specific behavior still needs more review. ### `field` in property initializer Should `field` be a keyword in a property initializer and bind to the backing field? ```csharp class A { const int field = -1; object P1 { get; } = field; // bind to const (ok) or backing field (error)? } ``` Are there useful scenarios for referencing the backing field in the initializer? ```csharp class B { object P2 { get; } = (field = 2); // error: initializer cannot reference instance member static object P3 { get; } = (field = 3); // ok, but useful? } ``` In the example above, binding to the backing field should result in an error: "initializer cannot reference non-static field". #### Answer We will bind the initializer as in previous versions of C#. We won't put the backing field in scope, nor will we prevent referencing other members named `field`. ### Interaction with partial properties #### Initializers When a partial property uses `field`, which parts should be allowed to have an initializer? ```cs partial class C { public partial int Prop { get; set; } = 1; public partial int Prop { get => field; set => field = value; } = 2; } ``` - It seems clear that an error should occur when both parts have an initializer. - We can think of use cases where either the definition or implementation part might want to set the initial value of the `field`. - It seems like if we permit the initializer on the definition part, it is effectively forcing the implementer to use `field` in order for the program to be valid. Is that fine? - We think it will be common for generators to use `field` whenever a backing field of the same type is needed in the implementation. This is in part because generators often want to enable their users to use `[field: ...]` targeted attributes on the property definition part. Using the `field` keyword saves the generator implementer the trouble of "forwarding" such attributes to some generated field and suppressing the warnings on the property. Those same generators are likely to also want to allow the user to specify an initial value for the field. **Recommendation**: Permit an initializer on either part of a partial property when the implementation part uses `field`. Report an error if both parts have an initializer. #### Answer Recommendation accepted. Either declaring or implementing property locations can use an initializer, but not both at the same time. #### Auto-accessors As originally designed, partial property implementation must have bodies for all the accessors. However, recent iterations of the `field` keyword feature have included the notion of "auto-accessors". Should partial property implementations be able to use such accessors? If they are used exclusively, it will be indistinguishable from a defining declaration. ```cs partial class C { public partial int Prop0 { get; set; } public partial int Prop0 { get => field; set => field = value; } // this is equivalent to the two "semi-auto" forms below. public partial int Prop1 { get; set; } public partial int Prop1 { get => field; set; } // is this a valid implementation part? public partial int Prop2 { get; set; } public partial int Prop2 { get; set => field = value; } // what about this? will there be disagreement about which is the "best" style? public partial int Prop3 { get; } public partial int Prop3 { get => field; } // it will only be valid to use at most 1 auto-accessor, when a second accessor is manually implemented. ``` **Recommendation**: Disallow auto-accessors in partial property implementations, because the limitations around when they would be usable are more confusing to follow than the benefit of allowing them. #### Answer At least one implementing accessor must be manually implemented, but the other accessor can be automatically implemented. ### Readonly field When should the synthesized backing field be considered *read-only*? ```csharp struct S { readonly object P0 { get => field; } = ""; // ok object P1 { get => field ??= ""; } // ok readonly object P2 { get => field ??= ""; } // error: 'field' is readonly readonly object P3 { get; set { _ = field; } } // ok readonly object P4 { get; set { field = value; } } // error: 'field' is readonly } ``` When the backing field is considered *read-only*, the field emitted to metadata is marked `initonly`, and an error is reported if `field` is modified other than in an initializer or constructor. **Recommendation**: The synthesized backing field is *read-only* when the containing type is a `struct` and the property or containing type is declared `readonly`. #### Answer Recommendation is accepted. ### Readonly context and `set` Should a `set` accessor be allowed in a `readonly` context for a property that uses `field`? ```csharp readonly struct S1 { readonly object _p1; object P1 { get => _p1; set { } } // ok object P2 { get; set; } // error: auto-prop in readonly struct must be readonly object P3 { get => field; set { } } // ok? } struct S2 { readonly object _p1; readonly object P1 { get => _p1; set { } } // ok readonly object P2 { get; set; } // error: auto-prop with set marked readonly readonly object P3 { get => field; set { } } // ok? } ``` #### Answer There could be scenarios for this where you're implementing a `set` accessor on a `readonly` struct and either passing it through, or throwing. We will allow this. ### `[Conditional]` code Should the synthesized field be generated when `field` is used only in omitted calls to [*conditional methods*](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/attributes.md#22532-conditional-methods)? For instance, should a backing field be generated for the following in a non-DEBUG build? ```csharp class C { object P { get { Debug.Assert(field is null); return null; } } } ``` For reference, fields for *primary constructor parameters* are generated in similar cases - see [sharplab.io](https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA+ABADAAgwRgDoARASwEMBzAOwgGcAXUsOgbgFgAoLjAJhwDCACgjAAVjDAMcAM1IwANgBMAlFwDeXHNrwocAWSFrOOnJpOmdxGMACulQgEE6dGFAZC5ipTlJ0c1LYKCiocFtoAvlxR3JyMULZSOADKIuKS0l7KxuamGHqGxqa5ltrWdg7Oru6e8sq+/oHBoVo6MRFAA). **Recommendation**: The backing field is generated when `field` is used only in omitted calls to *conditional methods*. #### Answer `Conditional` code can have effects on non-conditional code, such as `Debug.Assert` changing nullability. It would be strange if `field` didn't have similar impacts. It is also unlikely to come up in most code, so we'll do the simple thing and accept the recommendation. ### Interface properties and auto-accessors Is a combination of manually- and auto-implemented accessors recognized for an `interface` property where the auto-implemented accessor refers to a synthesized backing field? For an instance property, an error will be reported that instance fields are not supported. ```csharp interface I { object P1 { get; set; } // ok: not an implementation object P2 { get => field; set { field = value; }} // error: instance field object P3 { get; set { } } // error: instance field static object P4 { get; set { } } // ok: equivalent to { get => field; set { } } } ``` **Recommendation**: Auto-accessors are recognized in `interface` properties, and the auto-accessors refer to a synthesized backing field. For an instance property, an error is reported that instance fields are not supported. #### Answer Standardizing around the instance field itself being the cause of the error is consistent with partial properties in classes, and we like that outcome. The recommendation is accepted. ================================================ FILE: proposals/csharp-14.0/first-class-span-types.md ================================================ # First-class Span Types [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary We introduce first-class support for `Span` and `ReadOnlySpan` in the language, including new implicit conversion types and consider them in more places, allowing more natural programming with these integral types. ## Motivation Since their introduction in C# 7.2, `Span` and `ReadOnlySpan` have worked their way into the language and base class library (BCL) in many key ways. This is great for developers, as their introduction improves performance without costing developer safety. However, the language has held these types at arm's length in a few key ways, which makes it hard to express the intent of APIs and leads to a significant amount of surface area duplication for new APIs. For example, the BCL has added a number of new [tensor primitive APIs](https://github.com/dotnet/runtime/issues/94553) in .NET 9, but these APIs are all offered on `ReadOnlySpan`. C# doesn't recognize the relationship between `ReadOnlySpan`, `Span`, and `T[]`, so even though there are user-defined conversions between these types, they cannot be used for extension method receivers, cannot compose with other user-defined conversions, and don't help with all generic type inference scenarios. Users would need to use explicit conversions or type arguments, which means that IDE tooling is not guiding users to use these APIs, since nothing will indicate to the IDE that it is valid to pass these types after conversion. In order to provide maximum usability for this style of API, the BCL will have to define an entire set of `Span` and `T[]` overloads, which is a lot of duplicate surface area to maintain for no real gain. This proposal seeks to address the problem by having the language more directly recognize these types and conversions. For example, the BCL can add only one overload of any `MemoryExtensions` helper like: ```cs int[] arr = [1, 2, 3]; Console.WriteLine( arr.StartsWith(1) // CS8773 in C# 13, permitted with this proposal ); public static class MemoryExtensions { public static bool StartsWith(this ReadOnlySpan span, T value) where T : IEquatable => span.Length != 0 && EqualityComparer.Default.Equals(span[0], value); } ``` Previously, Span and array overloads would be needed to make the extension method usable on Span/array-typed variables because user-defined conversions (which exist between Span/array/ReadOnlySpan) are not considered for extension receivers. ## Detailed Design The changes in this proposal will be tied to `LangVersion >= 14`. ### Span conversions We add a new type of implicit conversion to the list in [§10.2.1](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1021-general), an _implicit span conversion_. This conversion is a conversion from type and is defined as follows: ------ An implicit span conversion permits `array_types`, `System.Span`, `System.ReadOnlySpan`, and `string` to be converted between each other as follows: * From any single-dimensional `array_type` with element type `Ei` to `System.Span` * From any single-dimensional `array_type` with element type `Ei` to `System.ReadOnlySpan`, provided that `Ei` is covariance-convertible ([§18.2.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/interfaces.md#18233-variance-conversion)) to `Ui` * From `System.Span` to `System.ReadOnlySpan`, provided that `Ti` is covariance-convertible ([§18.2.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/interfaces.md#18233-variance-conversion)) to `Ui` * From `System.ReadOnlySpan` to `System.ReadOnlySpan`, provided that `Ti` is covariance-convertible ([§18.2.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/interfaces.md#18233-variance-conversion)) to `Ui` * From `string` to `System.ReadOnlySpan` ------ Any Span/ReadOnlySpan types are considered applicable for the conversion if they are `ref struct`s and they match by their fully-qualified name ([LDM 2024-06-24](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-24.md#first-class-spans)). We also add _implicit span conversion_ to the list of standard implicit conversions ([§10.4.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1042-standard-implicit-conversions)). This allows overload resolution to consider them when performing argument resolution, as in the previously-linked API proposal. The explicit span conversions are the following: - All *implicit span conversions*. - From an *array_type* with element type `Ti` to `System.Span` or `System.ReadOnlySpan` provided an explicit reference conversion exists from `Ti` to `Ui`. There is no standard explicit span conversion unlike other *standard explicit conversions* ([§10.4.3][standard-explicit-conversions]) which always exist given the opposite standard implicit conversion. #### User defined conversions [udc]: #user-defined-conversions User-defined conversions are not considered when converting between types for which an implicit or an explicit span conversion exists. The implicit span conversions are exempted from the rule that it is not possible to define a user-defined operator between types for which a non-user-defined conversion exists ([§10.5.2 Permitted user-defined conversions][permitted-udcs]). This is needed so the BCL can keep defining the existing Span conversion operators even when they switch to C# 14 (they are still needed for lower LangVersions and also because these operators are used in codegen of the new standard span conversions). But it can be viewed as an implementation detail (codegen and lower LangVersions are not part of the spec) and Roslyn violates this part of the spec anyway (this particular rule about user-defined conversions is not enforced). #### Extension receiver We also add _implicit span conversion_ to the list of acceptable implicit conversions on the first parameter of an extension method when determining applicability ([12.8.9.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12893-extension-method-invocations)) (change in bold): > An extension method `Cᵢ.Mₑ` is ***eligible*** if: > > - `Cᵢ` is a non-generic, non-nested class > - The name of `Mₑ` is *identifier* > - `Mₑ` is accessible and applicable when applied to the arguments as a static method as shown above > - An implicit identity, reference ~~or boxing~~ **, boxing, or span** conversion exists from *expr* to the type of the first parameter of `Mₑ`. > **Span conversion is not considered when overload resolution is performed for a method group conversion.** Note that implicit span conversion is not considered for extension receiver in method group conversions ([LDM 2024-07-15](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-07-15.md#first-class-spans-open-question)) which makes the following code continue working as opposed to resulting in a compile-time error `CS1113: Extension method 'E.M(Span, int)' defined on value type 'Span' cannot be used to create delegates`: ```cs using System; using System.Collections.Generic; Action a = new int[0].M; // binds to M(IEnumerable, int) static class E { public static void M(this Span s, T x) => Console.Write(1); public static void M(this IEnumerable e, T x) => Console.Write(2); } ``` As possible future work, we could consider removing this condition that span conversion is not considered for extension receiver in method group conversions and instead implement changes so the scenario like the one above would end up successfully calling the `Span` overload instead: - The compiler could emit a thunk that would take the array as the receiver and perform the span conversion inside (similarly to the user manually creating the delegate like `x => new int[0].M(x)`). - Value delegates if implemented could be able to take the `Span` as receiver directly. #### Variance The goal of the variance section in _implicit span conversion_ is to replicate some amount of covariance for `System.ReadOnlySpan`. Runtime changes would be required to fully implement variance through generics here (see ../csharp-13.0/ref-struct-interfaces.md for using `ref struct` types in generics), but we can allow a limited amount of covariance through use of a proposed .NET 9 API: https://github.com/dotnet/runtime/issues/96952. This will allow the language to treat `System.ReadOnlySpan` as if the `T` was declared as `out T` in some scenarios. We do not, however, plumb this variant conversion through _all_ variance scenarios, and do not add it to the definition of variance-convertible in [§18.2.3.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/interfaces.md#18233-variance-conversion). If in the future, we change the runtime to more deeply understand the variance here, we can take the minor breaking change to fully recognize it in the language. #### Patterns Note that when `ref struct`s are used as a type in any pattern, only identity conversions are allowed: ```cs class C where T : allows ref struct { void M1(T t) { if (t is T x) { } } // ok (T is T) void M2(R r) { if (r is R x) { } } // ok (R is R) void M3(T t) { if (t is R x) { } } // error (T is R) void M4(R r) { if (r is T x) { } } // error (R is T) } ref struct R { } ``` From the specification of *the is-type operator* ([§12.12.12.1][is-type-operator]): > The result of the operation `E is T` [...] is a Boolean value indicating whether `E` is non-null and can successfully be converted to type `T` > by a reference conversion, a boxing conversion, an unboxing conversion, a wrapping conversion, or an unwrapping conversion. > > [...] > > If `T` is a non-nullable value type, the result is `true` if `D` and `T` are the same type. This behavior does not change with this feature, hence it will not be possible to write patterns for `Span`/`ReadOnlySpan`, although similar patterns are possible for arrays (including variance): ```cs using System; M1(["0"]); // prints M1(["1"]); // prints void M1(T t) { if (t is object[] r) Console.WriteLine(r[0]); // ok } void M2(T t) where T : allows ref struct { if (t is ReadOnlySpan r) Console.WriteLine(r[0]); // error } ``` #### Code generation The conversions will always exist, regardless of whether any runtime helpers used to implement them are present ([LDM 2024-05-13](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-05-13.md#variant-conversion-existence)). If the helpers are not present, attempting to use the conversion will result in a compile-time error that a compiler-required member is missing. The compiler expects to use the following helpers or equivalents to implement the conversions: | Conversion | Helpers | |---|---| | array to Span | `static implicit operator Span(T[])` (defined in `Span`) | | array to ReadOnlySpan | `static implicit operator ReadOnlySpan(T[])` (defined in `ReadOnlySpan`) | | Span to ReadOnlySpan | `static implicit operator ReadOnlySpan(Span)` (defined in `Span`) and `static ReadOnlySpan.CastUp(ReadOnlySpan)` | | ReadOnlySpan to ReadOnlySpan | `static ReadOnlySpan.CastUp(ReadOnlySpan)` | | string to ReadOnlySpan | `static ReadOnlySpan MemoryExtensions.AsSpan(string)` | Note that `MemoryExtensions.AsSpan` is used instead of the equivalent implicit operator defined on `string`. This means the codegen is different between LangVersions (the implicit operator is used in C# 13; the static method `AsSpan` is used in C# 14). On the other hand, the conversion can be emitted on .NET Framework (the `AsSpan` method exists there whereas the `string` operator does not). The explicit array to (ReadOnly)Span conversion first converts explicitly from the source array to an array with the destination element type and then to (ReadOnly)Span via the same helper as an implicit conversion would use, i.e., the corresponding `op_Implicit(T[])`. #### Better conversion from expression [betterness-rule]: #better-conversion-from-expression *Better conversion from expression* ([§12.6.4.5][better-conversion-from-expression]) is updated to prefer implicit span conversions. This is based on [collection expressions overload resolution changes][ce-or]. > Given an implicit conversion `C₁` that converts from an expression `E` to a type `T₁`, and an implicit conversion `C₂` that converts from an expression `E` to a type `T₂`, `C₁` is a *better conversion* than `C₂` if one of the following holds: > > - `E` is a *collection expression*, and `C₁` is a [*better collection conversion from expression*][better-collection-conversion-from-expression] than `C₂` > - `E` is not a *collection expression* and one of the following holds: > - `E` exactly matches `T₁` and `E` does not exactly match `T₂` > - **`E` exactly matches neither of `T₁` and `T₂`, > and `C₁` is an implicit span conversion and `C₂` is not an implicit span conversion** > - `E` exactly matches both or neither of `T₁` and `T₂`, > **both or neither of `C₁` and `C₂` are an implicit span conversion**, > and `T₁` is a *better conversion target* than `T₂` > - `E` is a method group, `T₁` is compatible with the single best method from the method group for conversion `C₁`, and `T₂` is not compatible with the single best method from the method group for conversion `C₂` #### Better conversion target [betterness-target]: #better-conversion-target *Better conversion target* ([§12.6.4.7][better-conversion-target]) is updated to prefer `ReadOnlySpan` over `Span`. > Given two types `T₁` and `T₂`, `T₁` is a ***better conversion target*** than `T₂` if one of the following holds: > > - **`T₁` is `System.ReadOnlySpan`, `T₂` is `System.Span`, and an identity conversion from `E₁` to `E₂` exists** > - **`T₁` is `System.ReadOnlySpan`, `T₂` is `System.ReadOnlySpan`, and an implicit conversion from `T₁` to `T₂` exists and no implicit conversion from `T₂` to `T₁` exists** > - **At least one of `T₁` or `T₂` is not `System.ReadOnlySpan` and is not `System.Span`, and** an implicit conversion from `T₁` to `T₂` exists and no implicit conversion from `T₂` to `T₁` exists > - ... Design meetings: - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#preferring-readonlyspant-over-spant-conversions - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-09.md#first-class-span-open-questions #### Betterness remarks The *better conversion from expression* rule should ensure that whenever an overload becomes applicable due to the new span conversions, any potential ambiguity with another overload is avoided because the newly-applicable overload is preferred. Without this rule, the following code that successfully compiled in C# 13 would result in an ambiguity error in C# 14 because of the new standard implicit conversion from array to ReadOnlySpan applicable to an extension method receiver: ```cs using System; using System.Collections.Generic; var a = new int[] { 1, 2, 3 }; a.M(); static class E { public static void M(this IEnumerable x) { } public static void M(this ReadOnlySpan x) { } } ``` The rule also allows introducing new APIs that would previously result in ambiguities, for example: ```cs using System; using System.Collections.Generic; C.M(new int[] { 1, 2, 3 }); // would be ambiguous before static class C { public static void M(IEnumerable x) { } public static void M(ReadOnlySpan x) { } // can be added now } ``` > [!WARNING] > Because the betterness rule is defined for the span conversions which only exist in `LangVersion >= 14`, > API authors cannot add such new overloads if they want to keep supporting users on `LangVersion <= 13`. > For example, if .NET 9 BCL introduces such overloads, users that upgrade to `net9.0` TFM but stay on lower LangVersion > will get ambiguity errors for existing code. > See also [an open question](#unrestricted-betterness-rule) below. ### Type inference We update the type inferences section of the specification as follows (changes in **bold**). > #### 12.6.3.9 Exact inferences > > An *exact inference* *from* a type `U` *to* a type `V` is made as follows: > > - If `V` is one of the *unfixed* `Xᵢ` then `U` is added to the set of exact bounds for `Xᵢ`. > - Otherwise, sets `V₁...Vₑ` and `U₁...Uₑ` are determined by checking if any of the following cases apply: > - `V` is an array type `V₁[...]` and `U` is an array type `U₁[...]` of the same rank > - **`V` is a `Span` and `U` is an array type `U₁[]` or a `Span`** > - **`V` is a `ReadOnlySpan` and `U` is an array type `U₁[]` or a `Span` or `ReadOnlySpan`** > - `V` is the type `V₁?` and `U` is the type `U₁` > - `V` is a constructed type `C` and `U` is a constructed type `C` > If any of these cases apply then an *exact inference* is made from each `Uᵢ` to the corresponding `Vᵢ`. > - Otherwise, no inferences are made. > > #### 12.6.3.10 Lower-bound inferences > > A *lower-bound inference from* a type `U` *to* a type `V` is made as follows: > > - If `V` is one of the *unfixed* `Xᵢ` then `U` is added to the set of lower bounds for `Xᵢ`. > - Otherwise, if `V` is the type `V₁?` and `U` is the type `U₁?` then a lower bound inference is made from `U₁` to `V₁`. > - Otherwise, sets `U₁...Uₑ` and `V₁...Vₑ` are determined by checking if any of the following cases apply: > - `V` is an array type `V₁[...]` and `U` is an array type `U₁[...]`of the same rank > - **`V` is a `Span` and `U` is an array type `U₁[]` or a `Span`** > - **`V` is a `ReadOnlySpan` and `U` is an array type `U₁[]` or a `Span` or `ReadOnlySpan`** > - `V` is one of `IEnumerable`, `ICollection`, `IReadOnlyList>`, `IReadOnlyCollection` or `IList` and `U` is a single-dimensional array type `U₁[]` > - `V` is a constructed `class`, `struct`, `interface` or `delegate` type `C` and there is a unique type `C` such that `U` (or, if `U` is a type `parameter`, its effective base class or any member of its effective interface set) is identical to, `inherits` from (directly or indirectly), or implements (directly or indirectly) `C`. > - (The “uniqueness” restriction means that in the case interface `C{} class U: C, C{}`, then no inference is made when inferring from `U` to `C` because `U₁` could be `X` or `Y`.) > If any of these cases apply then an inference is made from each `Uᵢ` to the corresponding `Vᵢ` as follows: > - If `Uᵢ` is not known to be a reference type then an *exact inference* is made > - Otherwise, if `U` is an array type then ~~a *lower-bound inference* is made~~ **inference depends on the type of `V`**: > - **If `V` is a `Span`, then an *exact inference* is made** > - **If `V` is an array type or a `ReadOnlySpan`, then a *lower-bound inference* is made** > - **Otherwise, if `U` is a `Span` then inference depends on the type of `V`**: > - **If `V` is a `Span`, then an *exact inference* is made** > - **If `V` is a `ReadOnlySpan`, then a *lower-bound inference* is made** > - **Otherwise, if `U` is a `ReadOnlySpan` and `V` is a `ReadOnlySpan` a *lower-bound inference* is made**: > - Otherwise, if `V` is `C` then inference depends on the `i-th` type parameter of `C`: > - If it is covariant then a *lower-bound inference* is made. > - If it is contravariant then an *upper-bound inference* is made. > - If it is invariant then an *exact inference* is made. > - Otherwise, no inferences are made. There are no rules for *upper-bound inference* because it would not be possible to hit them. Type inference never starts as upper-bound, it would have to go through a lower-bound inference and a contravariant type parameter. Because of the rule "if `Uᵢ` is not known to be a reference type then an *exact inference* is made," the source type argument could not be `Span`/`ReadOnlySpan` (those cannot be reference types). However, the upper-bound span inference would only apply if the source type were a `Span`/`ReadOnlySpan`, since it would have rules like: > - **`U` is a `Span` and `V` is an array type `V₁[]` or a `Span`** > - **`U` is a `ReadOnlySpan` and `V` is an array type `V₁[]` or a `Span` or `ReadOnlySpan`** ### Breaking changes As any proposal that changes conversions of existing scenarios, this proposal does introduce some new breaking changes. Here's a few examples: #### Calling `Reverse` on an array Calling `x.Reverse()` where `x` is an instance of type `T[]` would previously bind to `IEnumerable Enumerable.Reverse(this IEnumerable)`, whereas now it binds to `void MemoryExtensions.Reverse(this Span)`. Unfortunately these APIs are incompatible (the latter does the reversal in-place and returns `void`). .NET 10 mitigates this by adding an array-specific overload `IEnumerable Reverse(this T[])`, see https://github.com/dotnet/runtime/issues/107723. ```cs void M(int[] a) { foreach (var x in a.Reverse()) { } // fine previously, an error now (`Reverse` returns `void`) foreach (var x in Enumerable.Reverse(a)) { } // workaround } ``` See also: - https://developercommunity.visualstudio.com/t/Extension-method-SystemLinqEnumerable/10790323 - https://developercommunity.visualstudio.com/t/Compilation-Error-When-Calling-Reverse/10818048 - https://developercommunity.visualstudio.com/t/Version-17131-has-an-obvious-defect-th/10858254 - https://developercommunity.visualstudio.com/t/Visual-Studio-2022-update-breaks-build-w/10856758 - https://github.com/dotnet/runtime/issues/111532 - https://developercommunity.visualstudio.com/t/Backward-compatibility-issue-:-IEnumerab/10896189#T-ND10896782 Design meeting: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#reverse #### Ambiguities The following examples previously failed type inference for the Span overload, but now type inference from array to Span succeeds, hence these are ambiguous. To work around this, users can use `.AsSpan()` or API authors can use `OverloadResolutionPriorityAttribute`. ```cs var x = new long[] { 1 }; Assert.Equal([2], x); // previously Assert.Equal(T[], T[]), now ambiguous with Assert.Equal(ReadOnlySpan, Span) Assert.Equal([2], x.AsSpan()); // workaround ``` ```cs var x = new int[] { 1, 2 }; var s = new ArraySegment(x, 1, 1); Assert.Equal(x, s); // previously Assert.Equal(T, T), now ambiguous with Assert.Equal(Span, Span) Assert.Equal(x.AsSpan(), s); // workaround ``` xUnit is adding more overloads to mitigate this: https://github.com/xunit/xunit/discussions/3021. Design meeting: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#new-ambiguities #### Covariant arrays Overloads taking `IEnumerable` worked on covariant arrays, but overloads taking `Span` (which we now prefer) don't, because the span conversion throws an `ArrayTypeMismatchException` for covariant arrays. Arguably, the `Span` overload should not exist, it should take `ReadOnlySpan` instead. To work around this, users can use `.AsEnumerable()`, or API authors can use `OverloadResolutionPriorityAttribute` or add `ReadOnlySpan` overload which is preferred due to [the betterness rule](#better-conversion-target). ```cs string[] s = new[] { "a" }; object[] o = s; C.R(o); // wrote 1 previously, now crashes in Span constructor with ArrayTypeMismatchException C.R(o.AsEnumerable()); // workaround static class C { public static void R(IEnumerable e) => Console.Write(1); public static void R(Span s) => Console.Write(2); // another workaround: public static void R(ReadOnlySpan s) => Console.Write(3); } ``` Design meeting: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#covariant-arrays #### Preferring ReadOnlySpan over Span The [betterness rule](#better-conversion-target) causes preference of ReadOnlySpan overloads over Span overloads to avoid `ArrayTypeMismatchException`s in [covariant array scenarios](#covariant-arrays). That can lead to compilation breaks in some scenarios, for example when the overloads differ by their return type: ```cs double[] x = new double[0]; Span y = MemoryMarshal.Cast(x); // previously worked, now a compilation error (returns ReadOnlySpan, not Span) Span z = MemoryMarshal.Cast(x.AsSpan()); // workaround static class MemoryMarshal { public static ReadOnlySpan Cast(ReadOnlySpan span) => default; public static Span Cast(Span span) => default; } ``` See https://github.com/dotnet/roslyn/issues/76443. #### Expression trees Overloads taking spans like `MemoryExtensions.Contains` are preferred over classic overloads like `Enumerable.Contains`, even inside expression trees - but ref structs are not supported by the interpreter engine: ```cs Expression> exp = (array, num) => array.Contains(num); exp.Compile(preferInterpretation: true); // fails at runtime in C# 14 Expression> exp2 = (array, num) => Enumerable.Contains(array, num); // workaround exp2.Compile(preferInterpretation: true); // ok ``` Similarly, translation engines like LINQ-to-SQL need to react to this if their tree visitors expect `Enumerable.Contains` because they will encounter `MemoryExtensions.Contains` instead. See also: - https://github.com/dotnet/runtime/issues/109757 - https://github.com/dotnet/docs/issues/43952 - https://github.com/dotnet/efcore/issues/35100 - https://github.com/dotnet/csharplang/discussions/8959 Design meetings: - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#conversions-in-expression-trees - https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-06.md#ignoring-ref-structs-in-expressions #### User-defined conversions through inheritance By adding _implicit span conversions_ to the list of standard implicit conversions, we can potentially change behavior when user-defined conversions are involved in a type hierarchy. This example shows that change, in comparison to an integer scenario that already behaves as the new C# 14 behavior will. ```cs Span span = []; var d = new Derived(); d.M(span); // Base today, Derived tomorrow int i = 1; d.M(i); // Derived today, demonstrates new behavior class Base { public void M(Span s) { Console.WriteLine("Base"); } public void M(int i) { Console.WriteLine("Base"); } } class Derived : Base { public static implicit operator Derived(ReadOnlySpan r) => new Derived(); public static implicit operator Derived(long l) => new Derived(); public void M(Derived s) { Console.WriteLine("Derived"); } } ``` See also: https://github.com/dotnet/roslyn/issues/78314 #### Extension method lookup By allowing _implicit span conversions_ in extension method lookup, we can potentially change what extension method is resolved by overload resolution. ```cs namespace N1 { using N2; public class C { public static void M() { Span span = new string[0]; span.Test(); // Prints N2 today, N1 tomorrow } } public static class N1Ext { public static void Test(this ReadOnlySpan span) { Console.WriteLine("N1"); } } } namespace N2 { public static class N2Ext { public static void Test(this Span span) { Console.WriteLine("N2"); } } } ``` ## Open questions ### Unrestricted betterness rule Should we make [the betterness rule][betterness-rule] unconditional on LangVersion? That would allow API authors to add new Span APIs where IEnumerable equivalents exist without breaking users on older LangVersions or other compilers or languages (e.g., VB). However, that would mean users could get different behavior after updating the toolset (without changing LangVersion or TargetFramework): - Compiler could choose different overloads (technically a breaking change, but hopefully those overloads would have equivalent behavior). - Other breaks could arise, unknown at this time. Note that [`OverloadResolutionPriorityAttribute`][overload-resolution-priority] cannot fully solve this because it's also ignored on older LangVersions. However, it should be possible to use it to avoid ambiguities from VB where the attribute should be recognized. ### Ignoring more user-defined conversions We defined a set of type pairs for which there are language-defined implicit and explicit span conversions. Whenever a language-defined span conversion exists from `T1` to `T2`, any user-defined conversion from `T1` to `T2` is [ignored][udc] (regardless of the span and user-defined conversion being implicit or explicit). Note that this includes all the conditions, so for example there is no span conversion from `Span` to `ReadOnlySpan` (there is a span conversion from `Span` to `ReadOnlySpan` but it must hold that `T : U`), hence a user-defined conversion would be considered between those types if it existed (that would have to be a specialized conversion like `Span` to `ReadOnlySpan` because conversion operators cannot have generic parameters). Should we ignore user-defined conversions also between other combinations of array/Span/ReadOnlySpan/string types where no corresponding language-defined span conversion exists? For example, if there is a user-defined conversion from `ReadOnlySpan` to `Span`, should we ignore it? Spec possibilities to consider: 1. > Whenever a span conversion exists from `T1` to `T2`, ignore any user-defined conversion from `T1` to `T2` *or from `T2` to `T1`*. 2. > User-defined conversions are not considered when converting between > - any single-dimensional `array_type` and `System.Span`/`System.ReadOnlySpan`, > - any combination of `System.Span`/`System.ReadOnlySpan`, > - `string` and `System.ReadOnlySpan`. 3. Like above but replacing the last bullet point with: > - `string` and `System.Span`/`System.ReadOnlySpan`. 4. Like above but replacing the last bullet point with: > - `string` and `System.Span`/`System.ReadOnlySpan`. Technically, the spec disallows some of these user-defined conversions to be even defined: it is not possible to define a user-defined operator between types for which a non-user-defined conversion exists ([§10.5.2][permitted-udcs]). But Roslyn intentionally violates this part of the spec. And some conversions like between `Span` and `string` are allowed anyway (no language-defined conversion between these types exist). Nevertheless, alternatively to just *ignoring* the conversions, we could *disallow* them to be defined at all and perhaps break out of the spec violation at least for these new span conversions, i.e., change Roslyn to actually report a compile-time error if these conversions are defined (likely except those already defined by the BCL). ## Alternatives Keep things as they are. [standard-explicit-conversions]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/conversions.md#1043-standard-explicit-conversions [permitted-udcs]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/conversions.md#1052-permitted-user-defined-conversions [better-conversion-from-expression]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/expressions.md#12645-better-conversion-from-expression [better-conversion-target]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/expressions.md#12647-better-conversion-target [is-type-operator]: https://github.com/dotnet/csharpstandard/blob/8c5e008e2fd6057e1bbe802a99f6ce93e5c29f64/standard/expressions.md#1212121-the-is-type-operator [ce-or]: ../csharp-12.0/collection-expressions.md#overload-resolution [overload-resolution-priority]: ../csharp-13.0/overload-resolution-priority.md [better-collection-conversion-from-expression]: ../csharp-13.0/collection-expressions-better-conversion.md#detailed-design ================================================ FILE: proposals/csharp-14.0/ignored-directives.md ================================================ # Ignored directives for file-based apps [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Add `#:` directive prefix to be used by tooling, but ignored by the language. ```cs #!/usr/bin/dotnet run #:sdk Microsoft.NET.Sdk.Web #:property TargetFramework=net11.0 #:property LangVersion=preview #:package System.CommandLine@2.0.0-* Console.WriteLine("Hello, World!"); ``` See the [SDK documentation for `dotnet run file.cs`](https://learn.microsoft.com/dotnet/csharp/language-reference/preprocessor-directives#file-based-apps) for more details on directives supported in file-based apps. ## Motivation We are adding `dotnet run file.cs` support [in .NET SDK][dotnet-run-file]. Real-world file-based programs need to reference NuGet packages. It would be also useful if it were possible to execute file-based programs directly like `./file.cs` when they have [the shebang directive][shebang] (`#!`). The language should ignore these directives, but compiler implementations and other tooling can recognize them. We already have similar directives in the language: - `#region` - `#pragma`: details are implementation specific - `#error version`: the `version` part is not in the spec, but Roslyn will report its version ## Detailed design Introduce new *ignored pre-processing directives* ([§6.5][directives]): ```antlr PP_Kind : ... // Existing directive kinds | PP_Ignored ; PP_Ignored : PP_IgnoredToken Input_Character* ; PP_IgnoredToken : '!' | ':' ; ``` These are parsed regardless of language version. ### Restrictions Ignored directives must occur before the first token ([§6.4][tokens]) in the compilation unit, just like `#define`/`#undef` directives. This improves readability (all package references and other configuration is in one place), tooling performance (no need to scan long files in full). Ignored directives must also occur before any `#if` directives because the tooling might not know the full set of conditional compilation symbols while parsing ignored directives. Furthermore, the compiler should report a warning if the `#!` directive is not placed at the first line and the first character in the file (not even a BOM marker can be in front of it), because otherwise shells won't recognize it. Compilers are also free to report errors if these directives are used in unsupported scenarios, e.g., Roslyn will report an error if these directives are present in a file compiled as part of "project-based programs" as opposed to "file-based programs" (and tooling will remove these directives when migrating file-based programs to project-based programs). If needed, we might consider not reporting that error for the `#!` directive, as it might invoke some other tool than `dotnet run`, so there is no need to restrict it to scripts and file-based programs only. Similarly, the compiler or SDK should still error/warn on unrecognized directives to "reserve" them for future use by the official .NET tooling. ## Alternatives ### Separate directives instead of one ignored prefix We could add each ignored directive to the language instead of introducing one ignored prefix. - For `dotnet run file.cs` specifically, we might want to add only as few directives as possible, and for anything more advanced, recommend users to eject to project-based programs instead (i.e., avoid having two ways to configure everything). - In any case, it might be good if new directives are discussed and approved by the language design team since they are part of the overall C# language experience. ```cs #!/usr/bin/dotnet run #sdk Microsoft.NET.Sdk.Web #property TargetFramework=net11.0 #property LangVersion=preview #package System.CommandLine@2.0.0-* #something // unrecognized directives would still be required by the language spec to be an error Console.WriteLine("Hello, World!"); ``` ### Other syntax forms Other syntax forms could be used except for shebang which shells recognize only by `#!`. #### Pragma We could reuse `#pragma` directive although that's originally meant for compiler options, not SDK (project-wide) options. ```cs #pragma package Microsoft.CodeAnalysis 4.14.0 ``` #### Single directive We could introduce only a single new directive for everything (packages, sdks, and possibly more in the future). For example, `#r` is already supported by C# scripting and other existing tooling. However, the naming of the directive is less clear. ```cs #r "nuget: Microsoft.CodeAnalysis, 4.14.0" ``` #### Sigil We could reserve another sigil prefix (e.g., `#!`/`#@`/`#$`) for any directives that should be ignored by the language. Note that `#!` would be interpreted as shebang by shells if it is at the first line of the file. ```cs #!/usr/bin/dotnet run ##package Microsoft.CodeAnalysis 4.14.0 ``` #### Comments We could use comments instead of introducing new directives. Reusing normal comments with `//#` might be confused with directives that have been commented out unless we use some other syntax like `//!` but both of these could be breaking. Documentation XML comments are more verbose and we would need to ensure they do not apply to the class below them when placed at the top of the file. ```cs //#package Microsoft.CodeAnalysis 4.14.0 ``` ```cs /// ``` [dotnet-run-file]: https://github.com/dotnet/sdk/pull/46915 [shebang]: https://en.wikipedia.org/wiki/Shebang_%28Unix%29 [tokens]: https://github.com/dotnet/csharpstandard/blob/f885375267570784d8d529d94893555494781abb/standard/lexical-structure.md#64-tokens [directives]: https://github.com/dotnet/csharpstandard/blob/f885375267570784d8d529d94893555494781abb/standard/lexical-structure.md#65-pre-processing-directives ================================================ FILE: proposals/csharp-14.0/null-conditional-assignment.md ================================================ # Null-conditional assignment [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Permits assignment to occur conditionally within a `a?.b` or `a?[b]` expression. ```cs using System; class C { public object obj; } void M(C? c) { c?.obj = new object(); } ``` ```cs using System; class C { public event Action E; } void M(C? c) { c?.E += () => { Console.WriteLine("handled event E"); }; } ``` ```cs void M(object[]? arr) { arr?[42] = new object(); } ``` ## Motivation [motivation]: #motivation A variety of motivating use cases can be found in the championed issue. Major motivations include: 1. Parity between properties and `Set()` methods. 2. Attaching event handlers in UI code. ## Detailed design [design]: #detailed-design - The right side of the assignment is only evaluated when the receiver of the conditional access is non-null. ```cs // M() is only executed if 'a' is non-null. // note: the value of 'a.b' doesn't affect whether things are evaluated here. a?.b = M(); ``` - All forms of compound assignment are allowed. ```cs a?.b -= M(); // ok a?.b += M(); // ok // etc. ``` - If the result of the expression is used, the expression's type must be known to be of a value type or a reference type. This is consistent with existing behaviors on conditional accesses. ```cs class C { public T? field; } void M1(C? c, T t) { (c?.field = t).ToString(); // error: 'T' cannot be made nullable. c?.field = t; // ok } ``` - Conditional access expressions are still not lvalues, and it's still not allowed to e.g. take a `ref` to them. ```cs M(ref a?.b); // error ``` - It is not allowed to ref-assign to a conditional access. The main reason for this is that the only way you would conditionally access a ref variable is a ref field, and ref structs are forbidden from being used in nullable value types. If a valid scenario for a conditional ref-assignment came up in the future, we could add support at that time. ```cs ref struct RS { public ref int b; } void M(RS a, ref int x) { a?.b = ref x; // error: Operator '?' can't be applied to operand of type 'RS'. } ``` - It's not possible to e.g. assign to conditional accesses through deconstruction assignment. We anticipate it will be rare for people to want to do this, and not a significant drawback to need to do it over multiple separate assignment expressions instead. ```cs (a?.b, c?.d) = (x, y); // error ``` - Increment/decrement operators are [not supported](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-28.md#increment-and-decrement-operators-in-null-conditional-access). ```cs a?.b++; // error --a?.b; // error ``` - This feature generally doesn't work when the receiver of the conditional access is a value type. This is because it will fall into one of the following two cases: ```cs void Case1(MyStruct a) => a?.b = c; // a?.b is not allowed when 'a' is of non-nullable value type void Case2(MyStruct? a) => a?.b = c; // `a.Value` is not a variable, so there's no reasonable meaning to define for the assignment ``` [readonly-setter-calls-on-non-variables.md](https://github.com/dotnet/csharplang/blob/main/proposals/readonly-setter-calls-on-non-variables.md) proposes relaxing this, in which case we could define a reasonable behavior for `a?.b = c`, when `a` is a `System.Nullable` and `b` is a property with a readonly setter. ### Specification The *null conditional assignment* grammar is defined as follows: ```antlr null_conditional_assignment : null_conditional_member_access assignment_operator expression : null_conditional_element_access assignment_operator expression ``` See [§11.7.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1177-null-conditional-member-access) and [§11.7.11](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11711-null-conditional-element-access) for reference. When the *null conditional assignment* appears in an expression-statement, its semantics are as follows: - `P?.A = B` is equivalent to `if (P is not null) P.A = B;`, except that `P` is only evaluated once. - `P?[A] = B` is equivalent to `if (P is not null) P[A] = B`, except that `P` is only evaluated once. Otherwise, its semantics are as follows: - `P?.A = B` is equivalent to `(P is null) ? (T?)null : (P.A = B)`, where `T` is the result type of `P.A = B`, except that `P` is only evaluated once. - `P?[A] = B` is equivalent to `(P is null) ? (T?)null : (P[A] = B)`, where `T` is the result type of `P[A] = B`, except that `P` is only evaluated once. ### Implementation The grammar in the standard currently doesn't correspond strongly to the syntax design used in the implementation. We expect that to remain the case after this feature is implemented. The syntax design in the [implementation](https://github.com/dotnet/roslyn/blob/09408ab8a29e03caddfb11f29328c05169ac7cde/src/Compilers/CSharp/Portable/Syntax/Syntax.xml#L583-L607) isn't expected to actually change--only the way it is used will change. For example: ```mermaid graph TD; subgraph ConditionalAccessExpression whole[a?.b = c] end subgraph subgraph WhenNotNull whole-->whenNotNull[".b = c"]; whenNotNull-->.b; whenNotNull-->eq[=]; whenNotNull-->c; end subgraph OperatorToken whole-->?; end subgraph Expression whole-->a; end end ``` ### Complex examples ```cs class C { ref int M() => /*...*/; } void M1(C? c) { c?.M() = 42; // equivalent to: if (c is not null) c.M() = 42; } int? M2(C? c) { return c?.M() = 42; // equivalent to: return c is null ? (int?)null : c.M() = 42; } ``` ```cs M(a?.b?.c = d); // equivalent to: M(a is null ? null : (a.b is null ? null : (a.b.c = d))); ``` ```cs return a?.b = c?.d = e?.f; // equivalent to: return a?.b = (c?.d = e?.f); // equivalent to: return a is null ? null : (a.b = c is null ? null : (c.d = e is null ? null : e.f)); } ``` ```cs a?.b ??= c; // equivalent to: if (a is not null) { if (a.b is null) { a.b = c; } } return a?.b ??= c; // equivalent to: return a is null ? null : a.b is null ? a.b = c : a.b; ``` ## Drawbacks [drawbacks]: #drawbacks The choice to keep the assignment within the conditional access introduces some additional work for the IDE, which has many code paths which need to work backwards from an assignment to identifying the thing being assigned. ## Alternatives [alternatives]: #alternatives We could instead make the `?.` syntactically a child of the `=`. This makes it so any handling of `=` expressions needs to become aware of the conditionality of the right side in the presence of `?.` on the left. It also makes it so the structure of the syntax doesn't correspond as strongly to the semantics. ## Unresolved questions [unresolved]: #unresolved-questions ## Design meetings * https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-27.md#null-conditional-assignment * https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-31.md#null-conditional-assignment * https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-26.md#null-conditional-assignment * https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-28.md#increment-and-decrement-operators-in-null-conditional-access ================================================ FILE: proposals/csharp-14.0/optional-and-named-parameters-in-expression-trees.md ================================================ # Support optional and named arguments in Expression trees [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: https://github.com/dotnet/csharplang/issues/9246 ## Summary [summary]: #summary Support optional and named arguments in method calls in `Expression` trees ## Motivation [motivation]: #motivation Errors are reported for calls in `Expression` trees when the call is missing an argument for an optional parameter, or when arguments are named. This results in unnecessary code and differences for expressions within `Expression` trees. And lack of support for optional arguments can lead to breaking changes when a new overload with an optional parameter is applicable at an existing call site. The compiler restrictions should be removed if not needed. For example, compiling the following with the .NET 10 preview SDK results in errors currently. ```csharp namespace System { public static class MemoryExtensions { public static bool Contains(this ReadOnlySpan span, T value, IEqualityComparer? comparer = null); } } Expression> e; e = (a, i) => a.Contains(i); // error CS0854: expression tree may not contain a call that uses optional arguments e = (a, i) => a.Contains(i, comparer: null); // error CS0853: expression tree may not contain a named argument specification ``` ## Detailed design [design]: #design Remove the error reporting for these cases in `Expression` trees, and allow the existing method call rewriting to handle optional and named arguments. ## Drawbacks [drawbacks]: #drawbacks ## Alternatives [alternatives]: #alternatives ## Unresolved questions [unresolved]: #unresolved-questions It's unclear why the restrictions were added originally. An initial investigation hasn't revealed an issue supporting these cases. ## Design meetings [meetings]: #meetings *Based on a suggestion from @roji to support optional parameters.* ================================================ FILE: proposals/csharp-14.0/partial-events-and-constructors.md ================================================ # Partial Events and Constructors [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Allow the `partial` modifier on events and constructors to separate declaration and implementation parts, similar to [partial methods][partial-methods-ext] and [partial properties/indexers][partial-props]. ```cs partial class C { partial C(int x, string y); partial event Action MyEvent; } partial class C { partial C(int x, string y) { } partial event Action MyEvent { add { } remove { } } } ``` ## Motivation C# already supports partial methods, properties, and indexers. Partial events and constructors are missing. Partial events would be useful for [weak event][weak-events] libraries, where the user could write definitions: ```cs partial class C { [WeakEvent] partial event Action MyEvent; void M() { RaiseMyEvent(0, "a"); } } ``` And a library-provided source generator would provide implementations: ```cs partial class C { private readonly WeakEvent _myEvent; partial event Action MyEvent { add { _myEvent.Add(value); } remove { _myEvent.Remove(value); } } protected void RaiseMyEvent(int x, string y) { _myEvent.Invoke(x, y); } } ``` Partial events and partial constructors would be also useful for generating interop code like in [Xamarin][xamarin] where the user could write partial constructor and event definitions: ```cs partial class AVAudioCompressedBuffer : AVAudioBuffer { [Export("initWithFormat:packetCapacity:")] public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity); [Export("create:")] public partial event EventHandler Created; } ``` And the source generator would generate the bindings (to Objective-C in this case): ```cs partial class AVAudioCompressedBuffer : AVAudioBuffer { [BindingImpl(BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)] public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity) : base(NSObjectFlag.Empty) { // Call Objective-C runtime: InitializeHandle( global::ObjCRuntime.NativeHandle_objc_msgSendSuper_NativeHandle_UInt32( this.SuperHandle, Selector.GetHandle("initWithFormat:packetCapacity:"), format.GetNonNullHandle(nameof(format)), packetCapacity), "initWithFormat:packetCapacity:"); } public partial event EventHandler Created { add { /* ... */ } remove { /* ... */ } } } ``` ## Detailed design ### General Event declaration syntax ([§15.8.1][event-syntax]) is extended to allow the `partial` modifier: ```diff event_declaration - : attributes? event_modifier* 'event' type variable_declarators ';' + : attributes? event_modifier* 'partial'? 'event' type variable_declarators ';' - | attributes? event_modifier* 'event' type member_name + | attributes? event_modifier* 'partial'? 'event' type member_name '{' event_accessor_declarations '}' ; ``` Instance constructor declaration syntax ([§15.11.1][ctor-syntax]) is extended to allow the `partial` modifier: ```diff constructor_declaration - : attributes? constructor_modifier* constructor_declarator constructor_body + : attributes? constructor_modifier* 'partial'? constructor_declarator constructor_body ; ``` Note that there is [a proposal][partial-ordering] to allow the `partial` modifier anywhere among modifiers, rather than only as the last one (also for method, property, and type declarations). An event declaration with the `partial` modifier is said to be a *partial event declaration* and it is associated with one or more *partial events* with the specified names (note that one event declaration without accessors can define multiple events). A constructor declaration with the `partial` modifier is said to be a *partial constructor declaration* and it is associated with a *partial constructor* with the specified signature. A partial event declaration is said to be an *implementing declaration* when it specifies the `event_accessor_declarations` or it has the `extern` modifier. Otherwise, it is a *defining declaration*. A partial constructor declaration is said to be a *defining declaration* when it has a semicolon body and it lacks the `extern` modifier. Otherwise, it is an *implementing declaration*. ```cs partial class C { // defining declarations partial C(); partial C(int x); partial event Action E, F; // implementing declarations partial C() { } partial C(int x) { } partial event Action E { add { } remove { } } partial event Action F { add { } remove { } } } ``` Only the defining declaration of a partial member participates in lookup and is considered at use sites and for emitting the metadata. (Except for documentation comments as detailed [below](#documentation-comments).) The implementing declaration signature is used for nullable analysis of the associated bodies. A partial event or constructor: - May only be declared as a member of a partial type. - Must have one defining and one implementing declaration. - Is not permitted to have the `abstract` modifier. - Cannot explicitly implement an interface member. A partial event is not field-like ([§15.8.2][event-field-like]), i.e.: - It does not have any backing storage or accessors generated by the compiler. - It can only be used in `+=` and `-=` operations, not as a value. A defining partial constructor declaration cannot have a constructor initializer (`: this()` or `: base()`; [§15.11.2][ctor-init]). ### Parsing break Allowing the `partial` modifier in more contexts is a breaking change: ```cs class C { partial F() => new partial(); // previously a method, now a constructor @partial F() => new partial(); // workaround to keep it a method } class partial { } ``` To simplify the language parser, the `partial` modifier is accepted at any method-like declaration (i.e., local functions and top-level script methods, as well), even though we do not specify the grammar changes explicitly above. ### Attributes The attributes of the resulting event or constructor are the combined attributes of the partial declarations in the corresponding positions. The combined attributes are concatenated in an unspecified order and duplicates are not removed. The `method` *attribute_target* ([§22.3][attribute-spec]) is ignored on partial event declarations. Accessor attributes are used only from accessor declarations (which can be present only under the implementing declaration). Note that `param` and `return` *attribute_target*s are already ignored on all event declarations. Caller-info attributes on the implementing declaration are ignored by the compiler as specified by the partial properties proposal in section [Caller-info attributes][partial-props-caller-info] (notice that it applies to all partial *members* which includes partial events and constructors). ### Signatures Both declarations of a partial member must have matching signatures [similar to partial properties][partial-props-signatures]: 1. Type and ref kind differences between partial declarations which are significant to the runtime result in a compile-time error. 2. Differences in tuple element names between partial declarations result in a compile-time error. 3. The declarations must have the same modifiers, though the modifiers may appear in a different order. - Exception: this does not apply to the `extern` modifier which may only appear on the implementing declaration. 4. All other syntactic differences in the signatures of partial declarations result in a compile-time warning, with the following exceptions: - Attribute lists do not need to match as described [above](#attributes). - Nullable context differences (such as oblivious vs. annotated) do not cause warnings. - Default parameter values do not need to match, but a warning is reported when the implementing constructor declaration has default parameter values (because those would be ignored since only the defining declaration participates in lookup). 5. A warning occurs when parameter names differ across defining and implementing constructor declarations. 6. Nullability differences which do not involve oblivious nullability result in warnings. ### Documentation comments It is permitted to include doc comments on both the defining and implementing declaration. Note that doc comments are not supported on event accessors. When doc comments are present on only one of the declarations of a partial member, those doc comments are used normally (surfaced through Roslyn APIs, emitted to the documentation XML file). When doc comments are present on both declarations of a partial member, all the doc comments on the defining declaration are dropped, and only the doc comments on the implementing declaration are used. When parameter names differ between declarations of a partial member, `paramref` elements use the parameter names from the declaration associated with the documentation comment in source code. For example, a `paramref` on a doc comment placed on an implementing declaration refers to the parameter symbols of the implementing declaration using their parameter names. This can be confusing, because the metadata signature will use parameter names from the defining declaration. It is recommended to ensure that parameter names match across the declarations of a partial member to avoid this confusion. ## Open questions ### Member kinds Do we want partial events, constructors, operators, fields? We propose the first two member kinds, but any other subset could be considered. Partial *primary* constructors could be also considered, e.g., permitting the user to have the same parameter list on multiple partial type declarations. ### Attribute locations Should we recognize the `[method:]` attribute target specifier for partial events (or just the defining declarations)? Then the resulting accessor attributes would be the concatenation of `method`-targeting attributes from both (or just the defining) declaration parts plus self-targeting and `method`-targeting attributes from the accessors of the implementing declaration. Combining attributes from different declaration kinds would be unprecedented and indeed the current implementation of attribute matching in Roslyn does not support that. We can also consider recognizing `[param:]` and `[return:]`, not just on partial events, but all field-like and extern events. For more details, see https://github.com/dotnet/roslyn/issues/77254. [partial-methods-ext]: ../csharp-9.0/extending-partial-methods.md [partial-props]: ../csharp-13.0/partial-properties.md [partial-props-caller-info]: ../csharp-13.0/partial-properties.md#caller-info-attributes [partial-props-signatures]: ../csharp-13.0/partial-properties.md#matching-signatures [partial-events-discussion]: https://github.com/dotnet/csharplang/discussions/8064 [partial-ordering]: https://github.com/dotnet/csharplang/issues/8966 [xamarin]: https://github.com/xamarin/xamarin-macios/issues/21308#issuecomment-2447535524 [weak-events]: https://learn.microsoft.com/dotNet/api/system.windows.weakeventmanager [event-syntax]: https://github.com/dotnet/csharpstandard/blob/f3c66477dc2d0a76d5c278e457a63c1695ddae08/standard/classes.md#1581-general [event-field-like]: https://github.com/dotnet/csharpstandard/blob/f3c66477dc2d0a76d5c278e457a63c1695ddae08/standard/classes.md#1582-field-like-events [ctor-syntax]: https://github.com/dotnet/csharpstandard/blob/f3c66477dc2d0a76d5c278e457a63c1695ddae08/standard/classes.md#1511-instance-constructors [ctor-init]: https://github.com/dotnet/csharpstandard/blob/f3c66477dc2d0a76d5c278e457a63c1695ddae08/standard/classes.md#15112-constructor-initializers [ctor-var-init]: https://github.com/dotnet/csharpstandard/blob/f3c66477dc2d0a76d5c278e457a63c1695ddae08/standard/classes.md#15113-instance-variable-initializers [attribute-spec]: https://github.com/dotnet/csharpstandard/blob/f3c66477dc2d0a76d5c278e457a63c1695ddae08/standard/attributes.md#223-attribute-specification ================================================ FILE: proposals/csharp-14.0/simple-lambda-parameters-with-modifiers.md ================================================ # Simple lambda parameters with modifiers [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Allow lambda parameters to be declared with modifiers without requiring their type names. For example, `(ref entry) =>` rather than `(ref FileSystemEntry entry) =>`. As another example, given this delegate: ```cs delegate bool TryParse(string text, out T result); ``` Allow this simplified parameter declaration: ```cs TryParse parse1 = (text, out result) => Int32.TryParse(text, out result); ``` Currently only this is valid: ```cs TryParse parse2 = (string text, out int result) => Int32.TryParse(text, out result); ``` ## Detailed design ### Grammar No changes. The [latest lambda grammar](../csharp-12.0/lambda-method-group-defaults.md#detailed-design) is: ```g4 lambda_expression : modifier* identifier '=>' (block | expression) | attribute_list* modifier* type? lambda_parameter_list '=>' (block | expression) ; lambda_parameter_list : lambda_parameters (',' parameter_array)? | parameter_array ; lambda_parameter : identifier | attribute_list* modifier* type? identifier default_argument? ; ``` This grammar already considers `modifiers* identifier` to be syntactically legal. ### Notes 1. This does not apply to a lambda without a parameter list. `ref x => x.ToString()` would not be legal. 1. A lambda parameter list still cannot mix `implicit_anonymous_function_parameter` and `explicit_anonymous_function_parameter` parameters. 1. `(ref readonly p) =>`, `(scoped ref p) =>`, and `(scoped ref readonly p) =>` will be allowed, just as they are with explicit parameters, due to: - [Low-level struct improvements](../csharp-11.0/low-level-struct-improvements.md#syntax) in C# 11 - [`ref readonly` parameters](../csharp-12.0/ref-readonly-parameters.md#parameter-declarations) in C# 12 1. The presence/absence of a type has no impact on whether a modifier is required or optional. In other words, if a modifier was required with a type present, it is still required with the type absent. Similarly, if a modifier was optional with a type present, it is optional with the type absent. ### Semantics https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/expressions#12192-anonymous-function-signatures is updated as follows: In a `lambda_parameter_list` all `lambda_parameter` elements must either have a `type` present or not have a `type` present. The former is an "explicitly typed parameter list", while the latter is an "implicitly typed parameter list". Parameters in an implicitly typed parameter list cannot have a `default_argument`. They can have an `attribute_list`. The following change is required to [anonymous function conversions](../csharp-12.0/lambda-method-group-defaults.md#detailed-design): [...] > If F has an explicitly **or implicitly typed parameter list**, each parameter in D has the same type and > modifiers as the corresponding parameter in F ignoring params modifiers and default values. ### Notes/Clarifications `scoped` and `params` are allowed as explicit modifiers in a lambda without an explicit type present. Semantics remain the same for both. Specifically, neither is part of the determination made [in](https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/expressions#12192-anonymous-function-signatures): > If an anonymous function has an explicit_anonymous_function_signature, then the set of compatible delegate > types and expression tree types is restricted to those that have the same parameter types and modifiers in > the same order. The only modifiers that restrict compatible delegate types are `ref`, `out`, `in` and `ref readonly`. For example, in an explicitly typed lambda, the following is currently ambiguous: ```c# delegate void D(scoped T t) where T : allows ref struct; delegate void E(T t) where T : allows ref struct; class C { void M() where T : allows ref struct { // error CS0121: The call is ambiguous between the following methods or properties: 'C.M1(D)' and 'C.M1(E)' // despite the presence of the `scoped` keyword. M1((scoped T t) => { }); } void M1(D d) where T : allows ref struct { } void M1(E d) where T : allows ref struct { } } ``` This remains the case when using implicitly typed lambdas: ```c# delegate void D(scoped T t) where T : allows ref struct; delegate void E(T t) where T : allows ref struct; class C { void M() where T : allows ref struct { // This will remain ambiguous. 'scoped' will not be used to restrict the set of delegates. M1((scoped t) => { }); } void M1(D d) where T : allows ref struct { } void M1(E d) where T : allows ref struct { } } ``` ### Open Questions 1. Should `scoped` *always* be a modifier in a lambda in C# 14? This matters for a case like: ```C# M((scoped s = default) => { }); ``` In this case, this is does *not* fall under the 'simple lambda parameter' spec, as a 'simple lambda' cannot contain a initializer (`= default`). As such, `scoped` here is treated as a `type` (like it was in C# 13). Do we want to maintain this? Or would it just be simpler to have a more blanket rule that `scoped` is always modifier, and thus would still be a modifier here on an invalid simple parameter? Recomendation: Make this a modifier. We already dissuade people from types that are all lowercase, *AND* we've made it illegal to make a type called `scoped` in C# as well. So this could only be some sort of case of referencing a type from another library. The workaround is trivial if you did somehow hit this. Just use `@scoped` to make this a type name instead of a modifier. 2. Allow `params` in a simple lambda parameter? Prior lambda work already added support for `params T[] values` in a lambda. This modifier is optional, and the lambda and the original delegate are allowed to have a mismatch on this modifier (though we warn if the delegate does not have the modifier and the lambda does). Should we continue allowing this with a simple lambda parameter. e.g. `M((params values) => { ... })` Recomendation: Yes. Allow this. The purpose of this spec is to allow just dropping the 'type' from a lambda parameter, while keeping the modifiers. This is just another case of that. This also just falls out from the impl (as did supporting attributes on these parameters), so it's more work to try to block this. Conclusion 1/15/2025: No. This will always be an error. There do not appear to be any useful cases for this and no one is asking for this. It is easier and safer to restrict this non-sensical case from the start. If relevant use cases are presented, we can reconsider. 3. Does 'scoped' influence overload resolution? For example, if there were multiple overloads of a delegate and one had a 'scoped' parameter, while the other did not, would the presense of 'scoped' influencce overload resolution. Recomendation: No. Do not have 'scoped' influence overload resolution. That is *already* how things work with normal *explicitly typed* lambdas. For example: ```c# delegate void D(scoped T t) where T : allows ref struct; delegate void E(T t) where T : allows ref struct; class C { void M() where T : allows ref struct { M1((scoped T t) => { }); } void M1(D d) where T : allows ref struct { } void M1(E d) where T : allows ref struct { } } ``` This is ambiguous today. Despite having 'scoped' on `D` and 'scoped' in the lambda parameter, we do not resolve this. We do not believe this should change with implicitly typed lambdas. Conclusion 1/15/2025: The above will hold true for 'simple lambas' as well. 'scoped' will not influence overload resolution (while `ref` and `out` will continue to do so). 5. Allow '(scoped x) => ...' lambdas? Recommendation: Yes. If we do not allow this then we can end up in scenarios where a user can write the full explicitly typed lambda, but not the implicitly typed version. For example: ```c# delegate ReadOnlySpan D(scoped ReadOnlySpan x); class C { static void Main(string[] args) { D d = (scoped ReadOnlySpan x) => throw null!; D d = (ReadOnlySpan x) => throw null!; // error! 'scoped' is required } } ``` Removing 'scoped' here would cause an error (the language requires the correspondance in this case between the lambda and delegate. As we want the user to be able to write lambdas like this, without specifying the type explicitly, that then means that `(scoped x) => ...` needs to be allowed. Conclusion 1/15/2025: We will allow `(scoped x) => ...` lambdas. ================================================ FILE: proposals/csharp-14.0/unbound-generic-types-in-nameof.md ================================================ # Unbound generic types in `nameof` [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Allows unbound generic types to be used with `nameof`, as in `nameof(List<>)` to obtain the string `"List"`, rather than having to specify an unused generic type argument in order to obtain the same string. ## Motivation This is a small feature that removes a common frustration: "why do I have to pick a generic type argument when the choice has no effect on the evaluation of the expression?" It's very odd to require something to be specified within an operand when it has no impact on the result. Notably, `typeof` does not suffer from this limitation. This is also not simply about brevity and simplicity. Once some arbitrary type argument has been chosen in a `nameof` expression, such as `object?`, changing a constraint on a type parameter can break uses of `nameof` unnecessarily. Insult becomes added to injury in this scenario. Satisfying the type parameter can sometimes require declaring a dummy class to implement an interface which is constraining the type parameter. Now there's unused metadata and a strange name invented, all for the purpose of adding a type argument to the `nameof` expression, a type argument which `nameof` will ultimately ignore even though it requires it. In some rarer cases, with a generic class constraint, it's not even _possible_ to use `nameof` because it's not possible to inherit from a base class which is used as a generic constraint, due to the base class having an internal constructor or internal abstract member. A simple tweak allows this to fall out in the language, with minimal implementation complexity in the compiler. ## Description Unbound type names become available for use with `nameof`: - `nameof(A<>)` evaluates to `"A"` - `nameof(Dictionary<,>)` evaluates to `"Dictionary"` Additionally, chains of members will be able to be accessed on unbound types, just like on bound types: ```cs class A { public List B { get; } } ``` - `nameof(A<>.B)` evaluates to `"B"` - `nameof(A<>.B.Count)` evaluates to `"Count"` Even members of generic type parameters can be accessed, consistent with how `nameof` already works when the type is not unbound. Since the type is unbound, there is no type information on these members beyond what `System.Object` or any additional generic constraints provide. ```cs class A where TCollection : IReadOnlyCollection { public TCollection B { get; } } ``` - `nameof(A<,>.B)` evaluates to `"B"` - `nameof(A<,>.B.Count)` evaluates to `"Count"`. ### Not supported 1. Support is not included for nesting an unbound type as a type argument to another generic type, such as `A>` or `A>.C.D`. Even though this could be logically implemented, such expressions have no precedent in the language, and there is not sufficient motivation to introduce it: - `A>` provides no benefits over `A<>`, and `A>.C` provides no benefits over `A<>.C`. - If `C` returns the `T` of `A`, `A>.C.D` can be written more directly as `B<>.D`. If it returns some other type, then `A>.C.D` provides no benefits over `A<>.C.D`. - `typeof()` has the same restrictions. 2. Support is not included for partially unbound types, such as `Dictionary`. Similarly, such expressions have no precedent in the language, and there is not sufficient motivation. That form provides no benefits over `Dictionary<,>`, and accessing members of `T`-returning members can be written more directly without wrapping in a partially unbound type. ## Detailed design ### Grammar changes ```diff nameof_expression : 'nameof' '(' named_entity ')' ; named_entity - : named_entity_target ('.' identifier type_argument_list?)* + : named_entity_target ('.' identifier (type_argument_list | generic_dimension_specifier)?)* ; named_entity_target : simple_name | 'this' | 'base' | predefined_type | qualified_alias_member ; simple_name - : identifier type_argument_list? + : identifier (type_argument_list | generic_dimension_specifier)? ; generic_dimension_specifier : '<' ','* '>' ``` This now allows names like `X<>` or `X<,>` to be considered simple names, where they were previously only supported as `unbound_type_name`s within a `typeof` expression. ### Semantic changes It is an error to use `generic_dimension_specifier` outside of a `typeof` or `nameof` expression. Within either of those expressions it is an error to use `generic_dimension_specifier` within a `type_argument_list`, `array_type`, `pointer_type` or `nullable_type`. In other words the following are all illegal: ```c# // Illegal, not inside `nameof` or `typeof` var v = SomeType<>.StaticMember; ``` ```c# // All illegal var v = typeof(List<>[]); var v = typeof(List<>*); var v = typeof((List<> a, int b)); ``` Note: The above rules effectively serve to make it so that `generic_dimension_specifier` cannot show up within another type. However, at the top level, where the specifier is allowed, it is fine to mix and match with normal `type_argument_list`s. For example, the following are legal: ```c# var v = (nameof(X<>.Y)); var v = (nameof(X.Y<>)); ``` Member lookup on an unbound type expression within a `nameof` will be performed the same way as for a `this` expression within that type declaration (modulo performing accessibility checks at the callsite). In other words lookup off of `List<>` in `nameof(List<>...)` works the same as lookup off of `this` within the `class List` type itself. No change is needed for the cases listed in the [Not supported](#not-supported) section. They already provide the same errors for `nameof` expressions as they do for `typeof`. No change is needed when the syntax `nameof` binds to a method named 'nameof' rather than being the contextual keyword `nameof`. Passing any type expression to a method results in "CS0119: '...' is a type, which is not valid in the given context." This already covers unbound generic type expressions. ================================================ FILE: proposals/csharp-14.0/user-defined-compound-assignment.md ================================================ # User Defined Compound Assignment Operators [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: https://github.com/dotnet/csharplang/issues/9101 ## Summary [summary]: #summary Allow user types to customize behavior of compound assignment operators in a way that the target of the assignment is modified in-place. ## Motivation [motivation]: #motivation C# provides support for the developer overloading operator implementations for user-defined type. Additionally, it provides support for "compound assignment operators" which allow the user to write code similarly to `x += y` rather than `x = x + y`. However, the language does not currently allow for the developer to overload these compound assignment operators and while the default behavior does the right thing, especially as it pertains to immutable value types, it is not always "optimal". Given the following example ``` C# class C1 { static void Main() { var c1 = new C1(); c1 += 1; System.Console.Write(c1); } public static C1 operator+(C1 x, int y) => new C1(); } ``` with the current language rules, compound assignment operator `c1 += 1` invokes user defined `+` operator and then assigns its return value to the local variable `c1`. Note that operator implementation must allocate and return a new instance of `C1`, while, from the consumer's perspective, an in-place change to the original instance of `C1` instead would work as good (it is not used after the assignment), with an additional benefit of avoiding an extra allocation. When a program utilizes a compound assignment operation, the most common effect is that the original value is "lost" and is no longer available to the program. With types which have large data (such as BigInteger, Tensors, etc.) the cost of producing a net new destination, iterating, and copying the memory tends to be fairly expensive. An in-place mutation would allow skipping this expense in many cases, which can provide significant improvements to such scenarios. Therefore, it may be beneficial for C# to allow user types to customize behavior of compound assignment operators and optimize scenarios that would otherwise need to allocate and copy. ## Detailed design [design]: #detailed-design ### Syntax Grammar at https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15101-general is adjusted as follows. Operators are declared using *operator_declaration*s: ```diff operator_declaration : attributes? operator_modifier+ operator_declarator operator_body ; operator_modifier : 'public' | 'static' | 'extern' | unsafe_modifier // unsafe code support | 'abstract' | 'virtual' | 'sealed' + | 'override' + | 'new' + | 'readonly' ; operator_declarator : unary_operator_declarator | binary_operator_declarator | conversion_operator_declarator + | increment_operator_declarator + | compound_assignment_operator_declarator ; unary_operator_declarator : type 'operator' overloadable_unary_operator '(' fixed_parameter ')' ; logical_negation_operator : '!' ; overloadable_unary_operator - : '+' | 'checked'? '-' | logical_negation_operator | '~' | 'checked'? '++' | 'checked'? '--' | 'true' | 'false' + : '+' | 'checked'? '-' | logical_negation_operator | '~' | 'true' | 'false' ; binary_operator_declarator : type 'operator' overloadable_binary_operator '(' fixed_parameter ',' fixed_parameter ')' ; overloadable_binary_operator : 'checked'? '+' | 'checked'? '-' | 'checked'? '*' | 'checked'? '/' | '%' | '&' | '|' | '^' | '<<' | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<=' ; conversion_operator_declarator : 'implicit' 'operator' type '(' fixed_parameter ')' | 'explicit' 'operator' type '(' fixed_parameter ')' ; +increment_operator_declarator + : type 'operator' overloadable_increment_operator '(' fixed_parameter ')' + | 'void' 'operator' overloadable_increment_operator '(' ')' + ; +overloadable_increment_operator + : 'checked'? '++' | 'checked'? '--' + ; +compound_assignment_operator_declarator + : 'void' 'operator' overloadable_compound_assignment_operator + '(' fixed_parameter ')' + ; +overloadable_compound_assignment_operator + : 'checked'? '+=' | 'checked'? '-=' | 'checked'? '*=' | 'checked'? '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' + | right_shift_assignment + | unsigned_right_shift_assignment + ; operator_body : block | '=>' expression ';' | ';' ; ``` There are five categories of overloadable operators: [unary operators](#unary-operators), [binary operators](#binary-operators), [conversion operators](#conversion-operators), [increment operators](#increment-operators), [compound assignment operators](#compound-assignment-operators). >The following rules apply to all operator declarations: > >- An operator declaration shall include ~~both~~ a `public` ~~and a `static`~~ modifier. Compound assignment and instance increment operators can hide operators declared in a base class. Therefore, the following paragraph is no longer accurate and should either be adjusted accordingly, or it can be removed: >Because operator declarations always require the class or struct in which the operator is declared to participate in the signature of the operator, it is not possible for an operator declared in a derived class to hide an operator declared in a base class. Thus, the `new` modifier is never required, and therefore never permitted, in an operator declaration. ### Unary operators [unary-operators]: #unary-operators See https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15102-unary-operators. An operator declaration shall include a `static` modifier and shall not include an `override` modifier. The following bullet point is removed: >- A unary `++` or `--` operator shall take a single parameter of type `T` or `T?` and shall return that same type or a type derived from it. The following paragraph is adjusted to no longer mention `++` and `--` operator tokens: > The signature of a unary operator consists of the operator token (`+`, `-`, `!`, `~`, `++`, `--`, `true`, or `false`) and the type of the single parameter. The return type is not part of a unary operator’s signature, nor is the name of the parameter. An example in the section should be adjusted to not use a user defined increment operator. ### Binary operators [binary-operators]: #binary-operators See https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15103-binary-operators. An operator declaration shall include a `static` modifier and shall not include an `override` modifier. ### Conversion operators [conversion-operators]: #conversion-operators See https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#15104-conversion-operators. An operator declaration shall include a `static` modifier and shall not include an `override` modifier. ### Increment operators [increment-operators]: #increment-operators The following rules apply to static increment operator declarations, where `T` denotes the instance type of the class or struct that contains the operator declaration: - An operator declaration shall include a `static` modifier and shall not include an `override` modifier. - An operator shall take a single parameter of type `T` or `T?` and shall return that same type or a type derived from it. The signature of a static increment operator consists of the operator tokens ('checked'? `++`, 'checked'? `--`) and the type of the single parameter. The return type is not part of a static increment operator’s signature, nor is the name of the parameter. Static increment operators are very similar to [unary operators](#unary-operators). The following rules apply to instance increment operator declarations: - An operator declaration shall not include a `static` modifier. - An operator shall take no parameters. - An operator shall have `void` return type. Effectively, an instance increment operator is a void returning instance method that has no parameters and has a special name in metadata. The signature of an instance increment operator consists of the operator tokens ('checked'? '++' | 'checked'? '--'). A `checked operator` declaration requires a pair-wise declaration of a `regular operator`. A compile-time error occurs otherwise. See also ../csharp-11.0/checked-user-defined-operators.md#semantics. The purpose of the method is to adjust the value of the instance to result of the requested increment operation, whatever that means in context of the declaring type. Example: ``` C# class C1 { public int Value; public void operator ++() { Value++; } } ``` An instance increment operator can override an operator with the same signature declared in a base class, an `override` modifier can be used for this purpose. The following "reserved" special names should be added to ECMA-335 to support instance versions of increment/decrement operators: | Name | Operator | | -----| -------- | |op_DecrementAssignment| `--` | |op_IncrementAssignment| `++` | |op_CheckedDecrementAssignment| checked `--` | |op_CheckedIncrementAssignment| checked `++` | ### Compound assignment operators [compound-assignment-operators]: #compound-assignment-operators The following rules apply to compound assignment operator declarations: - An operator declaration shall not include a `static` modifier. - An operator shall take one parameter. - An operator shall have `void` return type. Effectively, a compound assignment operator is a void returning instance method that takes one parameter and has a special name in metadata. The signature of a compound assignment operator consists of the operator tokens ('checked'? '+=', 'checked'? '-=', 'checked'? '*=', 'checked'? '/=', '%=', '&=', '|=', '^=', '<<=', right_shift_assignment, unsigned_right_shift_assignment) and the type of the single parameter. The name of the parameter is not part of a compound assignment operator’s signature. A `checked operator` declaration requires a pair-wise declaration of a `regular operator`. A compile-time error occurs otherwise. See also ../csharp-11.0/checked-user-defined-operators.md#semantics. The purpose of the method is to adjust the value of the instance to result of ``` parameter```. Example: ``` C# class C1 { public int Value; public void operator +=(int x) { Value+=x; } } ``` A compound assignment operator can override an operator with the same signature declared in a base class, an `override` modifier can be used for this purpose. ECMA-335 already "reserved" the following special names for user defined increment operators: | Name | Operator | | -----| -------- | |op_AdditionAssignment|'+=' | |op_SubtractionAssignment|'-=' | |op_MultiplicationAssignment|'*=' | |op_DivisionAssignment|'/=' | |op_ModulusAssignment|'%=' | |op_BitwiseAndAssignment|'&=' | |op_BitwiseOrAssignment|'|=' | |op_ExclusiveOrAssignment|'^=' | |op_LeftShiftAssignment|'<<='| |op_RightShiftAssignment| right_shift_assignment| |op_UnsignedRightShiftAssignment|unsigned_right_shift_assignment| However, it states that CLS compliance requires the operator methods to be non-void static methods with two parameters, i.e. matches what C# binary operators are. We should consider relaxing the CLS compliance requirements to allow the operators to be void returning instance methods with a single parameter. The following names should be added to support checked versions of the operators: | Name | Operator | | -----| -------- | |op_CheckedAdditionAssignment| checked '+=' | |op_CheckedSubtractionAssignment| checked '-=' | |op_CheckedMultiplicationAssignment| checked '*=' | |op_CheckedDivisionAssignment| checked '/=' | ### Prefix increment and decrement operators See https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators If `x` in `«op» x` is classified as a variable and a new language version is targeted, then the priority is given to [instance increment operators](#increment-operators) as follows. First, an attempt is made to process the operation by applying [instance increment operator overload resolution](#instance-increment-operator-overload-resolution). If the process produces no result and no error, then the operation is processed by applying unary operator overload resolution as https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1296-prefix-increment-and-decrement-operators currently specifies. Otherwise, an operation `«op»x` is evaluated as follows. If type of `x` is known to be a reference type, the `x` is evaluated to get an instance `x₀`, the operator method is invoked on that instance, and `x₀` is returned as result of the operation. If `x₀` is `null`, the operator method invocation will throw a NullReferenceException. For example: ``` C# var a = ++(new C()); // error: not a variable var b = ++a; // var temp = a; temp.op_Increment(); b = temp; ++b; // b.op_Increment(); var d = ++C.P1; // error: setter is missing ++C.P1; // error: setter is missing var e = ++C.P2; // var temp = C.op_Increment(C.get_P2()); C.set_P2(temp); e = temp; ++C.P2; // var temp = C.op_Increment(C.get_P2()); C.set_P2(temp); class C { public static C P1 { get; } = new C(); public static C P2 { get; set; } = new C(); public static C operator ++(C x) => ...; public void operator ++() => ...; } ``` If type of `x` is not known to be a reference type: - If result of increment is used, the `x` is evaluated to get an instance `x₀`, the operator method is invoked on that instance, `x₀` is assigned to `x` and `x₀` is returned as result of the compound assignment. - Otherwise, the operator method is invoked on `x`. Note that side effects in `x` are evaluated only once in the process. For example: ``` C# var a = ++(new S()); // error: not a variable var b = ++S.P2; // var temp = S.op_Increment(S.get_P2()); S.set_P2(temp); b = temp; ++S.P2; // var temp = S.op_Increment(S.get_P2()); S.set_P2(temp); ++b; // b.op_Increment(); var d = ++S.P1; // error: set is missing ++S.P1; // error: set is missing var e = ++b; // var temp = b; temp.op_Increment(); e = (b = temp); struct S { public static S P1 { get; } = new S(); public static S P2 { get; set; } = new S(); public static S operator ++(S x) => ...; public void operator ++() => ...; } ``` ### Postfix increment and decrement operators See https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators If result of the operation is used or `x` in `x «op»` is not classified as a variable or an old language version is targeted, the operation is processed by applying unary operator overload resolution as https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators currently specifies. The reason why we are not even trying instance increment operators when result is used, is the fact that, if we are dealing with a reference type, it is not possible to produce value of `x` before the operation if it is mutated in-place. If we are dealing with a value type, we will have to make copies anyway, etc. Otherwise, the priority is given to [instance increment operators](#increment-operators) as follows. First, an attempt is made to process the operation by applying [instance increment operator overload resolution](#instance-increment-operator-overload-resolution). If the process produces no result and no error, then the operation is processed by applying unary operator overload resolution as https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12816-postfix-increment-and-decrement-operators currently specifies. Otherwise, an operation `x«op»` is evaluated as follows. If type of `x` is known to be a reference type, the operator method is invoked on `x`. If `x` is `null`, the operator method invocation will throw a NullReferenceException. For example: ``` C# var a = (new C())++; // error: not a variable var b = new C(); var c = b++; // var temp = b; b = C.op_Increment(temp); c = temp; b++; // b.op_Increment(); var d = C.P1++; // error: missing setter C.P1++; // error: missing setter var e = C.P2++; // var temp = C.get_P2(); C.set_P2(C.op_Increment(temp)); e = temp; C.P2++; // var temp = C.get_P2(); C.set_P2(C.op_Increment(temp)); class C { public static C P1 { get; } = new C(); public static C P2 { get; set; } = new C(); public static C operator ++(C x) => ...; public void operator ++() => ...; } ``` If type of `x` is not known to be a reference type, the operator method is invoked on `x`. For example: ``` C# var a = (new S())++; // error: not a variable var b = S.P2++; // var temp = S.get_P2(); S.set_P2(S.op_Increment(temp)); b = temp; S.P2++; // var temp = S.get_P2(); S.set_P2(S.op_Increment(temp)); b++; // b.op_Increment(); var d = S.P1++; // error: set is missing S.P1++; // error: missing setter var e = b++; // var temp = b; b = S.op_Increment(temp); e = temp; struct S { public static S P1 { get; } = new S(); public static S P2 { get; set; } = new S(); public static S operator ++(S x) => ...; public void operator ++() => ...; } ``` ### Instance increment operator overload resolution [instance-increment-operator-overload-resolution]: #instance-increment-operator-overload-resolution An operation of the form `«op» x` or `x «op»`, where «op» is an overloadable instance increment operator, and `x` is an expression of type `X`, is processed as follows: - The set of candidate user-defined operators provided by `X` for the operation `operator «op»(x)` is determined using the rules of [candidate instance increment operators](#candidate-instance-increment-operators). - If the set of candidate user-defined operators is not empty, then this becomes the set of candidate operators for the operation. Otherwise, the overload resolution yields no result. - The [overload resolution rules](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1264-overload-resolution) are applied to the set of candidate operators to select the best operator, and this operator becomes the result of the overload resolution process. If overload resolution fails to select a single best operator, a binding-time error occurs. ### Candidate instance increment operators [candidate-instance-increment-operators]: #candidate-instance-increment-operators Given a type `T` and an operation `«op»`, where `«op»` is an overloadable instance increment operator, the set of candidate user-defined operators provided by `T` is determined as follows: - In `unchecked` evaluation context, it is a group of operators that would be produced by [Member lookup](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1251-general) process when only instance `operator «op»()` operators were considered matching the target name `N`. - In `checked` evaluation context, it is a group of operators that would be produced by [Member lookup](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1251-general) process when only instance `operator «op»()` and instance `operator checked «op»()` operators were considered matching the target name `N`. The `operator «op»()` operators that have pair-wise matching `operator checked «op»()` declarations are excluded from the group. ### Compound assignment See https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment The paragraph at the beginning that deals with `dynamic` is still applicable as is. Otherwise, if `x` in `x «op»= y` is classified as a variable and a new language version is targeted, then the priority is given to [compound assignment operators](#compound-assignment-operators) as follows. First, an attempt is made to process an operation of the form `x «op»= y` by applying [compound assignment operator overload resolution](#compound-assignment-operator-overload-resolution). If the process produces no result and no error, then the operation is processed by applying binary operator overload resolution as https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment currently specifies. Otherwise, the operation is evaluated as follows. If type of `x` is known to be a reference type, the `x` is evaluated to get an instance `x₀`, the operator method is invoked on that instance with `y` as the argument, and `x₀` is returned as result of the compound assignment. If `x₀` is `null`, the operator method invocation will throw a NullReferenceException. For example: ``` C# var a = (new C())+=10; // error: not a variable var b = a += 100; // var temp = a; temp.op_AdditionAssignment(100); b = temp; var c = b + 1000; // c = C.op_Addition(b, 1000) c += 5; // c.op_AdditionAssignment(5); var d = C.P1 += 11; // error: setter is missing var e = C.P2 += 12; // var temp = C.op_Addition(C.get_P2(), 12); C.set_P2(temp); e = temp; C.P2 += 13; // var temp = C.op_Addition(C.get_P2(), 13); C.set_P2(temp); class C { public static C P1 { get; } = new C(); public static C P2 { get; set; } = new C(); // op_Addition public static C operator +(C x, int y) => ...; // op_AdditionAssignment public void operator +=(int y) => ...; } ``` If type of `x` is not known to be a reference type: - If result of compound assignment is used, the `x` is evaluated to get an instance `x₀`, the operator method is invoked on that instance with `y` as the argument, `x₀` is assigned to `x` and `x₀` is returned as result of the compound assignment. - Otherwise, the operator method is invoked on `x` with `y` as the argument. Note that side effects in `x` are evaluated only once in the process. For example: ``` C# var a = (new S())+=10; // error: not a variable var b = S.P2 += 100; // var temp = S.op_Addition(S.get_P2(), 100); S.set_P2(temp); b = temp; S.P2 += 100; // var temp = S.op_Addition(S.get_P2(), 100); S.set_P2(temp); var c = b + 1000; // c = S.op_Addition(b, 1000) c += 5; // c.op_AdditionAssignment(5); var d = S.P1 += 11; // error: setter is missing var e = c += 12; // var temp = c; temp.op_AdditionAssignment(12); e = (c = temp); struct S { public static S P1 { get; } = new S(); public static S P2 { get; set; } = new S(); // op_Addition public static S operator +(S x, int y) => ...; // op_AdditionAssignment public void operator +=(int y) => ...; } ``` ### Compound assignment operator overload resolution [compound-assignment-operator-overload-resolution]: #compound-assignment-operator-overload-resolution An operation of the form `x «op»= y`, where `«op»=` is an overloadable compound assignment operator, `x` is an expression of type `X` is processed as follows: - The set of candidate user-defined operators provided by `X` for the operation `operator «op»=(y)` is determined using the rules of [candidate compound assignment operators](#candidate-compound-assignment-operators). - If at least one candidate user-defined operator in the set is applicable to the argument list `(y)`, then this becomes the set of candidate operators for the operation. Otherwise, the overload resolution yields no result. - The [overload resolution rules](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1264-overload-resolution) are applied to the set of candidate operators to select the best operator with respect to the argument list `(y)`, and this operator becomes the result of the overload resolution process. If overload resolution fails to select a single best operator, a binding-time error occurs. ### Candidate compound assignment operators [candidate-compound-assignment-operators]: #candidate-compound-assignment-operators Given a type `T` and an operation `«op»=`, where `«op»=` is an overloadable compound assignment operator, the set of candidate user-defined operators provided by `T` is determined as follows: - In `unchecked` evaluation context, it is a group of operators that would be produced by [Member lookup](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1251-general) process when only instance `operator «op»=(Y)` operators were considered matching the target name `N`. - In `checked` evaluation context, it is a group of operators that would be produced by [Member lookup](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1251-general) process when only instance `operator «op»=(Y)` and instance `operator checked «op»=(Y)` operators were considered matching the target name `N`. The `operator «op»=(Y)` operators that have pair-wise matching `operator checked «op»=(Y)` declarations are excluded from the group. ## Open questions [open]: #open-questions ### [Resolved] Should `readonly` modifier be allowed in structures? It feels like there would be no benefit in allowing to mark a method with `readonly` when the whole purpose of the method is to modify the instance. [Conclusion:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-02.md#conclusion) We will allow `readonly` modifiers, but we will not relax the target requirements at this time. ### [Resolved] Should shadowing be allowed? If a derived class declares a 'compound assignment'/'instance increment' operator with the same signature as one in base, should we require an `override` modifier? [Conclusion:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-02.md#conclusion-1) Shadowing will be allowed with the same rules as methods. ### [Resolved] Should we have any consistency enforcement between declared `+=` and `+` operators? During [LDM-2025-02-12](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-02-12.md#user-defined-instance-based-operators) a concern was raised about authors accidentally pushing their users into odd scenarios where a `+=` may work, but `+` won't (or vice versa) because one form declares extra operators than the other. [Conclusion:](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-02.md#conclusion-2) Checks will not be done on consistency between different forms of operators. ## Alternatives [alternatives]: #alternatives ### Keep using static methods We could consider using static operator methods where the instance to be mutated is passed as the first parameter. In case of a value type, that parameter must be a `ref` parameter. Otherwise, the method won't be able to mutate the target variable. At the same time, in case of a class type, that parameter should not be a `ref` parameter. Because in case of a class, the passed in instance must be mutated, not the location where the instance is stored. However, when an operator is declared in an interface, it is often not known whether the interface will be implemented only by classes, or only by structures. Therefore, it is not clear whether the first parameter should be a `ref` parameter. ## Design meetings - [LDM-2025-02-12](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-02-12.md#user-defined-instance-based-operators) - [LDM-2025-04-02](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-02.md#user-defined-compound-assignment-operators) ================================================ FILE: proposals/csharp-6.0/empty-params-array.md ================================================ # Empty array for zero arguments passed to params parameter We use Array.Empty() to create a zero-element array to pass to a params method when no arguments correspond. The language spec requires a new array, so it is a language change. This is a placeholder for the specification. ================================================ FILE: proposals/csharp-6.0/enum-base-type.md ================================================ ## Enum Base Type In C# 6.0 we relaxed the syntax of an enum base type to be a type syntax, but require that it bind to one of a specified set of types. So `System.Int32` is permitted. But the spec has not been updated; it still requires one of a set of keywords for the base. This is a placeholder for a specification of that change. ================================================ FILE: proposals/csharp-6.0/struct-autoprop-init.md ================================================ # Relaxed rules for auto-properties in structs Before C# 6.0 you could not write code like this: ```csharp struct S { int X { get; set; } int Y { get; set; } public S(int x, int y) { this.X = x; this.Y = y; } } ``` ```none error CS0188: The 'this' object cannot be used before all of its fields are assigned to error CS0843: Backing field for automatically implemented property 'S.X' must be fully assigned before control is returned to the caller. Consider calling the default constructor from a constructor initializer. error CS0843: Backing field for automatically implemented property 'S.Y' must be fully assigned before control is returned to the caller. Consider calling the default constructor from a constructor initializer. ``` A workaround was to invoke the default constructor: ```csharp struct S { int X { get; set; } int Y { get; set; } public S(int x, int y) : this() { this.X = x; this.Y = y; } } ``` or to initialize this to the default value: ```csharp struct S { int X { get; set; } int Y { get; set; } public S(int x, int y) { this = default(S); this.X = x; this.Y = y; } } ``` Unfortunately, both methods effectively disabled definite assignment analysis for instance fields for the given constructor. C# 6.0 relaxes these rules by tracking assignments to instance auto-properties as if they were assignments to their backing fields, so the code in the beginning of this section is now valid. ================================================ FILE: proposals/csharp-7.0/binary-literals.md ================================================ # Binary literals Champion issue: There’s a relatively common request to add binary literals to C# and VB. For bitmasks (e.g. flag enums) this seems genuinely useful, but it would also be great just for educational purposes. Binary literals would look like this: ```csharp int nineteen = 0b10011; ``` Syntactically and semantically they are identical to hexadecimal literals, except for using `b`/`B` instead of `x`/`X`, having only digits `0` and `1` and being interpreted in base 2 instead of 16. There’s little cost to implementing these, and little conceptual overhead to users of the language. ## Syntax The grammar would be as follows: ```antlr integer-literal: : ... | binary-integer-literal ; binary-integer-literal: : `0b` binary-digits integer-type-suffix-opt | `0B` binary-digits integer-type-suffix-opt ; binary-digits: : binary-digit | binary-digits binary-digit ; binary-digit: : `0` | `1` ; ``` ================================================ FILE: proposals/csharp-7.0/digit-separators.md ================================================ # Digit separators Champion issue: Being able to group digits in large numeric literals would have great readability impact and no significant downside. Adding binary literals (#215) would increase the likelihood of numeric literals being long, so the two features enhance each other. We would follow Java and others, and use an underscore `_` as a digit separator. It would be able to occur everywhere in a numeric literal (except as the first and last character), since different groupings may make sense in different scenarios and especially for different numeric bases: ```csharp int bin = 0b1001_1010_0001_0100; int hex = 0x1b_a0_44_fe; int dec = 33_554_432; int weird = 1_2__3___4____5_____6______7_______8________9; double real = 1_000.111_1e-1_000; ``` Any sequence of digits may be separated by underscores, possibly more than one underscore between two consecutive digits. They are allowed in decimals as well as exponents, but following the previous rule, they may not appear next to the decimal (`10_.0`), next to the exponent character (`1.1e_1`), or next to the type specifier (`10_f`). When used in binary and hexadecimal literals, they may not appear immediately following the `0x` or `0b`. The syntax is straightforward, and the separators have no semantic impact - they are simply ignored. This has broad value and is easy to implement. ================================================ FILE: proposals/csharp-7.0/expression-bodied-everything.md ================================================ ## Expression Bodied Everything Champion issue: In C# 7.0, we added support for expression-bodied constructors, destructors, and accessors. This is a placeholder for the specification. ================================================ FILE: proposals/csharp-7.0/local-functions.md ================================================ # Local functions Champion issue: We extend C# to support the declaration of functions in block scope. Local functions may use (capture) variables from the enclosing scope. The compiler uses flow analysis to detect which variables a local function uses before assigning it a value. Every call of the function requires such variables to be definitely assigned. Similarly the compiler determines which variables are definitely assigned on return. Such variables are considered definitely assigned after the local function is invoked. Local functions may be called from a lexical point before its definition. Local function declaration statements do not cause a warning when they are not reachable. TODO: _WRITE SPEC_ ## Syntax grammar This grammar is represented as a diff from the current spec grammar. ```diff declaration-statement : local-variable-declaration ';' | local-constant-declaration ';' + | local-function-declaration ; +local-function-declaration + : local-function-header local-function-body + ; +local-function-header + : local-function-modifiers? return-type identifier type-parameter-list? + ( formal-parameter-list? ) type-parameter-constraints-clauses + ; +local-function-modifiers + : (async | unsafe) + ; +local-function-body + : block + | arrow-expression-body + ; ``` Local functions may use variables defined in the enclosing scope. The current implementation requires that every variable read inside a local function be definitely assigned, as if executing the local function at its point of definition. Also, the local function definition must have been "executed" at any use point. After experimenting with that a bit (for example, it is not possible to define two mutually recursive local functions), we've since revised how we want the definite assignment to work. The revision (not yet implemented) is that all local variables read in a local function must be definitely assigned at each invocation of the local function. That's actually more subtle than it sounds, and there is a bunch of work remaining to make it work. Once it is done you'll be able to move your local functions to the end of its enclosing block. The new definite assignment rules are incompatible with inferring the return type of a local function, so we'll likely be removing support for inferring the return type. Unless you convert a local function to a delegate, capturing is done into frames that are value types. That means you don't get any GC pressure from using local functions with capturing. ### Reachability We add to the spec > The body of a statement-bodied lambda expression or local function is considered reachable. ================================================ FILE: proposals/csharp-7.0/out-var.md ================================================ # Out variable declarations Champion issue: The *out variable declaration* feature enables a variable to be declared at the location that it is being passed as an `out` argument. ```antlr argument_value : 'out' type identifier | ... ; ``` A variable declared this way is called an *out variable*. You may use the contextual keyword `var` for the variable's type. The scope will be the same as for a *pattern-variable* introduced via pattern-matching. According to Language Specification (section 7.6.7 Element access) the argument-list of an element-access (indexing expression) does not contain ref or out arguments. However, they are permitted by the compiler for various scenarios, for example indexers declared in metadata that accept `out`. Within the scope of a local variable introduced by an argument_value, it is a compile-time error to refer to that local variable in a textual position that precedes its declaration. It is also an error to reference an implicitly-typed (§8.5.1) out variable in the same argument list that immediately contains its declaration. Overload resolution is modified as follows: We add a new conversion: > There is a *conversion from expression* from an implicitly-typed out variable declaration to every type. Also > The type of an explicitly-typed out variable argument is the declared type. and > An implicitly-typed out variable argument has no type. The *conversion from expression* from an implicitly-typed out variable declaration is not considered better than any other *conversion from expression*. The type of an implicitly-typed out variable is the type of the corresponding parameter in the signature of the method selected by overload resolution. The new syntax node `DeclarationExpressionSyntax` is added to represent the declaration in an out var argument. ================================================ FILE: proposals/csharp-7.0/pattern-matching.md ================================================ # Pattern Matching for C# 7 Pattern matching extensions for C# enable many of the benefits of algebraic data types and pattern matching from functional languages, but in a way that smoothly integrates with the feel of the underlying language. The basic features are: [record types](../csharp-9.0/records.md), which are types whose semantic meaning is described by the shape of the data; and pattern matching, which is a new expression form that enables extremely concise multilevel decomposition of these data types. Elements of this approach are inspired by related features in the programming languages [F#](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/p29-syme.pdf "Extensible Pattern Matching Via a Lightweight Language") and [Scala](https://infoscience.epfl.ch/record/98468/files/MatchingObjectsWithPatterns-TR.pdf "Matching Objects With Patterns"). ## Is expression The `is` operator is extended to test an expression against a *pattern*. ```antlr relational_expression : relational_expression 'is' pattern ; ``` This form of *relational_expression* is in addition to the existing forms in the C# specification. It is a compile-time error if the *relational_expression* to the left of the `is` token does not designate a value or does not have a type. Every *identifier* of the pattern introduces a new local variable that is *definitely assigned* after the `is` operator is `true` (i.e. *definitely assigned when true*). > Note: There is technically an ambiguity between *type* in an `is-expression` and *constant_pattern*, either of which might be a valid parse of a qualified identifier. We try to bind it as a type for compatibility with previous versions of the language; only if that fails do we resolve it as we do in other contexts, to the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand-side of an `is` expression. ## Patterns Patterns are used in the `is` operator and in a *switch_statement* to express the shape of data against which incoming data is to be compared. Patterns may be recursive so that parts of the data may be matched against sub-patterns. ```antlr pattern : declaration_pattern | constant_pattern | var_pattern ; declaration_pattern : type simple_designation ; constant_pattern : shift_expression ; var_pattern : 'var' simple_designation ; ``` > Note: There is technically an ambiguity between *type* in an `is-expression` and *constant_pattern*, either of which might be a valid parse of a qualified identifier. We try to bind it as a type for compatibility with previous versions of the language; only if that fails do we resolve it as we do in other contexts, to the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand-side of an `is` expression. ### Declaration pattern The *declaration_pattern* both tests that an expression is of a given type and casts it to that type if the test succeeds. If the *simple_designation* is an identifier, it introduces a local variable of the given type named by the given identifier. That local variable is *definitely assigned* when the result of the pattern-matching operation is true. ```antlr declaration_pattern : type simple_designation ; ``` The runtime semantic of this expression is that it tests the runtime type of the left-hand *relational_expression* operand against the *type* in the pattern. If it is of that runtime type (or some subtype), the result of the `is operator` is `true`. It declares a new local variable named by the *identifier* that is assigned the value of the left-hand operand when the result is `true`. Certain combinations of static type of the left-hand-side and the given type are considered incompatible and result in compile-time error. A value of static type `E` is said to be *pattern compatible* with the type `T` if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from `E` to `T`. It is a compile-time error if an expression of type `E` is not pattern compatible with the type in a type pattern that it is matched with. > Note: [In C# 7.1 we extend this](../csharp-7.1/generics-pattern-match.md) to permit a pattern-matching operation if either the input type or the type `T` is an open type. This paragraph is replaced by the following: > > Certain combinations of static type of the left-hand-side and the given type are considered incompatible and result in compile-time error. A value of static type `E` is said to be *pattern compatible* with the type `T` if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from `E` to `T`, **or if either `E` or `T` is an open type**. It is a compile-time error if an expression of type `E` is not pattern compatible with the type in a type pattern that it is matched with. The declaration pattern is useful for performing run-time type tests of reference types, and replaces the idiom ```csharp var v = expr as Type; if (v != null) { // code using v } ``` With the slightly more concise ```csharp if (expr is Type v) { // code using v } ``` It is an error if *type* is a nullable value type. The declaration pattern can be used to test values of nullable types: a value of type `Nullable` (or a boxed `T`) matches a type pattern `T2 id` if the value is non-null and the type of `T2` is `T`, or some base type or interface of `T`. For example, in the code fragment ```csharp int? x = 3; if (x is int v) { // code using v } ``` The condition of the `if` statement is `true` at runtime and the variable `v` holds the value `3` of type `int` inside the block. ### Constant pattern ```antlr constant_pattern : shift_expression ; ``` A constant pattern tests the value of an expression against a constant value. The constant may be any constant expression, such as a literal, the name of a declared `const` variable, or an enumeration constant, or a `typeof` expression. If both *e* and *c* are of integral types, the pattern is considered matched if the result of the expression `e == c` is `true`. Otherwise the pattern is considered matching if `object.Equals(e, c)` returns `true`. In this case it is a compile-time error if the static type of *e* is not *pattern compatible* with the type of the constant. ### Var pattern ```antlr var_pattern : 'var' simple_designation ; ``` An expression *e* matches a *var_pattern* always. In other words, a match to a *var pattern* always succeeds. If the *simple_designation* is an identifier, then at runtime the value of *e* is bound to a newly introduced local variable. The type of the local variable is the static type of *e*. It is an error if the name `var` binds to a type. ## Switch statement The `switch` statement is extended to select for execution the first block having an associated pattern that matches the *switch expression*. ```antlr switch_label : 'case' complex_pattern case_guard? ':' | 'case' constant_expression case_guard? ':' | 'default' ':' ; case_guard : 'when' expression ; ``` The order in which patterns are matched is not defined. A compiler is permitted to match patterns out of order, and to reuse the results of already matched patterns to compute the result of matching of other patterns. If a *case-guard* is present, its expression is of type `bool`. It is evaluated as an additional condition that must be satisfied for the case to be considered satisfied. It is an error if a *switch_label* can have no effect at runtime because its pattern is subsumed by previous cases. [TODO: We should be more precise about the techniques the compiler is required to use to reach this judgment.] A pattern variable declared in a *switch_label* is definitely assigned in its case block if and only if that case block contains precisely one *switch_label*. [TODO: We should specify when a *switch block* is reachable.] ### Scope of pattern variables The scope of a variable declared in a pattern is as follows: - If the pattern is a case label, then the scope of the variable is the *case block*. Otherwise the variable is declared in an *is_pattern* expression, and its scope is based on the construct immediately enclosing the expression containing the *is_pattern* expression as follows: - If the expression is in an expression-bodied lambda, its scope is the body of the lambda. - If the expression is in an expression-bodied method or property, its scope is the body of the method or property. - If the expression is in a `when` clause of a `catch` clause, its scope is that `catch` clause. - If the expression is in an *iteration_statement*, its scope is just that statement. - Otherwise if the expression is in some other statement form, its scope is the scope containing the statement. For the purpose of determining the scope, an *embedded_statement* is considered to be in its own scope. For example, the grammar for an *if_statement* is ``` antlr if_statement : 'if' '(' boolean_expression ')' embedded_statement | 'if' '(' boolean_expression ')' embedded_statement 'else' embedded_statement ; ``` So if the controlled statement of an *if_statement* declares a pattern variable, its scope is restricted to that *embedded_statement*: ```csharp if (x) M(y is var z); ``` In this case the scope of `z` is the embedded statement `M(y is var z);`. Other cases are errors for other reasons (e.g. in a parameter's default value or an attribute, both of which are an error because those contexts require a constant expression). > [In C# 7.3 we added the following contexts](../csharp-7.3/expression-variables-in-initializers.md) in which a pattern variable may be declared: > - If the expression is in a *constructor initializer*, its scope is the *constructor initializer* and the constructor's body. > - If the expression is in a field initializer, its scope is the *equals_value_clause* in which it appears. > - If the expression is in a query clause that is specified to be translated into the body of a lambda, its scope is just that expression. ## Changes to syntactic disambiguation There are situations involving generics where the C# grammar is ambiguous, and the language spec says how to resolve those ambiguities: > #### 7.6.5.2 Grammar ambiguities > The productions for *simple-name* (§7.6.3) and *member-access* (§7.6.5) can give rise to ambiguities in the grammar for expressions. For example, the statement: > ```csharp > F(G(7)); > ``` > could be interpreted as a call to `F` with two arguments, `G < A` and `B > (7)`. Alternatively, it could be interpreted as a call to `F` with one argument, which is a call to a generic method `G` with two type arguments and one regular argument. > If a sequence of tokens can be parsed (in context) as a *simple-name* (§7.6.3), *member-access* (§7.6.5), or *pointer-member-access* (§18.5.2) ending with a *type-argument-list* (§4.4.1), the token immediately following the closing `>` token is examined. If it is one of > ```none > ( ) ] } : ; , . ? == != | ^ > ``` > then the *type-argument-list* is retained as part of the *simple-name*, *member-access* or *pointer-member-access* and any other possible parse of the sequence of tokens is discarded. Otherwise, the *type-argument-list* is not considered to be part of the *simple-name*, *member-access* or > *pointer-member-access*, even if there is no other possible parse of the sequence of tokens. Note that these rules are not applied when parsing a *type-argument-list* in a *namespace-or-type-name* (§3.8). The statement > ```csharp > F(G(7)); > ``` > will, according to this rule, be interpreted as a call to `F` with one argument, which is a call to a generic method `G` with two type arguments and one regular argument. The statements > ```csharp > F(G < A, B > 7); > F(G < A, B >> 7); > ``` > will each be interpreted as a call to `F` with two arguments. The statement > ```csharp > x = F < A > +y; > ``` > will be interpreted as a less than operator, greater than operator, and unary plus operator, as if the statement had been written `x = (F < A) > (+y)`, instead of as a *simple-name* with a *type-argument-list* followed by a binary plus operator. In the statement > ```csharp > x = y is C + z; > ``` > the tokens `C` are interpreted as a *namespace-or-type-name* with a *type-argument-list*. There are a number of changes being introduced in C# 7 that make these disambiguation rules no longer sufficient to handle the complexity of the language. ### Out variable declarations It is now possible to declare a variable in an out argument: ```csharp M(out Type name); ``` However, the type may be generic: ```csharp M(out A name); ``` Since the language grammar for the argument uses *expression*, this context is subject to the disambiguation rule. In this case the closing `>` is followed by an *identifier*, which is not one of the tokens that permits it to be treated as a *type-argument-list*. I therefore propose to **add *identifier* to the set of tokens that triggers the disambiguation to a *type-argument-list*.** ### Tuples and deconstruction declarations A tuple literal runs into exactly the same issue. Consider the tuple expression ```csharp (A < B, C > D, E < F, G > H) ``` Under the old C# 6 rules for parsing an argument list, this would parse as a tuple with four elements, starting with `A < B` as the first. However, when this appears on the left of a deconstruction, we want the disambiguation triggered by the *identifier* token as described above: ```csharp (A D, E H) = e; ``` This is a deconstruction declaration which declares two variables, the first of which is of type `A` and named `D`. In other words, the tuple literal contains two expressions, each of which is a declaration expression. For simplicity of the specification and compiler, I propose that this tuple literal be parsed as a two-element tuple wherever it appears (whether or not it appears on the left-hand-side of an assignment). That would be a natural result of the disambiguation described in the previous section. ### Pattern-matching Pattern matching introduces a new context where the expression-type ambiguity arises. Previously the right-hand-side of an `is` operator was a type. Now it can be a type or expression, and if it is a type it may be followed by an identifier. This can, technically, change the meaning of existing code: ```csharp var x = e is T < A > B; ``` This could be parsed under C#6 rules as ```csharp var x = ((e is T) < A) > B; ``` but under under C#7 rules (with the disambiguation proposed above) would be parsed as ```csharp var x = e is T B; ``` which declares a variable `B` of type `T`. Fortunately, the native and Roslyn compilers have a bug whereby they give a syntax error on the C#6 code. Therefore this particular breaking change is not a concern. Pattern-matching introduces additional tokens that should drive the ambiguity resolution toward selecting a type. The following examples of existing valid C#6 code would be broken without additional disambiguation rules: ```csharp var x = e is A && f; // && var x = e is A || f; // || var x = e is A & f; // & var x = e is A[]; // [ ``` ### Proposed change to the disambiguation rule I propose to revise the specification to change the list of disambiguating tokens from > ```none ( ) ] } : ; , . ? == != | ^ ``` to > ```none ( ) ] } : ; , . ? == != | ^ && || & [ ``` And, in certain contexts, we treat *identifier* as a disambiguating token. Those contexts are where the sequence of tokens being disambiguated is immediately preceded by one of the keywords `is`, `case`, or `out`, or arises while parsing the first element of a tuple literal (in which case the tokens are preceded by `(` or `:` and the identifier is followed by a `,`) or a subsequent element of a tuple literal. ### Modified disambiguation rule The revised disambiguation rule would be something like this > If a sequence of tokens can be parsed (in context) as a *simple-name* (§7.6.3), *member-access* (§7.6.5), or *pointer-member-access* (§18.5.2) ending with a *type-argument-list* (§4.4.1), the token immediately following the closing `>` token is examined, to see if it is > - One of `( ) ] } : ; , . ? == != | ^ && || & [`; or > - One of the relational operators `< > <= >= is as`; or > - A contextual query keyword appearing inside a query expression; or > - In certain contexts, we treat *identifier* as a disambiguating token. Those contexts are where the sequence of tokens being disambiguated is immediately preceded by one of the keywords `is`, `case` or `out`, or arises while parsing the first element of a tuple literal (in which case the tokens are preceded by `(` or `:` and the identifier is followed by a `,`) or a subsequent element of a tuple literal. > > If the following token is among this list, or an identifier in such a context, then the *type-argument-list* is retained as part of the *simple-name*, *member-access* or *pointer-member-access* and any other possible parse of the sequence of tokens is discarded. Otherwise, the *type-argument-list* is not considered to be part of the *simple-name*, *member-access* or *pointer-member-access*, even if there is no other possible parse of the sequence of tokens. Note that these rules are not applied when parsing a *type-argument-list* in a *namespace-or-type-name* (§3.8). ### Breaking changes due to this proposal No breaking changes are known due to this proposed disambiguation rule. ### Interesting examples Here are some interesting results of these disambiguation rules: The expression `(A < B, C > D)` is a tuple with two elements, each a comparison. The expression `(A D, E)` is a tuple with two elements, the first of which is a declaration expression. The invocation `M(A < B, C > D, E)` has three arguments. The invocation `M(out A D, E)` has two arguments, the first of which is an `out` declaration. The expression `e is A C` uses a declaration expression. The case label `case A C:` uses a declaration expression. ## Some examples of pattern matching ### Is-As We can replace the idiom ```csharp var v = expr as Type; if (v != null) { // code using v } ``` With the slightly more concise and direct ```csharp if (expr is Type v) { // code using v } ``` ### Testing nullable We can replace the idiom ```csharp Type? v = x?.y?.z; if (v.HasValue) { var value = v.GetValueOrDefault(); // code using value } ``` With the slightly more concise and direct ```csharp if (x?.y?.z is Type value) { // code using value } ``` ### Arithmetic simplification Suppose we define a set of recursive types to represent expressions (per a separate proposal): ```csharp abstract class Expr; class X() : Expr; class Const(double Value) : Expr; class Add(Expr Left, Expr Right) : Expr; class Mult(Expr Left, Expr Right) : Expr; class Neg(Expr Value) : Expr; ``` Now we can define a function to compute the (unreduced) derivative of an expression: ```csharp Expr Deriv(Expr e) { switch (e) { case X(): return Const(1); case Const(*): return Const(0); case Add(var Left, var Right): return Add(Deriv(Left), Deriv(Right)); case Mult(var Left, var Right): return Add(Mult(Deriv(Left), Right), Mult(Left, Deriv(Right))); case Neg(var Value): return Neg(Deriv(Value)); } } ``` An expression simplifier demonstrates positional patterns: ```csharp Expr Simplify(Expr e) { switch (e) { case Mult(Const(0), *): return Const(0); case Mult(*, Const(0)): return Const(0); case Mult(Const(1), var x): return Simplify(x); case Mult(var x, Const(1)): return Simplify(x); case Mult(Const(var l), Const(var r)): return Const(l*r); case Add(Const(0), var x): return Simplify(x); case Add(var x, Const(0)): return Simplify(x); case Add(Const(var l), Const(var r)): return Const(l+r); case Neg(Const(var k)): return Const(-k); default: return e; } } ``` ================================================ FILE: proposals/csharp-7.0/ref-locals-returns.md ================================================ ## Ref Locals and Returns Champion issue: In C# 7.0 we added support for *ref locals and ref returns*. This is a placeholder for its specification. ================================================ FILE: proposals/csharp-7.0/task-types.md ================================================ # Async Task Types in C# # Champion issue: Extend `async` to support _task types_ that match a specific pattern, in addition to the well known types `System.Threading.Tasks.Task` and `System.Threading.Tasks.Task`. ## Task Type A _task type_ is a `class` or `struct` with an associated _builder type_ identified with `System.Runtime.CompilerServices.AsyncMethodBuilderAttribute`. The _task type_ may be non-generic, for async methods that do not return a value, or generic, for methods that return a value. To support `await`, the _task type_ must have a corresponding, accessible `GetAwaiter()` method that returns an instance of an _awaiter type_ (see _C# 7.7.7.1 Awaitable expressions_). ```cs [AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))] class MyTask { public Awaiter GetAwaiter(); } class Awaiter : INotifyCompletion { public bool IsCompleted { get; } public T GetResult(); public void OnCompleted(Action completion); } ``` ## Builder Type The _builder type_ is a `class` or `struct` that corresponds to the specific _task type_. The _builder type_ can have at most 1 type parameter and must not be nested in a generic type. The _builder type_ has the following `public` methods. For non-generic _builder types_, `SetResult()` has no parameters. ```cs class MyTaskMethodBuilder { public static MyTaskMethodBuilder Create(); public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine; public void SetStateMachine(IAsyncStateMachine stateMachine); public void SetException(Exception exception); public void SetResult(T result); public void AwaitOnCompleted( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine; public void AwaitUnsafeOnCompleted( ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine; public MyTask Task { get; } } ``` ## Execution The types above are used by the compiler to generate the code for the state machine of an `async` method. (The generated code is equivalent to the code generated for async methods that return `Task`, `Task`, or `void`. The difference is, for those well known types, the _builder types_ are also known to the compiler.) `Builder.Create()` is invoked to create an instance of the _builder type_. `builder.Start(ref stateMachine)` is invoked to associate the builder with compiler-generated state machine instance. The builder must call `stateMachine.MoveNext()` either in `Start()` or after `Start()` has returned to advance the state machine. After `Start()` returns, the `async` method calls `builder.Task` for the task to return from the async method. Each call to `stateMachine.MoveNext()` will advance the state machine. If the state machine completes successfully, `builder.SetResult()` is called, with the method return value if any. If an exception is thrown in the state machine, `builder.SetException(exception)` is called. If the state machine reaches an `await expr` expression, `expr.GetAwaiter()` is invoked. If the awaiter implements `ICriticalNotifyCompletion` and `IsCompleted` is false, the state machine invokes `builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)`. `AwaitUnsafeOnCompleted()` should call `awaiter.UnsafeOnCompleted(action)` with an `Action` that calls `stateMachine.MoveNext()` when the awaiter completes. Similarly for `INotifyCompletion` and `builder.AwaitOnCompleted()`. `SetStateMachine(IAsyncStateMachine)` is called by the compiler-generated `IAsyncStateMachine` implementation. That can be used to identify the instance of the builder associated with a state machine instance, particularly for cases where the state machine is implemented as a value type: if the builder calls `stateMachine.SetStateMachine(stateMachine)`, the `stateMachine` will call `builder.SetStateMachine(stateMachine)` on the _builder instance associated with `stateMachine`_. ## Overload Resolution Overload resolution is extended to recognize _task types_ in addition to `Task` and `Task`. An `async` lambda with no return value is an exact match for an overload candidate parameter of non-generic _task type_, and an `async` lambda with return type `T` is an exact match for an overload candidate parameter of generic _task type_. Otherwise if an `async` lambda is not an exact match for either of two candidate parameters of _task types_, or an exact match for both, and there is an implicit conversion from one candidate type to the other, the from candidate wins. Otherwise recursively evaluate the types `A` and `B` within `Task1` and `Task2` for better match. Otherwise if an `async` lambda is not an exact match for either of two candidate parameters of _task types_, but one candidate is a more specialized type than the other, the more specialized candidate wins. ================================================ FILE: proposals/csharp-7.0/throw-expression.md ================================================ # Throw expression Champion issue: We extend the set of expression forms to include ```antlr throw_expression : 'throw' null_coalescing_expression ; null_coalescing_expression : throw_expression ; ``` The type rules are as follows: - A *throw_expression* has no type. - A *throw_expression* is convertible to every type by an implicit conversion. A *throw expression* throws the value produced by evaluating the *null_coalescing_expression*, which must denote a value of the class type `System.Exception`, of a class type that derives from `System.Exception` or of a type parameter type that has `System.Exception` (or a subclass thereof) as its effective base class. If evaluation of the expression produces `null`, a `System.NullReferenceException` is thrown instead. The behavior at runtime of the evaluation of a *throw expression* is the same as specified for a *throw statement* ([§12.10.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#12106-the-throw-statement)). The flow-analysis rules are as follows: - For every variable *v*, *v* is definitely assigned before the *null_coalescing_expression* of a *throw_expression* iff it is definitely assigned before the *throw_expression*. - For every variable *v*, *v* is definitely assigned after *throw_expression*. A *throw expression* is permitted in only the following syntactic contexts: - As the second or third operand of a ternary conditional operator `?:` - As the second operand of a null coalescing operator `??` - As the body of an expression-bodied lambda or method. ================================================ FILE: proposals/csharp-7.0/tuples.md ================================================ ## Tuples Champion issue: In C# 7.0 we added support for *tuples*. This is a placeholder for its specification. ================================================ FILE: proposals/csharp-7.1/README.md ================================================ # C# 7.1 - [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) - [Infer tuple 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) ================================================ FILE: proposals/csharp-7.1/async-main.md ================================================ # Async Main Champion issue: ## Summary [summary]: #summary Allow `await` to be used in an application's Main / entrypoint method by allowing the entrypoint to return `Task` / `Task` and be marked `async`. ## Motivation [motivation]: #motivation It is very common when learning C#, when writing console-based utilities, and when writing small test apps to want to call and `await` `async` methods from Main. Today we add a level of complexity here by forcing such `await`'ing to be done in a separate async method, which causes developers to need to write boilerplate like the following just to get started: ```csharp public static void Main() { MainAsync().GetAwaiter().GetResult(); } private static async Task MainAsync() { ... // Main body here } ``` We can remove the need for this boilerplate and make it easier to get started simply by allowing Main itself to be `async` such that `await`s can be used in it. ## Detailed design [design]: #detailed-design The following signatures are currently allowed entrypoints: ```csharp static void Main() static void Main(string[]) static int Main() static int Main(string[]) ``` We extend the list of allowed entrypoints to include: ```csharp static Task Main() static Task Main() static Task Main(string[]) static Task Main(string[]) ``` To avoid compatibility risks, these new signatures will only be considered as valid entrypoints if no overloads of the previous set are present. The language / compiler will not require that the entrypoint be marked as `async`, though we expect the vast majority of uses will be marked as such. When one of these is identified as the entrypoint, the compiler will synthesize an actual entrypoint method that calls one of these coded methods: - ```static Task Main()``` will result in the compiler emitting the equivalent of ```private static void $GeneratedMain() => Main().GetAwaiter().GetResult();``` - ```static Task Main(string[])``` will result in the compiler emitting the equivalent of ```private static void $GeneratedMain(string[] args) => Main(args).GetAwaiter().GetResult();``` - ```static Task Main()``` will result in the compiler emitting the equivalent of ```private static int $GeneratedMain() => Main().GetAwaiter().GetResult();``` - ```static Task Main(string[])``` will result in the compiler emitting the equivalent of ```private static int $GeneratedMain(string[] args) => Main(args).GetAwaiter().GetResult();``` Example usage: ```csharp using System; using System.Net.Http; class Test { static async Task Main(string[] args) => Console.WriteLine(await new HttpClient().GetStringAsync(args[0])); } ``` ## Drawbacks [drawbacks]: #drawbacks The main drawback is simply the additional complexity of supporting additional entrypoint signatures. ## Alternatives [alternatives]: #alternatives Other variants considered: Allowing `async void`. We need to keep the semantics the same for code calling it directly, which would then make it difficult for a generated entrypoint to call it (no Task returned). We could solve this by generating two other methods, e.g. ```csharp public static async void Main() { ... // await code } ``` becomes ```csharp public static async void Main() => await $MainTask(); private static void $EntrypointMain() => Main().GetAwaiter().GetResult(); private static async Task $MainTask() { ... // await code } ``` There are also concerns around encouraging usage of `async void`. Using "MainAsync" instead of "Main" as the name. While the async suffix is recommended for Task-returning methods, that's primarily about library functionality, which Main is not, and supporting additional entrypoint names beyond "Main" is not worth it. ## Unresolved questions [unresolved]: #unresolved-questions n/a ## Design meetings n/a ================================================ FILE: proposals/csharp-7.1/generics-pattern-match.md ================================================ # pattern-matching with generics Champion issue: ## Summary [summary]: #summary The specification for the existing C# as operator ([§11.11.12](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111112-the-as-operator)) permits there to be no conversion between the type of the operand and the specified type when either is an open type. However, in C# 7 the `Type identifier` pattern requires there be a conversion between the type of the input and the given type. We propose to relax this and change `expression is Type identifier`, in addition to being permitted in the conditions when it is permitted in C# 7, to also be permitted when `expression as Type` would be allowed. Specifically, the new cases are cases where the type of the expression or the specified type is an open type. ## Motivation [motivation]: #motivation Cases where pattern-matching should "obviously" be permitted currently fail to compile. See, for example, https://github.com/dotnet/roslyn/issues/16195. ## Detailed design [design]: #detailed-design We change the paragraph in the pattern-matching specification (the proposed addition is shown in bold): > Certain combinations of static type of the left-hand-side and the given type are considered incompatible and result in compile-time error. A value of static type `E` is said to be *pattern compatible* with the type `T` if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from `E` to `T`**, or if either `E` or `T` is an open type**. It is a compile-time error if an expression of type `E` is not pattern compatible with the type in a type pattern that it is matched with. ## Drawbacks [drawbacks]: #drawbacks None. ## Alternatives [alternatives]: #alternatives None. ## Unresolved questions [unresolved]: #unresolved-questions None. ## Design meetings LDM considered this question and felt it was a bug-fix level change. We are treating it as a separate language feature because just making the change after the language has been released would introduce a forward incompatibility. Using the proposed change requires that the programmer specify language version 7.1. ================================================ FILE: proposals/csharp-7.1/infer-tuple-names.md ================================================ # Infer tuple names (aka. tuple projection initializers) ## Summary [summary]: #summary In a number of common cases, this feature allows the tuple element names to be omitted and instead be inferred. For instance, instead of typing `(f1: x.f1, f2: x?.f2)`, the element names "f1" and "f2" can be inferred from `(x.f1, x?.f2)`. This parallels the behavior of anonymous types, which allow inferring member names during creation. For instance, `new { x.f1, y?.f2 }` declares members "f1" and "f2". This is particularly handy when using tuples in LINQ: ```csharp // "c" and "result" have element names "f1" and "f2" var result = list.Select(c => (c.f1, c.f2)).Where(t => t.f2 == 1); ``` ## Detailed design [design]: #detailed-design There are two parts to the change: 1. Try to infer a candidate name for each tuple element which does not have an explicit name: - Using same rules as name inference for anonymous types. - In C#, this allows three cases: `y` (identifier), `x.y` (simple member access) and `x?.y` (conditional access). - In VB, this allows for additional cases, such as `x.y()`. - Rejecting reserved tuple names (case-sensitive in C#, case-insensitive in VB), as they are either forbidden or already implicit. For instance, such as `ItemN`, `Rest`, and `ToString`. - If any candidate names are duplicates (case-sensitive in C#, case-insensitive in VB) within the entire tuple, we drop those candidates, 2. During conversions (which check and warn about dropping names from tuple literals), inferred names would not produce any warnings. This avoids breaking existing tuple code. Note that the rule for handling duplicates is different than that for anonymous types. For instance, `new { x.f1, x.f1 }` produces an error, but `(x.f1, x.f1)` would still be allowed (just without any inferred names). This avoids breaking existing tuple code. For consistency, the same would apply to tuples produced by deconstruction-assignments (in C#): ```csharp // tuple has element names "f1" and "f2" var tuple = ((x.f1, x?.f2) = (1, 2)); ``` The same would also apply to VB tuples, using the VB-specific rules for inferring name from expression and case-insensitive name comparisons. When using the C# 7.1 compiler (or later) with language version "7.0", the element names will be inferred (despite the feature not being available), but there will be a use-site error for trying to access them. This will limit additions of new code that would later face the compatibility issue (described below). ## Drawbacks [drawbacks]: #drawbacks The main drawback is that this introduces a compatibility break from C# 7.0: ```csharp Action y = () => M(); var t = (x: x, y); t.y(); // this might have previously picked up an extension method called “y”, but would now call the lambda. ``` The compatibility council found this break acceptable, given that it is limited and the time window since tuples shipped (in C# 7.0) is short. ## References - [LDM April 4th 2017](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-05.md#tuple-names) - [Github discussion](https://github.com/dotnet/csharplang/issues/370) (thanks @alrz for bringing this issue up) - [Tuples design](https://github.com/dotnet/roslyn/blob/master/docs/features/tuples.md) ================================================ FILE: proposals/csharp-7.1/target-typed-default.md ================================================ # Target-typed "default" literal Champion issue: ## Summary [summary]: #summary The target-typed `default` feature is a shorter form variation of the `default(T)` operator, which allows the type to be omitted. Its type is inferred by target-typing instead. Aside from that, it behaves like `default(T)`. ## Motivation [motivation]: #motivation The main motivation is to avoid typing redundant information. For instance, when invoking `void Method(ImmutableArray array)`, the *default* literal allows `M(default)` in place of `M(default(ImmutableArray))`. This is applicable in a number of scenarios, such as: - declaring locals (`ImmutableArray x = default;`) - ternary operations (`var x = flag ? default : ImmutableArray.Empty;`) - returning in methods and lambdas (`return default;`) - declaring default values for optional parameters (`void Method(ImmutableArray arrayOpt = default)`) - including default values in array creation expressions (`var x = new[] { default, ImmutableArray.Create(y) };`) ## Detailed design [design]: #detailed-design A new expression is introduced, the *default* literal. An expression with this classification can be implicitly converted to any type, by a *default literal conversion*. The inference of the type for the *default* literal works the same as that for the *null* literal, except that any type is allowed (not just reference types). This conversion produces the default value of the inferred type. The *default* literal may have a constant value, depending on the inferred type. So `const int x = default;` is legal, but `const int? y = default;` is not. The *default* literal can be the operand of equality operators, as long as the other operand has a type. So `default == x` and `x == default` are valid expressions, but `default == default` is illegal. ## Drawbacks [drawbacks]: #drawbacks A minor drawback is that *default* literal can be used in place of *null* literal in most contexts. Two of the exceptions are `throw null;` and `null == null`, which are allowed for the *null* literal, but not the *default* literal. ## Alternatives [alternatives]: #alternatives There are a couple of alternatives to consider: - The status quo: The feature is not justified on its own merits and developers continue to use the default operator with an explicit type. - Extending the null literal: This is the VB approach with `Nothing`. We could allow `int x = null;`. ## Unresolved questions [unresolved]: #unresolved-questions - [x] Should *default* be allowed as the operand of the *is* or *as* operators? Answer: disallow `default is T`, allow `x is default`, allow `default as RefType` (with always-null warning) ## Design meetings - [LDM 3/7/2017](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-03-07.md) - [LDM 3/28/2017](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-03-28.md) - [LDM 5/31/2017](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-31.md#default-in-operators) ================================================ FILE: proposals/csharp-7.2/conditional-ref.md ================================================ # Conditional ref expressions Champion issue: The pattern of binding a ref variable to one or another expression conditionally is not currently expressible in C#. The typical workaround is to introduce a method like: ```csharp ref T Choice(bool condition, ref T consequence, ref T alternative) { if (condition) { return ref consequence; } else { return ref alternative; } } ``` Note that this is not an exact replacement of a ternary since all arguments must be evaluated at the call site. The following will not work as expected: ```csharp // will crash with NRE because 'arr[0]' will be executed unconditionally ref var r = ref Choice(arr != null, ref arr[0], ref otherArr[0]); ``` The proposed syntax would look like: ```csharp ? ref : ref ; ``` The above attempt with "Choice" can be _correctly_ written using ref ternary as: ```csharp ref var r = ref (arr != null ? ref arr[0]: ref otherArr[0]); ``` The difference from Choice is that consequence and alternative expressions are accessed in a _truly_ conditional manner, so we do not see a crash if ```arr == null``` The ternary ref is just a ternary where both alternative and consequence are refs. It will naturally require that consequence/alternative operands are LValues. It will also require that consequence and alternative have types that are identity convertible to each other. The type of the expression will be computed similarly to the one for the regular ternary. I.E. in a case if consequence and alternative have identity convertible, but different types, the existing type-merging rules will apply. Safe-to-return will be assumed conservatively from the conditional operands. If either is unsafe to return the whole thing is unsafe to return. Ref ternary is an LValue and as such it can be passed/assigned/returned by reference; ```csharp // pass by reference foo(ref (arr != null ? ref arr[0]: ref otherArr[0])); // return by reference return ref (arr != null ? ref arr[0]: ref otherArr[0]); ``` Being an LValue, it can also be assigned to. ```csharp // assign to (arr != null ? ref arr[0]: ref otherArr[0]) = 1; ``` Ref ternary can be used in a regular (not ref) context as well. Although it would not be common since you could as well just use a regular ternary. ```csharp int x = (arr != null ? ref arr[0]: ref otherArr[0]); ``` ___ Implementation notes: The complexity of the implementation would seem to be the size of a moderate-to-large bug fix. - I.E not very expensive. I do not think we need any changes to the syntax or parsing. There is no effect on metadata or interop. The feature is completely expression based. No effect on debugging/PDB either ================================================ FILE: proposals/csharp-7.2/leading-separator.md ================================================ # Allow digit separator after 0b or 0x Champion issue: In C# 7.2, we extend the set of places that digit separators (the underscore character) can appear in integral literals. [Beginning in C# 7.0, separators are permitted between the digits of a literal](../csharp-7.0/digit-separators.md). Now, in C# 7.2, we also permit digit separators before the first significant digit of a binary or hexadecimal literal, after the prefix. ```csharp 123 // permitted in C# 1.0 and later 1_2_3 // permitted in C# 7.0 and later 0x1_2_3 // permitted in C# 7.0 and later 0b101 // binary literals added in C# 7.0 0b1_0_1 // permitted in C# 7.0 and later // in C# 7.2, _ is permitted after the `0x` or `0b` 0x_1_2 // permitted in C# 7.2 and later 0b_1_0_1 // permitted in C# 7.2 and later ``` We do not permit a decimal integer literal to have a leading underscore. A token such as `_123` is an identifier. ================================================ FILE: proposals/csharp-7.2/non-trailing-named-arguments.md ================================================ # Non-trailing named arguments Champion issue: ## Summary [summary]: #summary Allow named arguments to be used in non-trailing position, as long as they are used in their correct position. For example: `DoSomething(isEmployed:true, name, age);`. ## Motivation [motivation]: #motivation The main motivation is to avoid typing redundant information. It is common to name an argument that is a literal (such as `null`, `true`) for the purpose of clarifying the code, rather than of passing arguments out-of-order. That is currently disallowed (`CS1738`) unless all the following arguments are also named. ```csharp DoSomething(isEmployed:true, name, age); // currently disallowed, even though all arguments are in position // CS1738 "Named argument specifications must appear after all fixed arguments have been specified" ``` Some additional examples: ```csharp public void DoSomething(bool isEmployed, string personName, int personAge) { ... } DoSomething(isEmployed:true, name, age); // currently CS1738, but would become legal DoSomething(true, personName:name, age); // currently CS1738, but would become legal DoSomething(name, isEmployed:true, age); // remains illegal DoSomething(name, age, isEmployed:true); // remains illegal DoSomething(true, personAge:age, personName:name); // already legal ``` This would also work with params: ```csharp public class Task { public static Task When(TaskStatus all, TaskStatus any, params Task[] tasks); } Task.When(all: TaskStatus.RanToCompletion, any: TaskStatus.Faulted, task1, task2) ``` ## Detailed design [design]: #detailed-design In §7.5.1 (Argument lists), the spec currently says: > An *argument* with an *argument-name* is referred to as a __named argument__, whereas an *argument* without an *argument-name* is a __positional argument__. It is an error for a positional argument to appear after a named argument in an *argument-list*. The proposal is to remove this error and update the rules for finding the corresponding parameter for an argument (§7.5.1.1): Arguments in the argument-list of instance constructors, methods, indexers and delegates: - [existing rules] - An unnamed argument corresponds to no parameter when it is after an out-of-position named argument or a named params argument. In particular, this prevents invoking `void M(bool a = true, bool b = true, bool c = true, );` with `M(c: false, valueB);`. The first argument is used out-of-position (the argument is used in first position, but the parameter named "c" is in third position), so the following arguments should be named. In other words, non-trailing named arguments are only allowed when the name and the position result in finding the same corresponding parameter. ## Drawbacks [drawbacks]: #drawbacks This proposal exacerbates existing subtleties with named arguments in overload resolution. For instance: ```csharp void M(int x, int y) { } void M(T y, int x) { } void M2() { M(3, 4); M(y: 3, x: 4); // Invokes M(int, int) M(y: 3, 4); // Invokes M(T, int) } ``` You could get this situation today by swapping the parameters: ```csharp void M(int y, int x) { } void M(int x, T y) { } void M2() { M(3, 4); M(x: 3, y: 4); // Invokes M(int, int) M(3, y: 4); // Invokes M(int, T) } ``` Similarly, if you have two methods `void M(int a, int b)` and `void M(int x, string y)`, the mistaken invocation `M(x: 1, 2)` will produce a diagnostic based on the second overload ("cannot convert from 'int' to 'string'"). This problem already exists when the named argument is used in a trailing position. ## Alternatives [alternatives]: #alternatives There are a couple of alternatives to consider: - The status quo - Providing IDE assistance to fill-in all the names of trailing arguments when you type specific a name in the middle. Both of those suffer from more verbosity, as they introduce multiple named arguments even if you just need one name of a literal at the beginning of the argument list. ## Unresolved questions [unresolved]: #unresolved-questions ## Design meetings [ldm]: #ldm The feature was briefly discussed in LDM on May 16th 2017, with approval in principle (ok to move to proposal/prototype). It was also briefly discussed on June 28th 2017. Relates to initial discussion https://github.com/dotnet/csharplang/issues/518 Relates to championed issue https://github.com/dotnet/csharplang/issues/570 ================================================ FILE: proposals/csharp-7.2/private-protected.md ================================================ # private protected Champion issue: ## Summary [summary]: #summary Expose the CLR `protectedAndInternal` accessibility level in C# as `private protected`. ## Motivation [motivation]: #motivation There are many circumstances in which an API contains members that are only intended to be implemented and used by subclasses contained in the assembly that provides the type. While the CLR provides an accessibility level for that purpose, it is not available in C#. Consequently API owners are forced to either use `internal` protection and self-discipline or a custom analyzer, or to use `protected` with additional documentation explaining that, while the member appears in the public documentation for the type, it is not intended to be part of the public API. For examples of the latter, see members of Roslyn's `CSharpCompilationOptions` whose names start with `Common`. Directly providing support for this access level in C# enables these circumstances to be expressed naturally in the language. ## Detailed design [design]: #detailed-design ### `private protected` access modifier We propose to add a new access modifier combination `private protected` (which can appear in any order among the modifiers). This maps to the CLR notion of protectedAndInternal, and borrows the same syntax currently used in [C++/CLI](https://docs.microsoft.com/cpp/dotnet/how-to-define-and-consume-classes-and-structs-cpp-cli#BKMK_Member_visibility). A member declared `private protected` can be accessed within a subclass of its container if that subclass is in the same assembly as the member. We modify the language specification as follows (additions in bold). Section numbers are not shown below as they may vary depending on which version of the specification it is integrated into. ----- > The declared accessibility of a member can be one of the following: - Public, which is selected by including a public modifier in the member declaration. The intuitive meaning of public is “access not limited”. - Protected, which is selected by including a protected modifier in the member declaration. The intuitive meaning of protected is “access limited to the containing class or types derived from the containing class”. - Internal, which is selected by including an internal modifier in the member declaration. The intuitive meaning of internal is “access limited to this assembly”. - Protected internal, which is selected by including both a protected and an internal modifier in the member declaration. The intuitive meaning of protected internal is “accessible within this assembly as well as types derived from the containing class”. - **Private protected, which is selected by including both a private and a protected modifier in the member declaration. The intuitive meaning of private protected is “accessible within this assembly by types derived from the containing class”.** ----- > Depending on the context in which a member declaration takes place, only certain types of declared accessibility are permitted. Furthermore, when a member declaration does not include any access modifiers, the context in which the declaration takes place determines the default declared accessibility. - Namespaces implicitly have public declared accessibility. No access modifiers are allowed on namespace declarations. - Types declared directly in compilation units or namespaces (as opposed to within other types) can have public or internal declared accessibility and default to internal declared accessibility. - Class members can have any of the five kinds of declared accessibility and default to private declared accessibility. [Note: A type declared as a member of a class can have any of the five kinds of declared accessibility, whereas a type declared as a member of a namespace can have only public or internal declared accessibility. end note] - Struct members can have public, internal, or private declared accessibility and default to private declared accessibility because structs are implicitly sealed. Struct members introduced in a struct (that is, not inherited by that struct) cannot have protected*,* ~~or~~ protected internal**, or private protected** declared accessibility. [Note: A type declared as a member of a struct can have public, internal, or private declared accessibility, whereas a type declared as a member of a namespace can have only public or internal declared accessibility. end note] - Interface members implicitly have public declared accessibility. No access modifiers are allowed on interface member declarations. - Enumeration members implicitly have public declared accessibility. No access modifiers are allowed on enumeration member declarations. ----- > The accessibility domain of a nested member M declared in a type T within a program P, is defined as follows (noting that M itself might possibly be a type): - If the declared accessibility of M is public, the accessibility domain of M is the accessibility domain of T. - If the declared accessibility of M is protected internal, let D be the union of the program text of P and the program text of any type derived from T, which is declared outside P. The accessibility domain of M is the intersection of the accessibility domain of T with D. - **If the declared accessibility of M is private protected, let D be the intersection of the program text of P and the program text of any type derived from T. The accessibility domain of M is the intersection of the accessibility domain of T with D.** - If the declared accessibility of M is protected, let D be the union of the program text of T and the program text of any type derived from T. The accessibility domain of M is the intersection of the accessibility domain of T with D. - If the declared accessibility of M is internal, the accessibility domain of M is the intersection of the accessibility domain of T with the program text of P. - If the declared accessibility of M is private, the accessibility domain of M is the program text of T. ----- > When a protected **or private protected** instance member is accessed outside the program text of the class in which it is declared, and when a protected internal instance member is accessed outside the program text of the program in which it is declared, the access shall take place within a class declaration that derives from the class in which it is declared. Furthermore, the access is required to take place through an instance of that derived class type or a class type constructed from it. This restriction prevents one derived class from accessing protected members of other derived classes, even when the members are inherited from the same base class. ----- > The permitted access modifiers and the default access for a type declaration depend on the context in which the declaration takes place (§9.5.2): - Types declared in compilation units or namespaces can have public or internal access. The default is internal access. - Types declared in classes can have public, protected internal, **private protected**, protected, internal, or private access. The default is private access. - Types declared in structs can have public, internal, or private access. The default is private access. ----- > A static class declaration is subject to the following restrictions: - A static class shall not include a sealed or abstract modifier. (However, since a static class cannot be instantiated or derived from, it behaves as if it was both sealed and abstract.) - A static class shall not include a class-base specification (§16.2.5) and cannot explicitly specify a base class or a list of implemented interfaces. A static class implicitly inherits from type object. - A static class shall only contain static members (§16.4.8). [Note: All constants and nested types are classified as static members. end note] - A static class shall not have members with protected**, private protected** or protected internal declared accessibility. > It is a compile-time error to violate any of these restrictions. ----- > A class-member-declaration can have any one of the ~~five~~**six** possible kinds of declared accessibility (§9.5.2): public, **private protected**, protected internal, protected, internal, or private. Except for the protected internal **and private protected** combination**s**, it is a compile-time error to specify more than one access modifier. When a class-member-declaration does not include any access modifiers, private is assumed. ----- > Non-nested types can have public or internal declared accessibility and have internal declared accessibility by default. Nested types can have these forms of declared accessibility too, plus one or more additional forms of declared accessibility, depending on whether the containing type is a class or struct: - A nested type that is declared in a class can have any of ~~five~~**six** forms of declared accessibility (public, **private protected**, protected internal, protected, internal, or private) and, like other class members, defaults to private declared accessibility. - A nested type that is declared in a struct can have any of three forms of declared accessibility (public, internal, or private) and, like other struct members, defaults to private declared accessibility. ----- > The method overridden by an override declaration is known as the overridden base method For an override method M declared in a class C, the overridden base method is determined by examining each base class type of C, starting with the direct base class type of C and continuing with each successive direct base class type, until in a given base class type at least one accessible method is located which has the same signature as M after substitution of type arguments. For the purposes of locating the overridden base method, a method is considered accessible if it is public, if it is protected, if it is protected internal, or if it is **either** internal **or private protected** and declared in the same program as C. ----- > The use of accessor-modifiers is governed by the following restrictions: - An accessor-modifier shall not be used in an interface or in an explicit interface member implementation. - For a property or indexer that has no override modifier, an accessor-modifier is permitted only if the property or indexer has both a get and set accessor, and then is permitted only on one of those accessors. - For a property or indexer that includes an override modifier, an accessor shall match the accessor-modifier, if any, of the accessor being overridden. - The accessor-modifier shall declare an accessibility that is strictly more restrictive than the declared accessibility of the property or indexer itself. To be precise: - If the property or indexer has a declared accessibility of public, the accessor-modifier may be either **private protected**, , protected internal, internal, protected, or private. - If the property or indexer has a declared accessibility of protected internal, the accessor-modifier may be either **private protected**, internal, protected, or private. - If the property or indexer has a declared accessibility of internal or protected, the accessor-modifier shall be **either private protected or** private. - **If the property or indexer has a declared accessibility of private protected, the accessor-modifier shall be private.** - If the property or indexer has a declared accessibility of private, no accessor-modifier may be used. ----- > Since inheritance isn’t supported for structs, the declared accessibility of a struct member cannot be protected, **private protected**, or protected internal. ----- ## Drawbacks [drawbacks]: #drawbacks As with any language feature, we must question whether the additional complexity to the language is repaid in the additional clarity offered to the body of C# programs that would benefit from the feature. ## Alternatives [alternatives]: #alternatives An alternative would be the provision of an API combining an attribute and an analyzer. The attribute is placed by the programmer on an `internal` member to indicates that the member is intended to be used only in subclasses, and the analyzer checks that those restrictions are obeyed. ## Unresolved questions [unresolved]: #unresolved-questions The implementation is largely complete. The only open work item is drafting a corresponding specification for VB. ## Design meetings TBD ================================================ FILE: proposals/csharp-7.2/readonly-ref.md ================================================ # Readonly references Champion issue: ## Summary [summary]: #summary The "readonly references" feature is actually a group of features that leverage the efficiency of passing variables by reference, but without exposing the data to modifications: - `in` parameters - `ref readonly` returns - `readonly` structs - `ref`/`in` extension methods - `ref readonly` locals - `ref` conditional expressions ## Passing arguments as readonly references. There is an existing proposal that touches this topic https://github.com/dotnet/roslyn/issues/115 as a special case of readonly parameters without going into many details. Here I just want to acknowledge that the idea by itself is not very new. ### Motivation Prior to this feature C# did not have an efficient way of expressing a desire to pass struct variables into method calls for readonly purposes with no intention of modifying. Regular by-value argument passing implies copying, which adds unnecessary costs. That drives users to use by-ref argument passing and rely on comments/documentation to indicate that the data is not supposed to be mutated by the callee. It is not a good solution for many reasons. The examples are numerous - vector/matrix math operators in graphics libraries like [XNA](https://msdn.microsoft.com/library/bb194944.aspx) are known to have ref operands purely because of performance considerations. There is code in Roslyn compiler itself that uses structs to avoid allocations and then passes them by reference to avoid copying costs. ### Solution (`in` parameters) Similarly to the `out` parameters, `in` parameters are passed as managed references with additional guarantees from the callee. Unlike `out` parameters which _must_ be assigned by the callee before any other use, `in` parameters cannot be assigned by the callee at all. As a result `in` parameters allow for effectiveness of indirect argument passing without exposing arguments to mutations by the callee. ### Declaring `in` parameters `in` parameters are declared by using `in` keyword as a modifier in the parameter signature. For all purposes the `in` parameter is treated as a `readonly` variable. Most of the restrictions on the use of `in` parameters inside the method are the same as with `readonly` fields. > Indeed an `in` parameter may represent a `readonly` field. Similarity of restrictions is not a coincidence. For example fields of an `in` parameter which has a struct type are all recursively classified as `readonly` variables . ```csharp static Vector3 Add (in Vector3 v1, in Vector3 v2) { // not OK!! v1 = default(Vector3); // not OK!! v1.X = 0; // not OK!! foo(ref v1.X); // OK return new Vector3(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); } ``` - `in` parameters are allowed anywhere where ordinary byval parameters are allowed. This includes indexers, operators (including conversions), delegates, lambdas, local functions. > ```csharp > (in int x) => x // lambda expression > TValue this[in TKey index]; // indexer > public static Vector3 operator +(in Vector3 x, in Vector3 y) => ... // operator > ``` - `in` is not allowed in combination with `out` or with anything that `out` does not combine with. - It is not permitted to overload on `ref`/`out`/`in` differences. - It is permitted to overload on ordinary byval and `in` differences. - For the purpose of OHI (Overloading, Hiding, Implementing), `in` behaves similarly to an `out` parameter. All the same rules apply. For example the overriding method will have to match `in` parameters with `in` parameters of an identity-convertible type. - For the purpose of delegate/lambda/method group conversions, `in` behaves similarly to an `out` parameter. Lambdas and applicable method group conversion candidates will have to match `in` parameters of the target delegate with `in` parameters of an identity-convertible type. - For the purpose of generic variance, `in` parameters are nonvariant. > NOTE: There are no warnings on `in` parameters that have reference or primitives types. It may be pointless in general, but in some cases user must/want to pass primitives as `in`. Examples - overriding a generic method like `Method(in T param)` when `T` was substituted to be `int`, or when having methods like `Volatile.Read(in int location)` > > It is conceivable to have an analyzer that warns in cases of inefficient use of `in` parameters, but the rules for such analysis would be too fuzzy to be a part of a language specification. ### Use of `in` at call sites. (`in` arguments) There are two ways to pass arguments to `in` parameters. #### `in` arguments can match `in` parameters: An argument with an `in` modifier at the call site can match `in` parameters. ```csharp int x = 1; void M1(in T x) { // . . . } var x = M1(in x); // in argument to a method class D { public string this[in Guid index]; } D dictionary = . . . ; var y = dictionary[in Guid.Empty]; // in argument to an indexer ``` - `in` argument must be a _readable_ LValue(*). Example: `M1(in 42)` is invalid > (*) The notion of [LValue/RValue](https://en.wikipedia.org/wiki/Value_(computer_science)#lrvalue) vary between languages. Here, by LValue I mean an expression that represent a location that can be referred to directly. And RValue means an expression that yields a temporary result which does not persist on its own. - In particular it is valid to pass `readonly` fields, `in` parameters or other formally `readonly` variables as `in` arguments. Example: `dictionary[in Guid.Empty]` is legal. `Guid.Empty` is a static readonly field. - `in` argument must have type _identity-convertible_ to the type of the parameter. Example: `M1(in Guid.Empty)` is invalid. `Guid.Empty` is not _identity-convertible_ to `object` The motivation for the above rules is that `in` arguments guarantee _aliasing_ of the argument variable. The callee always receives a direct reference to the same location as represented by the argument. - in rare situations when `in` arguments must be stack-spilled due to `await` expressions used as operands of the same call, the behavior is the same as with `out` and `ref` arguments - if the variable cannot be spilled in referentially-transparent manner, an error is reported. Examples: 1. `M1(in staticField, await SomethingAsync())` is valid. `staticField` is a static field which can be accessed more than once without observable side effects. Therefore both the order of side effects and aliasing requirements can be provided. 2. `M1(in RefReturningMethod(), await SomethingAsync())` will produce an error. `RefReturningMethod()` is a `ref` returning method. A method call may have observable side effects, therefore it must be evaluated before the `SomethingAsync()` operand. However the result of the invocation is a reference that cannot be preserved across the `await` suspension point which make the direct reference requirement impossible. > NOTE: the stack spilling errors are considered to be implementation-specific limitations. Therefore they do not have effect on overload resolution or lambda inference. #### Ordinary byval arguments can match `in` parameters: Regular arguments without modifiers can match `in` parameters. In such case the arguments have the same relaxed constraints as an ordinary byval arguments would have. The motivation for this scenario is that `in` parameters in APIs may result in inconveniences for the user when arguments cannot be passed as a direct reference - ex: literals, computed or `await`-ed results or arguments that happen to have more specific types. All these cases have a trivial solution of storing the argument value in a temporary local of appropriate type and passing that local as an `in` argument. To reduce the need for such boilerplate code compiler can perform the same transformation, if needed, when `in` modifier is not present at the call site. In addition, in some cases, such as invocation of operators, or `in` extension methods, there is no syntactical way to specify `in` at all. That alone requires specifying the behavior of ordinary byval arguments when they match `in` parameters. In particular: - it is valid to pass RValues. A reference to a temporary is passed in such case. Example: ```csharp Print("hello"); // not an error. void Print(in T x) { //. . . } ``` - implicit conversions are allowed. > This is actually a special case of passing an RValue A reference to a temporary holding converted value is passed in such case. Example: ```csharp Print(Short.MaxValue) // not an error. ``` - in a case of a receiver of an `in` extension method (as opposed to `ref` extension methods), RValues or implicit _this-argument-conversions_ are allowed. A reference to a temporary holding converted value is passed in such case. Example: ```csharp public static IEnumerable Concat(in this (IEnumerable, IEnumerable) arg) => . . .; ("aa", "bb").Concat() // not an error. ``` More information on `ref`/`in` extension methods is provided further in this document. - argument spilling due to `await` operands could spill "by-value", if necessary. In scenarios where providing a direct reference to the argument is not possible due to intervening `await` a copy of the argument's value is spilled instead. Example: ```csharp M1(RefReturningMethod(), await SomethingAsync()) // not an error. ``` Since the result of a side-effecting invocation is a reference that cannot be preserved across `await` suspension, a temporary containing the actual value will be preserved instead (as it would in an ordinary byval parameter case). #### Omitted optional arguments It is permitted for an `in` parameter to specify a default value. That makes the corresponding argument optional. Omitting optional argument at the call site results in passing the default value via a temporary. ```csharp Print("hello"); // not an error, same as Print("hello", c: Color.Black); void Print(string s, in Color c = Color.Black) { // . . . } ``` ### Aliasing behavior in general Just like `ref` and `out` variables, `in` variables are references/aliases to existing locations. While callee is not allowed to write into them, reading an `in` parameter can observe different values as a side effect of other evaluations. Example: ```C# static Vector3 v = Vector3.UnitY; static void Main() { Test(v); } static void Test(in Vector3 v1) { Debug.Assert(v1 == Vector3.UnitY); // changes v1 deterministically (no races required) ChangeV(); Debug.Assert(v1 == Vector3.UnitX); } static void ChangeV() { v = Vector3.UnitX; } ``` ### `in` parameters and capturing of local variables. For the purpose of lambda/async capturing `in` parameters behave the same as `out` and `ref` parameters. - `in` parameters cannot be captured in a closure - `in` parameters are not allowed in iterator methods - `in` parameters are not allowed in async methods ### Temporary variables. Some uses of `in` parameter passing may require indirect use of a temporary local variable: - `in` arguments are always passed as direct aliases when call-site uses `in`. Temporary is never used in such case. - `in` arguments are not required to be direct aliases when call-site does not use `in`. When argument is not an LValue, a temporary may be used. - `in` parameter may have default value. When corresponding argument is omitted at the call site, the default value are passed via a temporary. - `in` arguments may have implicit conversions, including those that do not preserve identity. A temporary is used in those cases. - receivers of ordinary struct calls may not be writeable LValues (**existing case!**). A temporary is used in those cases. The life time of the argument temporaries matches the closest encompassing scope of the call-site. The formal life time of temporary variables is semantically significant in scenarios involving escape analysis of variables returned by reference. ### Metadata representation of `in` parameters. When `System.Runtime.CompilerServices.IsReadOnlyAttribute` is applied to a byref parameter, it means that the parameter is an `in` parameter. In addition, if the method is *abstract* or *virtual*, then the signature of such parameters (and only such parameters) must have `modreq[System.Runtime.InteropServices.InAttribute]`. **Motivation**: this is done to ensure that in a case of method overriding/implementing the `in` parameters match. Same requirements apply to `Invoke` methods in delegates. **Motivation**: this is to ensure that existing compilers cannot simply ignore `readonly` when creating or assigning delegates. **Warning**: this is currently not implemented. See https://github.com/dotnet/roslyn/issues/69079. ## Returning by readonly reference. ### Motivation The motivation for this sub-feature is roughly symmetrical to the reasons for the `in` parameters - avoiding copying, but on the returning side. Prior to this feature, a method or an indexer had two options: 1) return by reference and be exposed to possible mutations or 2) return by value which results in copying. ### Solution (`ref readonly` returns) The feature allows a member to return variables by reference without exposing them to mutations. ### Declaring `ref readonly` returning members A combination of modifiers `ref readonly` on the return signature is used to to indicate that the member returns a readonly reference. For all purposes a `ref readonly` member is treated as a `readonly` variable - similar to `readonly` fields and `in` parameters. For example fields of `ref readonly` member which has a struct type are all recursively classified as `readonly` variables. - It is permitted to pass them as `in` arguments, but not as `ref` or `out` arguments. ```csharp ref readonly Guid Method1() { } Method2(in Method1()); // valid. Can pass as `in` argument. Method3(ref Method1()); // not valid. Cannot pass as `ref` argument ``` - `ref readonly` returns are allowed in the same places were `ref` returns are allowed. This includes indexers, delegates, lambdas, local functions. - It is not permitted to overload on `ref`/`ref readonly` / differences. - It is permitted to overload on ordinary byval and `ref readonly` return differences. - For the purpose of OHI (Overloading, Hiding, Implementing), `ref readonly` is similar but distinct from `ref`. For example the a method that overrides `ref readonly` one, must itself be `ref readonly` and have identity-convertible type. - For the purpose of delegate/lambda/method group conversions, `ref readonly` is similar but distinct from `ref`. Lambdas and applicable method group conversion candidates have to match `ref readonly` return of the target delegate with `ref readonly` return of the type that is identity-convertible. - For the purpose of generic variance, `ref readonly` returns are nonvariant. > NOTE: There are no warnings on `ref readonly` returns that have reference or primitives types. It may be pointless in general, but in some cases user must/want to pass primitives as `in`. Examples - overriding a generic method like `ref readonly T Method()` when `T` was substituted to be `int`. > >It is conceivable to have an analyzer that warns in cases of inefficient use of `ref readonly` returns, but the rules for such analysis would be too fuzzy to be a part of a language specification. ### Returning from `ref readonly` members Inside the method body the syntax is the same as with regular ref returns. The `readonly` will be inferred from the containing method. The motivation is that `return ref readonly ` is unnecessary long and only allows for mismatches on the `readonly` part that would always result in errors. The `ref` is, however, required for consistency with other scenarios where something is passed via strict aliasing vs. by value. > Unlike the case with `in` parameters, `ref readonly` returns never return via a local copy. Considering that the copy would cease to exist immediately upon returning such practice would be pointless and dangerous. Therefore `ref readonly` returns are always direct references. Example: ```csharp struct ImmutableArray { private readonly T[] array; public ref readonly T ItemRef(int i) { // returning a readonly reference to an array element return ref this.array[i]; } } ``` - An argument of `return ref` must be an LValue (**existing rule**) - An argument of `return ref` must be "safe to return" (**existing rule**) - In a `ref readonly` member an argument of `return ref` is _not required to be writeable_ . For example such member can ref-return a readonly field or one of its `in` parameters. ### Safe to Return rules. Normal safe to return rules for references will apply to readonly references as well. Note that a `ref readonly` can be obtained from a regular `ref` local/parameter/return, but not the other way around. Otherwise the safety of `ref readonly` returns is inferred the same way as for regular `ref` returns. Considering that RValues can be passed as `in` parameter and returned as `ref readonly` we need one more rule - **RValues are not safe-to-return by reference**. > Consider the situation when an RValue is passed to an `in` parameter via a copy and then returned back in a form of a `ref readonly`. In the context of the caller the result of such invocation is a reference to local data and as such is unsafe to return. > Once RValues are not safe to return, the existing rule `#6` already handles this case. Example: ```csharp ref readonly Vector3 Test1() { // can pass an RValue as "in" (via a temp copy) // but the result is not safe to return // because the RValue argument was not safe to return by reference return ref Test2(default(Vector3)); } ref readonly Vector3 Test2(in Vector3 r) { // this is ok, r is returnable return ref r; } ``` Updated `safe to return` rules: 1. **refs to variables on the heap are safe to return** 2. **ref/in parameters are safe to return** `in` parameters naturally can only be returned as readonly. 3. **out parameters are safe to return** (but must be definitely assigned, as is already the case today) 4. **instance struct fields are safe to return as long as the receiver is safe to return** 5. **'this' is not safe to return from struct members** 6. **a ref, returned from another method is safe to return if all refs/outs passed to that method as formal parameters were safe to return.** *Specifically it is irrelevant if receiver is safe to return, regardless whether receiver is a struct, class or typed as a generic type parameter.* 7. **RValues are not safe to return by reference.** *Specifically RValues are safe to pass as in parameters.* > NOTE: There are additional rules regarding safety of returns that come into play when ref-like types and ref-reassignments are involved. > The rules equally apply to `ref` and `ref readonly` members and therefore are not mentioned here. ### Aliasing behavior. `ref readonly` members provide the same aliasing behavior as ordinary `ref` members (except for being readonly). Therefore for the purpose of capturing in lambdas, async, iterators, stack spilling etc... the same restrictions apply. - I.E. due to inability to capture the actual references and due to side-effecting nature of member evaluation such scenarios are disallowed. > It is permitted and required to make a copy when `ref readonly` return is a receiver of regular struct methods, which take `this` as an ordinary writeable reference. Historically in all cases where such invocations are applied to readonly variable a local copy is made. ### Metadata representation. When `System.Runtime.CompilerServices.IsReadOnlyAttribute` is applied to the return of a byref returning method, it means that the method returns a readonly reference. In addition, the result signature of such methods (and only those methods) must have `modreq[System.Runtime.InteropServices.InAttribute]`. **Motivation**: this is to ensure that existing compilers cannot simply ignore `readonly` when invoking methods with `ref readonly` returns ## Readonly structs In short - a feature that makes `this` parameter of all instance members of a struct, except for constructors, an `in` parameter. ### Motivation Compiler must assume that any method call on a struct instance may modify the instance. Indeed a writeable reference is passed to the method as `this` parameter and fully enables this behavior. To allow such invocations on `readonly` variables, the invocations are applied to temp copies. That could be unintuitive and sometimes forces people to abandon `readonly` for performance reasons. Example: https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/ After adding support for `in` parameters and `ref readonly` returns the problem of defensive copying will get worse since readonly variables will become more common. ### Solution Allow `readonly` modifier on struct declarations which would result in `this` being treated as `in` parameter on all struct instance methods except for constructors. ```csharp static void Test(in Vector3 v1) { // no need to make a copy of v1 since Vector3 is a readonly struct System.Console.WriteLine(v1.ToString()); } readonly struct Vector3 { . . . public override string ToString() { // not OK!! `this` is an `in` parameter foo(ref this.X); // OK return $"X: {X}, Y: {Y}, Z: {Z}"; } } ``` ### Restrictions on members of readonly struct - Instance fields of a readonly struct must be readonly. **Motivation:** can only be written to externally, but not through members. - Instance autoproperties of a readonly struct must be get-only. **Motivation:** consequence of restriction on instance fields. - Readonly struct may not declare field-like events. **Motivation:** consequence of restriction on instance fields. ### Metadata representation. When `System.Runtime.CompilerServices.IsReadOnlyAttribute` is applied to a value type, it means that the type is a `readonly struct`. In particular: - The identity of the `IsReadOnlyAttribute` type is unimportant. In fact it can be embedded by the compiler in the containing assembly if needed. ## `ref`/`in` extension methods There is actually an existing proposal (https://github.com/dotnet/roslyn/issues/165) and corresponding prototype PR (https://github.com/dotnet/roslyn/pull/15650). I just want to acknowledge that this idea is not entirely new. It is, however, relevant here since `ref readonly` elegantly removes the most contentious issue about such methods - what to do with RValue receivers. The general idea is allowing extension methods to take the `this` parameter by reference, as long as the type is known to be a struct type. ```csharp public static void Extension(ref this Guid self) { // do something } ``` The reasons for writing such extension methods are primarily: 1. Avoid copying when receiver is a large struct 2. Allow mutating extension methods on structs The reasons why we do not want to allow this on classes 1. It would be of very limited purpose. 2. It would break long standing invariant that a method call cannot turn non-`null` receiver to become `null` after invocation. > In fact, currently a non-`null` variable cannot become `null` unless _explicitly_ assigned or passed by `ref` or `out`. > That greatly aids readability or other forms of "can this be a null here" analysis. 3. It would be hard to reconcile with "evaluate once" semantics of null-conditional accesses. Example: `obj.stringField?.RefExtension(...)` - need to capture a copy of `stringField` to make the null check meaningful, but then assignments to `this` inside RefExtension would not be reflected back to the field. An ability to declare extension methods on **structs** that take the first argument by reference was a long-standing request. One of the blocking consideration was "what happens if receiver is not an LValue?". - There is a precedent that any extension method could also be called as a static method (sometimes it is the only way to resolve ambiguity). It would dictate that RValue receivers should be disallowed. - On the other hand there is a practice of making invocation on a copy in similar situations when struct instance methods are involved. The reason why the "implicit copying" exists is because the majority of struct methods do not actually modify the struct while not being able to indicate that. Therefore the most practical solution was to just make the invocation on a copy, but this practice is known for harming performance and causing bugs. Now, with availability of `in` parameters, it is possible for an extension to signal the intent. Therefore the conundrum can be resolved by requiring `ref` extensions to be called with writeable receivers while `in` extensions permit implicit copying if necessary. ```csharp // this can be called on either RValue or an LValue public static void Reader(in this Guid self) { // do something nonmutating. WriteLine(self == default(Guid)); } // this can be called only on an LValue public static void Mutator(ref this Guid self) { // can mutate self self = new Guid(); } ``` ### `in` extensions and generics. The purpose of `ref` extension methods is to mutate the receiver directly or by invoking mutating members. Therefore `ref this T` extensions are allowed as long as `T` is constrained to be a struct. On the other hand `in` extension methods exist specifically to reduce implicit copying. However any use of an `in T` parameter will have to be done through an interface member. Since all interface members are considered mutating, any such use would require a copy. - Instead of reducing copying, the effect would be the opposite. Therefore `in this T` is not allowed when `T` is a generic type parameter regardless of constraints. ### Valid kinds of extension methods (recap): The following forms of `this` declaration in an extension method are now allowed: 1) `this T arg` - regular byval extension. (**existing case**) - T can be any type, including reference types or type parameters. Instance will be the same variable after the call. Allows implicit conversions of _this-argument-conversion_ kind. Can be called on RValues. - `in this T self` - `in` extension. T must be an actual struct type. Instance will be the same variable after the call. Allows implicit conversions of _this-argument-conversion_ kind. Can be called on RValues (may be invoked on a temp if needed). - `ref this T self` - `ref` extension. T must be a struct type or a generic type parameter constrained to be a struct. Instance may be written to by the invocation. Allows only identity conversions. Must be called on writeable LValue. (never invoked via a temp). ## Readonly ref locals. ### Motivation. Once `ref readonly` members were introduced, it was clear from the use that they need to be paired with appropriate kind of local. Evaluation of a member may produce or observe side effects, therefore if the result must be used more than once, it needs to be stored. Ordinary `ref` locals do not help here since they cannot be assigned a `readonly` reference. ### Solution. Allow declaring `ref readonly` locals. This is a new kind of `ref` locals that is not writeable. As a result `ref readonly` locals can accept references to readonly variables without exposing these variables to writes. ### Declaring and using `ref readonly` locals. The syntax of such locals uses `ref readonly` modifiers at declaration site (in that specific order). Similarly to ordinary `ref` locals, `ref readonly` locals must be ref-initialized at declaration. Unlike regular `ref` locals, `ref readonly` locals can refer to `readonly` LValues like `in` parameters, `readonly` fields, `ref readonly` methods. For all purposes a `ref readonly` local is treated as a `readonly` variable. Most of the restrictions on the use are the same as with `readonly` fields or `in` parameters. For example fields of an `in` parameter which has a struct type are all recursively classified as `readonly` variables . ```csharp static readonly ref Vector3 M1() => . . . static readonly ref Vector3 M1_Trace() { // OK ref readonly var r1 = ref M1(); // Not valid. Need an LValue ref readonly Vector3 r2 = ref default(Vector3); // Not valid. r1 is readonly. Mutate(ref r1); // OK. Print(in r1); // OK. return ref r1; } ``` ### Restrictions on use of `ref readonly` locals Except for their `readonly` nature, `ref readonly` locals behave like ordinary `ref` locals and are subject to exactly same restrictions. For example restrictions related to capturing in closures, declaring in `async` methods or the `safe-to-return` analysis equally applies to `ref readonly` locals. ## Ternary `ref` expressions. (aka "Conditional LValues") ### Motivation Use of `ref` and `ref readonly` locals exposed a need to ref-initialize such locals with one or another target variable based on a condition. A typical workaround is to introduce a method like: ```csharp ref T Choice(bool condition, ref T consequence, ref T alternative) { if (condition) { return ref consequence; } else { return ref alternative; } } ``` Note that `Choice` is not an exact replacement of a ternary since _all_ arguments must be evaluated at the call site, which was leading to unintuitive behavior and bugs. The following will not work as expected: ```csharp // will crash with NRE because 'arr[0]' will be executed unconditionally ref var r = ref Choice(arr != null, ref arr[0], ref otherArr[0]); ``` ### Solution Allow special kind of conditional expression that evaluates to a reference to one of LValue argument based on a condition. ### Using `ref` ternary expression. The syntax for the `ref` flavor of a conditional expression is ` ? ref : ref ;` Just like with the ordinary conditional expression only `` or `` is evaluated depending on result of the boolean condition expression. Unlike ordinary conditional expression, `ref` conditional expression: - requires that `` and `` are LValues. - `ref` conditional expression itself is an LValue and - `ref` conditional expression is writeable if both `` and `` are writeable LValues Examples: `ref` ternary is an LValue and as such it can be passed/assigned/returned by reference; ```csharp // pass by reference foo(ref (arr != null ? ref arr[0]: ref otherArr[0])); // return by reference return ref (arr != null ? ref arr[0]: ref otherArr[0]); ``` Being an LValue, it can also be assigned to. ```csharp // assign to (arr != null ? ref arr[0]: ref otherArr[0]) = 1; // error. readOnlyField is readonly and thus conditional expression is readonly (arr != null ? ref arr[0]: ref obj.readOnlyField) = 1; ``` Can be used as a receiver of a method call and skip copying if necessary. ```csharp // no copies (arr != null ? ref arr[0]: ref otherArr[0]).StructMethod(); // invoked on a copy. // The receiver is `readonly` because readOnlyField is readonly. (arr != null ? ref arr[0]: ref obj.readOnlyField).StructMethod(); // no copies. `ReadonlyStructMethod` is a method on a `readonly` struct // and can be invoked directly on a readonly receiver (arr != null ? ref arr[0]: ref obj.readOnlyField).ReadonlyStructMethod(); ``` `ref` ternary can be used in a regular (not ref) context as well. ```csharp // only an example // a regular ternary could work here just the same int x = (arr != null ? ref arr[0]: ref otherArr[0]); ``` ### Drawbacks [drawbacks]: #drawbacks I can see two major arguments against enhanced support for references and readonly references: 1) The problems that are solved here are very old. Why suddenly solve them now, especially since it would not help existing code? As we find C# and .Net used in new domains, some problems become more prominent. As examples of environments that are more critical than average about computation overheads, I can list * cloud/datacenter scenarios where computation is billed for and responsiveness is a competitive advantage. * Games/VR/AR with soft-realtime requirements on latencies This feature does not sacrifice any of the existing strengths such as type-safety, while allowing to lower overheads in some common scenarios. 2) Can we reasonably guarantee that the callee will play by the rules when it opts into `readonly` contracts? We have similar trust when using `out`. Incorrect implementation of `out` can cause unspecified behavior, but in reality it rarely happens. Making the formal verification rules familiar with `ref readonly` would further mitigate the trust issue. ### Alternatives [alternatives]: #alternatives The main competing design is really "do nothing". ### Unresolved questions [unresolved]: #unresolved-questions ### Design meetings https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-02-22.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-03-01.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-08-28.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-09-25.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-09-27.md ================================================ FILE: proposals/csharp-7.2/readonly-struct.md ================================================ ## Readonly structs In C# 7.2, we added a feature permitting a struct declaration to have the `readonly` modifier. This is a placeholder for that feature's specification. ================================================ FILE: proposals/csharp-7.2/ref-extension-methods.md ================================================ ## Ref Extension Methods Champion issue: In C# 7.2, we added support for *ref extension methods*. This is a placeholder for the feature's specification. ================================================ FILE: proposals/csharp-7.2/ref-struct-and-span.md ================================================ ## Ref Structs and Span In C# 7.2 we added support for *ref struct* types. This is a placeholder for the specification. ================================================ FILE: proposals/csharp-7.2/span-safety.md ================================================ # Compile time enforcement of safety for ref-like types ## Introduction The main reason for the additional safety rules when dealing with types like `Span` and `ReadOnlySpan` is that such types must be confined to the execution stack. There are two reasons why `Span` and similar types must be a stack-only types. 1. `Span` is semantically a struct containing a reference and a range - `(ref T data, int length)`. Regardless of actual implementation, writes to such struct would not be atomic. Concurrent "tearing" of such struct would lead to the possibility of `length` not matching the `data`, causing out-of-range accesses and type-safety violations, which ultimately could result in GC heap corruption in seemingly "safe" code. 2. Some implementations of `Span` literally contain a managed pointer in one of its fields. Managed pointers are not supported as fields of heap objects and code that manages to put a managed pointer on the GC heap typically crashes at JIT time. All the above problems would be alleviated if instances of `Span` are constrained to exist only on the execution stack. An additional problem arises due to composition. It would be generally desirable to build more complex data types that would embed `Span` and `ReadOnlySpan` instances. Such composite types would have to be structs and would share all the hazards and requirements of `Span`. As a result the safety rules described here should be viewed as applicable to the whole range of **_ref-like types_**. The [draft language specification](#draft-language-specification) is intended to ensure that values of a ref-like type occur only on the stack. ## Generalized `ref-like` types in source code `ref-like` structs are explicitly marked in the source code using `ref` modifier: ```csharp ref struct TwoSpans { // can have ref-like instance fields public Span first; public Span second; } // error: arrays of ref-like types are not allowed. TwoSpans[] arr = null; ``` Designating a struct as ref-like will allow the struct to have ref-like instance fields and will also make all the requirements of ref-like types applicable to the struct. ## Metadata representation of ref-like structs Ref-like structs will be marked with **System.Runtime.CompilerServices.IsRefLikeAttribute** attribute. The attribute will be added to common base libraries such as `mscorlib`. In a case if the attribute is not available, compiler will generate an internal one similarly to other embedded-on-demand attributes such as `IsReadOnlyAttribute`. An additional measure will be taken to prevent the use of ref-like structs in compilers not familiar with the safety rules (this includes C# compilers prior to the one in which this feature is implemented). Having no other good alternatives that work in old compilers without servicing, an `Obsolete` attribute with a known string will be added to all ref-like structs. Compilers that know how to use ref-like types will ignore this particular form of `Obsolete`. A typical metadata representation: ```csharp [IsRefLike] [Obsolete("Types with embedded references are not supported in this version of your compiler.")] public struct TwoSpans { // . . . . } ``` NOTE: it is not the goal to make it so that any use of ref-like types on old compilers fails 100%. That is hard to achieve and is not strictly necessary. For example there would always be a way to get around the `Obsolete` using dynamic code or, for example, creating an array of ref-like types through reflection. In particular, if user wants to actually put an `Obsolete` or `Deprecated` attribute on a ref-like type, we will have no choice other than not emitting the predefined one since `Obsolete` attribute cannot be applied more than once.. ## Examples: ```csharp SpanLikeType M1(ref SpanLikeType x, Span y) { // this is all valid, unconcerned with stack-referring stuff var local = new SpanLikeType(y); x = local; return x; } void Test1(ref SpanLikeType param1, Span param2) { Span stackReferring1 = stackalloc byte[10]; var stackReferring2 = new SpanLikeType(stackReferring1); // this is allowed stackReferring2 = M1(ref stackReferring2, stackReferring1); // this is NOT allowed stackReferring2 = M1(ref param1, stackReferring1); // this is NOT allowed param1 = M1(ref stackReferring2, stackReferring1); // this is NOT allowed param2 = stackReferring1.Slice(10); // this is allowed param1 = new SpanLikeType(param2); // this is allowed stackReferring2 = param1; } ref SpanLikeType M2(ref SpanLikeType x) { return ref x; } ref SpanLikeType Test2(ref SpanLikeType param1, Span param2) { Span stackReferring1 = stackalloc byte[10]; var stackReferring2 = new SpanLikeType(stackReferring1); ref var stackReferring3 = M2(ref stackReferring2); // this is allowed stackReferring3 = M1(ref stackReferring2, stackReferring1); // this is allowed M2(ref stackReferring3) = stackReferring2; // this is NOT allowed M1(ref param1) = stackReferring2; // this is NOT allowed param1 = stackReferring3; // this is NOT allowed return ref stackReferring3; // this is allowed return ref param1; } ``` ---------------- ## Draft language specification Below we describe a set of safety rules for ref-like types (`ref struct`s) to ensure that values of these types occur only on the stack. A different, simpler set of safety rules would be possible if locals cannot be passed by reference. This specification would also permit the safe reassignment of ref locals. ### Overview We associate with each expression at compile-time the concept of what scope that expression is permitted to escape to, "safe-to-escape". Similarly, for each lvalue we maintain a concept of what scope a reference to it is permitted to escape to, "ref-safe-to-escape". For a given lvalue expression, these may be different. These are analogous to the "safe to return" of the ref locals feature, but it is more fine-grained. Where the "safe-to-return" of an expression records only whether (or not) it may escape the enclosing method as a whole, the safe-to-escape records which scope it may escape to (which scope it may not escape beyond). The basic safety mechanism is enforced as follows. Given an assignment from an expression E1 with a safe-to-escape scope S1, to an (lvalue) expression E2 with safe-to-escape scope S2, it is an error if S2 is a wider scope than S1. By construction, the two scopes S1 and S2 are in a nesting relationship, because a legal expression is always safe-to-return from some scope enclosing the expression. For the time being it is sufficient, for the purpose of the analysis, to support just two scopes - external to the method, and top-level scope of the method. That is because ref-like values with inner scopes cannot be created and ref locals do not support re-assignment. The rules, however, can support more than two scope levels. The precise rules for computing the *safe-to-return* status of an expression, and the rules governing the legality of expressions, follow. ### ref-safe-to-escape The *ref-safe-to-escape* is a scope, enclosing an lvalue expression, to which it is safe for a ref to the lvalue to escape to. If that scope is the entire method, we say that a ref to the lvalue is *safe to return* from the method. The *ref-safe-to-escape* scope for an lvalue expression can never be to a greater scope than the *safe-to-escape* for the same value. That means when the spec limits the *safe-to-escape* of a value it is implicitly also limiting the *ref-safe-to-escape*. However *ref-safe-to-escape* scope can be to a smaller scope than *safe-to-escape*. Consider that non-ref locals have *safe-to-escape* scope outside method but *ref-safe-to-escape* inside the method. ### safe-to-escape The *safe-to-escape* is a scope, enclosing an expression, to which it is safe for the value to escape to. If that scope is the entire method, we say that the value is *safe to return* from the method. An expression whose type is not a `ref struct` type is always *safe-to-return* from the entire enclosing method. Otherwise we refer to the rules below. #### Parameters An lvalue designating a formal parameter is *ref-safe-to-escape* (by reference) as follows: - If the parameter is a `ref`, `out`, or `in` parameter, it is *ref-safe-to-escape* from the entire method (e.g. by a `return ref` statement); otherwise - If the parameter is the `this` parameter of a struct type, it is *ref-safe-to-escape* to the top-level scope of the method (but not from the entire method itself); [Sample](#struct-this-escape) - Otherwise the parameter is a value parameter, and it is *ref-safe-to-escape* to the top-level scope of the method (but not from the method itself). An expression that is an rvalue designating the use of a formal parameter is *safe-to-escape* (by value) from the entire method (e.g. by a `return` statement). This applies to the `this` parameter as well. #### Locals An lvalue designating a local variable is *ref-safe-to-escape* (by reference) as follows: - If the variable is a `ref` variable, then its *ref-safe-to-escape* is taken from the *ref-safe-to-escape* of its initializing expression; otherwise - The variable is *ref-safe-to-escape* the scope in which it was declared. An expression that is an rvalue designating the use of a local variable is *safe-to-escape* (by value) as follows: - But the general rule above, a local whose type is not a `ref struct` type is *safe-to-return* from the entire enclosing method. - If the variable is an iteration variable of a `foreach` loop, then the variable's *safe-to-escape* scope is the same as the *safe-to-escape* of the `foreach` loop's expression. - A local of `ref struct` type and uninitialized at the point of declaration is *safe-to-return* from the entire enclosing method. - Otherwise the variable's type is a `ref struct` type, and the variable's declaration requires an initializer. The variable's *safe-to-escape* scope is the same as the *safe-to-escape* of its initializer. #### Field reference An lvalue designating a reference to a field, `e.F`, is *ref-safe-to-escape* (by reference) as follows: - If `e` is of a reference type, it is *ref-safe-to-escape* from the entire method; otherwise - If `e` is of a value type, its *ref-safe-to-escape* is taken from the *ref-safe-to-escape* of `e`. An rvalue designating a reference to a field, `e.F`, has a *safe-to-escape* scope that is the same as the *safe-to-escape* of `e`. #### Operators including `?:` The application of a user-defined operator is treated as a method invocation. For an operator that yields an rvalue, such as `e1 + e2` or `c ? e1 : e2`, the *safe-to-escape* of the result is the narrowest scope among the *safe-to-escape* of the operands of the operator. As a consequence, for a unary operator that yields an rvalue, such as `+e`, the *safe-to-escape* of the result is the *safe-to-escape* of the operand. For an operator that yields an lvalue, such as `c ? ref e1 : ref e2` - the *ref-safe-to-escape* of the result is the narrowest scope among the *ref-safe-to-escape* of the operands of the operator. - the *safe-to-escape* of the operands must agree, and that is the *safe-to-escape* of the resulting lvalue. #### Method invocation An lvalue resulting from a ref-returning method invocation `e1.M(e2, ...)` is *ref-safe-to-escape* the smallest of the following scopes: - The entire enclosing method - the *ref-safe-to-escape* of all `ref` and `out` argument expressions (excluding the receiver) - For each `in` parameter of the method, if there is a corresponding expression that is an lvalue, its *ref-safe-to-escape*, otherwise the nearest enclosing scope - the *safe-to-escape* of all argument expressions (including the receiver) > Note: the last bullet is necessary to handle code such as > ```csharp > var sp = new Span(...) > return ref sp[0]; > ``` > or > ```csharp > return ref M(sp, 0); > ``` An rvalue resulting from a method invocation `e1.M(e2, ...)` is *safe-to-escape* from the smallest of the following scopes: - The entire enclosing method - the *safe-to-escape* of all argument expressions (including the receiver) #### An Rvalue An rvalue is *ref-safe-to-escape* from the nearest enclosing scope. This occurs for example in an invocation such as `M(ref d.Length)` where `d` is of type `dynamic`. It is also consistent with (and perhaps subsumes) our handling of arguments corresponding to `in` parameters. #### Property invocations A property invocation (either `get` or `set`) it treated as a method invocation of the underlying method by the above rules. #### `stackalloc` A stackalloc expression is an rvalue that is *safe-to-escape* to the top-level scope of the method (but not from the entire method itself). #### Constructor invocations A `new` expression that invokes a constructor obeys the same rules as a method invocation that is considered to return the type being constructed. In addition *safe-to-escape* is no wider than the smallest of the *safe-to-escape* of all arguments/operands of the object initializer expressions, recursively, if initializer is present. #### Span constructor The language relies on `Span` not having a constructor of the following form: ```csharp void Example(ref int x) { // Create a span of length one var span = new Span(ref x); } ``` Such a constructor makes `Span` which are used as fields indistinguishable from a `ref` field. The safety rules described in this document depend on `ref` fields not being a valid construct in C# or .NET. #### `default` expressions A `default` expression is *safe-to-escape* from the entire enclosing method. ## Language Constraints We wish to ensure that no `ref` local variable, and no variable of `ref struct` type, refers to stack memory or variables that are no longer alive. We therefore have the following language constraints: - Neither a ref parameter, nor a ref local, nor a parameter or local of a `ref struct` type can be lifted into a lambda or local function. - Neither a ref parameter nor a parameter of a `ref struct` type may be an argument on an iterator method or an `async` method. - Neither a ref local, nor a local of a `ref struct` type may be in scope at the point of a `yield return` statement or an `await` expression. - A `ref struct` type may not be used as a type argument, or as an element type in a tuple type. - A `ref struct` type may not be the declared type of a field, except that it may be the declared type of an instance field of another `ref struct`. - A `ref struct` type may not be the element type of an array. - A value of a `ref struct` type may not be boxed: - There is no conversion from a `ref struct` type to the type `object` or the type `System.ValueType`. - A `ref struct` type may not be declared to implement any interface - No instance method declared in `object` or in `System.ValueType` but not overridden in a `ref struct` type may be called with a receiver of that `ref struct` type. - No instance method of a `ref struct` type may be captured by method conversion to a delegate type. - For a ref reassignment `e1 = ref e2`, the *ref-safe-to-escape* of `e2` must be at least as wide a scope as the *ref-safe-to-escape* of `e1`. - For a ref return statement `return ref e1`, the *ref-safe-to-escape* of `e1` must be *ref-safe-to-escape* from the entire method. (TODO: Do we also need a rule that `e1` must be *safe-to-escape* from the entire method, or is that redundant?) - For a return statement `return e1`, the *safe-to-escape* of `e1` must be *safe-to-escape* from the entire method. - For an assignment `e1 = e2`, if the type of `e1` is a `ref struct` type, then the *safe-to-escape* of `e2` must be at least as wide a scope as the *safe-to-escape* of `e1`. - For a method invocation if there is a `ref` or `out` argument of a `ref struct` type (including the receiver unless the type is `readonly`), with *safe-to-escape* E1, then no argument (including the receiver) may have a narrower *safe-to-escape* than E1. [Sample](#method-arguments-must-match) - A local function or anonymous function may not refer to a local or parameter of `ref struct` type declared in an enclosing scope. > ***Open Issue:*** We need some rule that permits us to produce an error when needing to spill a stack value of a `ref struct` type at an await expression, for example in the code > ```csharp > Foo(new Span(...), await e2); > ``` ## Explanations These explanations and samples help explain why many of the safety rules above exist ### Method Arguments Must Match When invoking a method where there is an `out` or `ref` parameter that is a `ref struct` then all of the `ref struct` parameters need to have the same lifetime. This is necessary because C# must make all of its decisions around lifetime safety based on the information available in the signature of the method and the lifetime of the values at the call site. When there are `ref` parameters that are `ref struct` then there is the potential that they could swap around their contents. Hence at the call site we must ensure all of these **potential** swaps are compatible. If the language didn't enforce that then it will allow for bad code like the following. ```csharp void M1(ref Span s1) { Span s2 = stackalloc int[1]; Swap(ref s1, ref s2); } void Swap(ref Span x, ref Span y) { // This will effectively assign the stackalloc to the s1 parameter and allow it // to escape to the caller of M1 ref x = ref y; } ``` This analysis of `ref` parameters includes the receiver in instance methods. This is necessary because it can be used to store values passed in as parameters, just as a `ref` parameter could`. This means with mismatched lifetimes you could create a type safety hole in the following way: ```csharp ref struct S { public Span Span; public void Set(Span span) { Span = span; } } void Broken(ref S s) { Span span = stackalloc int[1]; // The result of a stackalloc is now stored in s.Span and escaped to the caller // of Broken s.Set(span); } ``` For the purpose of this analysis the receiver is considered an `in`, not a `ref`, if the type is a `readonly struct`. In that case the receiver cannot be used to store values from other parameters, it is effectively an `in` parameter for analysis purposes. Hence the same example above is legal when `S` is `readonly` because the `span` cannot be stored anywhere. ### Struct This Escape When it comes to span safety rules, the `this` value in an instance member is modeled as a parameter to the member. Now for a `struct` the type of `this` is actually `ref S` where in a `class` it's simply `S` (for members of a `class / struct` named S). Yet `this` has different escaping rules than other `ref` parameters. Specifically it is not ref-safe-to-escape while other parameters are: ```csharp ref struct S { int Field; // Illegal because `this` isn't safe to escape as ref ref int Get() => ref Field; // Legal ref int GetParam(ref int p) => ref p; } ``` The reason for this restriction actually has little to do with `struct` member invocation. There are some rules that need to be worked out with respect to member invocation on `struct` members where the receiver is an rvalue. But that is very approachable. The reason for this restriction is actually about interface invocation. Specifically it comes down to whether or not the following sample should or should not compile; ```csharp interface I1 { ref int Get(); } ref int Use(T p) where T : I1 { return ref p.Get(); } ``` Consider the case where `T` is instantiated as a `struct`. If the `this` parameter is ref-safe-to-escape then the return of `p.Get` could point to the stack (specifically it could be a field inside of the instantiated type of `T`). That means the language could not allow this sample to compile as it could be returning a `ref` to a stack location. On the other hand if `this` is not ref-safe-to-escape then `p.Get` cannot refer to the stack and hence it's safe to return. This is why the escapability of `this` in a `struct` is really all about interfaces. It can absolutely be made to work but it has a trade off. The design eventually came down in favor of making interfaces more flexible. There is potential for us to relax this in the future though. ## Future Considerations ### Length one Span\ over ref values Though not legal today there are cases where creating a length one `Span` instance over a value would be beneficial: ```csharp void RefExample() { int x = ...; // Today creating a length one Span requires a stackalloc and a new // local Span span1 = stackalloc [] { x }; Use(span1); x = span1[0]; // Simpler to just allow length one span var span2 = new Span(ref x); Use(span2); } ``` This feature gets more compelling if we lift the restrictions on [fixed sized buffers](https://github.com/dotnet/csharplang/blob/master/proposals/fixed-sized-buffers.md) as it would allow for `Span` instances of even greater length. If there is ever a need to go down this path then the language could accommodate this by ensuring such `Span` instances were downward facing only. That is they were only ever *safe-to-escape* to the scope in which they were created. This ensure the language never had to consider a `ref` value escaping a method via a `ref struct` return or field of `ref struct`. This would likely also require further changes to recognize such constructors as capturing a `ref` parameter in this way though. ================================================ FILE: proposals/csharp-7.3/auto-prop-field-attrs.md ================================================ # Auto-Implemented Property Field-Targeted Attributes Champion issu: https://github.com/dotnet/csharplang/issues/42 ## Summary [summary]: #summary This feature intends to allow developers to apply attributes directly to the backing fields of auto-implemented properties. ## Motivation [motivation]: #motivation Currently it is not possible to apply attributes to the backing fields of auto-implemented properties. In those cases where the developer must use a field-targeting attribute they are forced to declare the field manually and use the more verbose property syntax. Given that C# has always supported field-targeted attributes on the generated backing field for events it makes sense to extend the same functionality to their property kin. ## Detailed design [design]: #detailed-design In short, the following would be legal C# and not produce a warning: ```csharp [Serializable] public class Foo { [field: NonSerialized] public string MySecret { get; set; } } ``` This would result in the field-targeted attributes being applied to the compiler-generated backing field: ```csharp [Serializable] public class Foo { [NonSerialized] private string _mySecretBackingField; public string MySecret { get { return _mySecretBackingField; } set { _mySecretBackingField = value; } } } ``` As mentioned, this brings parity with event syntax from C# 1.0 as the following is already legal and behaves as expected: ```csharp [Serializable] public class Foo { [field: NonSerialized] public event EventHandler MyEvent; } ``` ## Drawbacks [drawbacks]: #drawbacks There are two potential drawbacks to implementing this change: 1. Attempting to apply an attribute to the field of an auto-implemented property produces a compiler warning that the attributes in that block will be ignored. If the compiler were changed to support those attributes they would be applied to the backing field on a subsequent recompilation which could alter the behavior of the program at runtime. 1. The compiler does not currently validate the AttributeUsage targets of the attributes when attempting to apply them to the field of the auto-implemented property. If the compiler were changed to support field-targeted attributes and the attribute in question cannot be applied to a field the compiler would emit an error instead of a warning, breaking the build. ## Alternatives [alternatives]: #alternatives ## Unresolved questions [unresolved]: #unresolved-questions ## Design meetings ================================================ FILE: proposals/csharp-7.3/blittable.md ================================================ # Unmanaged type constraint Champion issue: ## Summary [summary]: #summary The unmanaged constraint feature will give language enforcement to the class of types known as "unmanaged types" in the C# language spec. This is defined in section 18.2 as a type which is not a reference type and doesn't contain reference type fields at any level of nesting. ## Motivation [motivation]: #motivation The primary motivation is to make it easier to author low level interop code in C#. Unmanaged types are one of the core building blocks for interop code, yet the lack of support in generics makes it impossible to create re-usable routines across all unmanaged types. Instead developers are forced to author the same boiler plate code for every unmanaged type in their library: ```csharp int Hash(Point point) { ... } int Hash(TimeSpan timeSpan) { ... } ``` To enable this type of scenario the language will be introducing a new constraint: unmanaged: ```csharp void Hash(T value) where T : unmanaged { ... } ``` This constraint can only be met by types which fit into the unmanaged type definition in the C# language spec. Another way of looking at it is that a type satisfies the unmanaged constraint if it can also be used as a pointer. ```csharp Hash(new Point()); // Okay Hash(42); // Okay Hash("hello") // Error: Type string does not satisfy the unmanaged constraint ``` Type parameters with the unmanaged constraint can use all the features available to unmanaged types: pointers, fixed, etc ... ```csharp void Hash(T value) where T : unmanaged { // Okay fixed (T* p = &value) { ... } } ``` This constraint will also make it possible to have efficient conversions between structured data and streams of bytes. This is an operation that is common in networking stacks and serialization layers: ```csharp Span Convert(ref T value) where T : unmanaged { ... } ``` Such routines are advantageous because they are provably safe at compile time and allocation free. Interop authors today can not do this (even though it's at a layer where perf is critical). Instead they need to rely on allocating routines that have expensive runtime checks to verify values are correctly unmanaged. ## Detailed design [design]: #detailed-design The language will introduce a new constraint named `unmanaged`. In order to satisfy this constraint a type must be a struct and all the fields of the type must fall into one of the following categories: - Have the type `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `bool`, `IntPtr` or `UIntPtr`. - Be any `enum` type. - Be a pointer type. - Be a user defined struct that satisfies the `unmanaged` constraint. Compiler generated instance fields, such as those backing auto-implemented properties, must also meet these constraints. For example: ```csharp // Unmanaged type struct Point { int X; int Y {get; set;} } // Not an unmanaged type struct Student { string FirstName; string LastName; } ``` The `unmanaged` constraint cannot be combined with `struct`, `class` or `new()`. This restriction derives from the fact that `unmanaged` implies `struct` hence the other constraints do not make sense. The `unmanaged` constraint is not enforced by CLR, only by the language. To prevent mis-use by other languages, methods which have this constraint will be protected by a mod-req. This will prevent other languages from using type arguments which are not unmanaged types. The token `unmanaged` in the constraint is not a keyword, nor a contextual keyword. Instead it is like `var` in that it is evaluated at that location and will either: - Bind to user defined or referenced type named `unmanaged`: This will be treated just as any other named type constraint is treated. - Bind to no type: This will be interpreted as the `unmanaged` constraint. In the case there is a type named `unmanaged` and it is available without qualification in the current context, then there will be no way to use the `unmanaged` constraint. This parallels the rules surrounding the feature `var` and user defined types of the same name. ## Drawbacks [drawbacks]: #drawbacks The primary drawback of this feature is that it serves a small number of developers: typically low level library authors or frameworks. Hence it's spending precious language time for a small number of developers. Yet these frameworks are often the basis for the majority of .NET applications out there. Hence performance / correctness wins at this level can have a ripple effect on the .NET ecosystem. This makes the feature worth considering even with the limited audience. ## Alternatives [alternatives]: #alternatives There are a couple of alternatives to consider: - The status quo: The feature is not justified on its own merits and developers continue to use the implicit opt in behavior. ## Questions [quesions]: #questions ### Metadata Representation The F# language encodes the constraint in the signature file which means C# cannot re-use their representation. A new attribute will need to be chosen for this constraint. Additionally a method which has this constraint must be protected by a mod-req. ### Blittable vs. Unmanaged The F# language has a very [similar feature](https://docs.microsoft.com/dotnet/articles/fsharp/language-reference/generics/constraints) which uses the keyword unmanaged. The blittable name comes from the use in Midori. May want to look to precedence here and use unmanaged instead. **Resolution** The language decide to use unmanaged ### Verifier Does the verifier / runtime need to be updated to understand the use of pointers to generic type parameters? Or can it simply work as is without changes? **Resolution** No changes needed. All pointer types are simply unverifiable. ## Design meetings n/a ================================================ FILE: proposals/csharp-7.3/enum-delegate-constraints.md ================================================ ## Enum and Delegate type parameter constraint Champion issue: In C# 7.3, we added support for type parameter constraint keywords `enum` and `delegate`. This is a placeholder for their specification. ================================================ FILE: proposals/csharp-7.3/expression-variables-in-initializers.md ================================================ # Expression variables in initializers Champion issue: ## Summary [summary]: #summary We extend the features introduced in C# 7 to permit expressions containing expression variables (out variable declarations and declaration patterns) in field initializers, property initializers, ctor-initializers, and query clauses. ## Motivation [motivation]: #motivation This completes a couple of the rough edges left in the C# language due to lack of time. ## Detailed design [design]: #detailed-design We remove the restriction preventing the declaration of expression variables (out variable declarations and declaration patterns) in a ctor-initializer. Such a declared variable is in scope throughout the body of the constructor. We remove the restriction preventing the declaration of expression variables (out variable declarations and declaration patterns) in a field or property initializer. Such a declared variable is in scope throughout the initializing expression. We remove the restriction preventing the declaration of expression variables (out variable declarations and declaration patterns) in a query expression clause that is translated into the body of a lambda. Such a declared variable is in scope throughout that expression of the query clause. ## Drawbacks [drawbacks]: #drawbacks None. ## Alternatives [alternatives]: #alternatives The appropriate scope for expression variables declared in these contexts is not obvious, and deserves further LDM discussion. ## Unresolved questions [unresolved]: #unresolved-questions - [ ] What is the appropriate scope for these variables? ## Design meetings None. ================================================ FILE: proposals/csharp-7.3/improved-overload-candidates.md ================================================ # Improved overload candidates Champion issue: ## Summary [summary]: #summary The overload resolution rules have been updated in nearly every C# language update to improve the experience for programmers, making ambiguous invocations select the "obvious" choice. This has to be done carefully to preserve backward compatibility, but since we are usually resolving what would otherwise be error cases, these enhancements usually work out nicely. 1. When a method group contains both instance and static members, we discard the instance members if invoked without an instance receiver or context, and discard the static members if invoked with an instance receiver. When there is no receiver, we include only static members in a static context, otherwise both static and instance members. When the receiver is ambiguously an instance or type due to a color-color situation, we include both. A static context, where an implicit this instance receiver cannot be used, includes the body of members where no this is defined, such as static members, as well as places where this cannot be used, such as field initializers and constructor-initializers. 2. When a method group contains some generic methods whose type arguments do not satisfy their constraints, these members are removed from the candidate set. 3. For a method group conversion, candidate methods whose return type doesn't match up with the delegate's return type are removed from the set. ================================================ FILE: proposals/csharp-7.3/indexing-movable-fixed-fields.md ================================================ # Indexing `fixed` fields should not require pinning regardless of the movable/unmovable context. # Champion issue: The change has the size of a bug fix. It can be in 7.3 and does not conflict with whatever direction we take further. This change is only about allowing the following scenario to work even though `s` is moveable. It is already valid when `s` is not moveable. NOTE: in either case, it still requires `unsafe` context. It is possible to read uninitialized data or even out of range. That is not changing. ```csharp unsafe struct S { public fixed int myFixedField[10]; } class Program { static S s; unsafe static void Main() { int p = s.myFixedField[5]; // indexing fixed-size array fields would be ok } } ``` The main “challenge” that I see here is how to explain the relaxation in the spec. In particular, since the following would still need pinning. (because `s` is moveable and we explicitly use the field as a pointer) ```csharp unsafe struct S { public fixed int myFixedField[10]; } class Program { static S s; unsafe static void Main() { int* ptr = s.myFixedField; // taking a pointer explicitly still requires pinning. int p = ptr[5]; } } ``` One reason why we require pinning of the target when it is movable is the artifact of our code generation strategy, - we always convert to an unmanaged pointer and thus force the user to pin via `fixed` statement. However, conversion to unmanaged is unnecessary when doing indexing. The same unsafe pointer math is equally applicable when we have the receiver in the form of a managed pointer. If we do that, then the intermediate ref is managed (GC-tracked) and the pinning is unnecessary. The change https://github.com/dotnet/roslyn/pull/24966 is a prototype PR that relaxes this requirement. ================================================ FILE: proposals/csharp-7.3/pattern-based-fixed.md ================================================ # Pattern-based `fixed` statement Champion issue: ## Summary [summary]: #summary Introduce a pattern that would allow types to participate in `fixed` statements. ## Motivation [motivation]: #motivation The language provides a mechanism for pinning managed data and obtain a native pointer to the underlying buffer. ```csharp fixed(byte* ptr = byteArray) { // ptr is a native pointer to the first element of the array // byteArray is protected from being moved/collected by the GC for the duration of this block } ``` The set of types that can participate in `fixed` is hardcoded and limited to arrays and `System.String`. Hardcoding "special" types does not scale when new primitives such as `ImmutableArray`, `Span`, `Utf8String` are introduced. In addition, the current solution for `System.String` relies on a fairly rigid API. The shape of the API implies that `System.String` is a contiguous object that embeds UTF16 encoded data at a fixed offset from the object header. Such approach has been found problematic in several proposals that could require changes to the underlying layout. It would be desirable to be able to switch to something more flexible that decouples `System.String` object from its internal representation for the purpose of unmanaged interop. ## Detailed design [design]: #detailed-design ## *Pattern* ## A viable pattern-based “fixed” need to: - Provide the managed references to pin the instance and to initialize the pointer (preferably this is the same reference) - Convey unambiguously the type of the unmanaged element (i.e. “char” for “string”) - Prescribe the behavior in "empty" case when there is nothing to refer to. - Should not push API authors toward design decisions that hurt the use of the type outside of `fixed`. I think the above could be satisfied by recognizing a specially named ref-returning member: `ref [readonly] T GetPinnableReference()`. In order to be used by the `fixed` statement the following conditions must be met: 1. There is only one such member provided for a type. 1. Returns by `ref` or `ref readonly`. (`readonly` is permitted so that authors of immutable/readonly types could implement the pattern without adding writeable API that could be used in safe code) 1. T is an unmanaged type. (since `T*` becomes the pointer type. The restriction will naturally expand if/when the notion of "unmanaged" is expanded) 1. Returns managed `nullptr` when there is no data to pin – probably the cheapest way to convey emptiness. (note that “” string returns a ref to '\0' since strings are null-terminated) Alternatively for the `#3` we can allow the result in empty cases be undefined or implementation-specific. That, however, may make the API more dangerous and prone to abuse and unintended compatibility burdens. ## *Translation* ## ```csharp fixed(byte* ptr = thing) { // } ``` becomes the following pseudocode (not all expressible in C#) ```csharp byte* ptr; // specially decorated "pinned" IL local slot, not visible to user code. pinned ref byte _pinned; try { // NOTE: null check is omitted for value types // NOTE: `thing` is evaluated only once (temporary is introduced if necessary) if (thing != null) { // obtain and "pin" the reference _pinned = ref thing.GetPinnableReference(); // unsafe cast in IL ptr = (byte*)_pinned; } else { ptr = default(byte*); } // } finally // finally can be omitted when not observable { // "unpin" the object _pinned = nullptr; } ``` ## Drawbacks [drawbacks]: #drawbacks - GetPinnableReference is intended to be used only in `fixed`, but nothing prevents its use in safe code, so implementor must keep that in mind. ## Alternatives [alternatives]: #alternatives Users can introduce GetPinnableReference or similar member and use it as ```csharp fixed(byte* ptr = thing.GetPinnableReference()) { // } ``` There is no solution for `System.String` if alternative solution is desired. ## Unresolved questions [unresolved]: #unresolved-questions - [ ] Behavior in "empty" state. - `nullptr` or `undefined` ? - [ ] Should the extension methods be considered ? - [ ] If a pattern is detected on `System.String`, should it win over ? ## Design meetings None yet. ================================================ FILE: proposals/csharp-7.3/ref-local-reassignment.md ================================================ # Ref Local Reassignment Champion issue: In C# 7.3, we add support for rebinding the referent of a ref local variable or a ref parameter. We add the following to the set of `assignment_operator`s. ```antlr assignment_operator : '=' 'ref' ; ``` The `=ref` operator is called the ***ref assignment operator***. It is not a *compound assignment operator*. The left operand must be an expression that binds to a ref local variable, a ref parameter (other than `this`), or an out parameter. The right operand must be an expression that yields an lvalue designating a value of the same type as the left operand. The right operand must be definitely assigned at the point of the ref assignment. When the left operand binds to an `out` parameter, it is an error if that `out` parameter has not been definitely assigned at the beginning of the ref assignment operator. If the left operand is a writeable ref (i.e. it designates anything other than a `ref readonly` local or `in` parameter), then the right operand must be a writeable lvalue. The ref assignment operator yields an lvalue of the assigned type. It is writeable if the left operand is writeable (i.e. not `ref readonly` or `in`). The safety rules for this operator are: - For a ref reassignment `e1 = ref e2`, the *ref-safe-to-escape* of `e2` must be at least as wide a scope as the *ref-safe-to-escape* of `e1`. Where *ref-safe-to-escape* is defined in [Safety for ref-like types](../csharp-7.2/span-safety.md) ================================================ FILE: proposals/csharp-7.3/ref-loops.md ================================================ ## Ref loops In C# 7.3, we added support for *ref for loops* and *ref foreach loops*. This is a placeholder for their specifications. ================================================ FILE: proposals/csharp-7.3/stackalloc-array-initializers.md ================================================ # Stackalloc array initializers Champion issue: ## Summary [summary]: #summary Allow array initializer syntax to be used with `stackalloc`. ## Motivation [motivation]: #motivation Ordinary arrays can have their elements initialized at creation time. It seems reasonable to allow that in `stackalloc` case. The question of why such syntax is not allowed with `stackalloc` arises fairly frequently. See, for example, [#1112](https://github.com/dotnet/csharplang/issues/1112) ## Detailed design Ordinary arrays can be created through the following syntax: ```csharp new int[3] new int[3] { 1, 2, 3 } new int[] { 1, 2, 3 } new[] { 1, 2, 3 } ``` We should allow stack allocated arrays be created through: ```csharp stackalloc int[3] // currently allowed stackalloc int[3] { 1, 2, 3 } stackalloc int[] { 1, 2, 3 } stackalloc[] { 1, 2, 3 } ``` The semantics of all cases is roughly the same as with arrays. For example: in the last case the element type is inferred from the initializer and must be an "unmanaged" type. NOTE: the feature is not dependent on the target being a `Span`. It is just as applicable in `T*` case, so it does not seem reasonable to predicate it on `Span` case. ## Translation The naive implementation could just initialize the array right after creation through a series of element-wise assignments. Similarly to the case with arrays, it might be possible and desirable to detect cases where all or most of the elements are blittable types and use more efficient techniques by copying over the pre-created state of all the constant elements. ## Drawbacks [drawbacks]: #drawbacks ## Alternatives [alternatives]: #alternatives This is a convenience feature. It is possible to just do nothing. ## Unresolved questions [unresolved]: #unresolved-questions ## Design meetings None yet. ================================================ FILE: proposals/csharp-7.3/tuple-equality.md ================================================ # Support for == and != on tuple types Champion issue: Allow expressions `t1 == t2` where `t1` and `t2` are tuple or nullable tuple types of same cardinality, and evaluate them roughly as `temp1.Item1 == temp2.Item1 && temp1.Item2 == temp2.Item2` (assuming `var temp1 = t1; var temp2 = t2;`). Conversely it would allow `t1 != t2` and evaluate it as `temp1.Item1 != temp2.Item1 || temp1.Item2 != temp2.Item2`. In the nullable case, additional checks for `temp1.HasValue` and `temp2.HasValue` are used. For instance, `nullableT1 == nullableT2` evaluates as `temp1.HasValue == temp2.HasValue ? (temp1.HasValue ? ... : true) : false`. When an element-wise comparison returns a non-bool result (for instance, when a non-bool user-defined `operator ==` or `operator !=` is used, or in a dynamic comparison), then that result will be either converted to `bool` or run through `operator true` or `operator false` to get a `bool`. The tuple comparison always ends up returning a `bool`. As of C# 7.2, such code produces an error (`error CS0019: Operator '==' cannot be applied to operands of type '(...)' and '(...)'`), unless there is a user-defined `operator==`. ## Details When binding the `==` (or `!=`) operator, the existing rules are: (1) dynamic case, (2) overload resolution, and (3) fail. This proposal adds a tuple case between (1) and (2): if both operands of a comparison operator are tuples (have tuple types or are tuple literals) and have matching cardinality, then the comparison is performed element-wise. This tuple equality is also lifted onto nullable tuples. Both operands (and, in the case of tuple literals, their elements) are evaluated in order from left to right. Each pair of elements is then used as operands to bind the operator `==` (or `!=`), recursively. Any elements with compile-time type `dynamic` cause an error. The results of those element-wise comparisons are used as operands in a chain of conditional AND (or OR) operators. For instance, in the context of `(int, (int, int)) t1, t2;`, `t1 == (1, (2, 3))` would evaluate as `temp1.Item1 == temp2.Item1 && temp1.Item2.Item1 == temp2.Item2.Item1 && temp1.Item2.Item2 == temp2.Item2.Item2`. When a tuple literal is used as operand (on either side), it receives a converted tuple type formed by the element-wise conversions which are introduced when binding the operator `==` (or `!=`) element-wise. For instance, in `(1L, 2, "hello") == (1, 2L, null)`, the converted type for both tuple literals is `(long, long, string)` and the second literal has no natural type. ### Deconstruction and conversions to tuple In `(a, b) == x`, the fact that `x` can deconstruct into two elements does not play a role. That could conceivably be in a future proposal, although it would raise questions about `x == y` (is this a simple comparison or an element-wise comparison, and if so using what cardinality?). Similarly, conversions to tuple play no role. ### Tuple element names When converting a tuple literal, we warn when an explicit tuple element name was provided in the literal, but it doesn't match the target tuple element name. We use the same rule in tuple comparison, so that assuming `(int a, int b) t` we warn on `d` in `t == (c, d: 0)`. ### Non-bool element-wise comparison results If an element-wise comparison is dynamic in a tuple equality, we use a dynamic invocation of the operator `false` and negate that to get a `bool` and continue with further element-wise comparisons. If an element-wise comparison returns some other non-bool type in a tuple equality, there are two cases: - if the non-bool type converts to `bool`, we apply that conversion, - if there is no such conversion, but the type has an operator `false`, we'll use that and negate the result. In a tuple inequality, the same rules apply except that we'll use the operator `true` (without negation) instead of the operator `false`. Those rules are similar to the rules involved for using a non-bool type in an `if` statement and some other existing contexts. ## Evaluation order and special cases The left-hand-side value is evaluated first, then the right-hand-side value, then the element-wise comparisons from left to right (including conversions, and with early exit based on existing rules for conditional AND/OR operators). For instance, if there is a conversion from type `A` to type `B` and a method `(A, A) GetTuple()`, evaluating `(new A(1), (new B(2), new B(3))) == (new B(4), GetTuple())` means: - `new A(1)` - `new B(2)` - `new B(3)` - `new B(4)` - `GetTuple()` - then the element-wise conversions and comparisons and conditional logic is evaluated (convert `new A(1)` to type `B`, then compare it with `new B(4)`, and so on). ### Comparing `null` to `null` This is a special case from regular comparisons, that carries over to tuple comparisons. The `null == null` comparison is allowed, and the `null` literals do not get any type. In tuple equality, this means, `(0, null) == (0, null)` is also allowed and the `null` and tuple literals don't get a type either. ### Comparing a nullable struct to `null` without `operator==` This is another special case from regular comparisons, that carries over to tuple comparisons. If you have a `struct S` without `operator==`, the `(S?)x == null` comparison is allowed, and it is interpreted as `((S?).x).HasValue`. In tuple equality, the same rule is applied, so `(0, (S?)x) == (0, null)` is allowed. ## Compatibility If someone wrote their own `ValueTuple` types with an implementation of the comparison operator, it would have previously been picked up by overload resolution. But since the new tuple case comes before overload resolution, we would handle this case with tuple comparison instead of relying on the user-defined comparison. ---- Relates to relational and type testing operators ([§11.11](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1111-relational-and-type-testing-operators)) Relates to [#190](https://github.com/dotnet/csharplang/issues/190) ================================================ FILE: proposals/csharp-8.0/README.md ================================================ ================================================ FILE: proposals/csharp-8.0/alternative-interpolated-verbatim.md ================================================ ## Alternative interpolated verbatim strings Champion issue: In C# 8.0 we added a feature that permits an interpolated verbatim string to be introduced with the characters `@$"` or the characters `$@"`. This is a placeholder for its specification. ================================================ FILE: proposals/csharp-8.0/async-streams.md ================================================ # Async Streams [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary C# has support for iterator methods and async methods, but no support for a method that is both an iterator and an async method. We should rectify this by allowing for `await` to be used in a new form of `async` iterator, one that returns an `IAsyncEnumerable` or `IAsyncEnumerator` rather than an `IEnumerable` or `IEnumerator`, with `IAsyncEnumerable` consumable in a new `await foreach`. An `IAsyncDisposable` interface is also used to enable asynchronous cleanup. ## Related discussion - https://github.com/dotnet/roslyn/issues/261 - https://github.com/dotnet/roslyn/issues/114 ## Detailed design [design]: #detailed-design ## Interfaces ### IAsyncDisposable There has been much discussion of `IAsyncDisposable` (e.g. https://github.com/dotnet/roslyn/issues/114) and whether it's a good idea. However, it's a required concept to add in support of async iterators. Since `finally` blocks may contain `await`s, and since `finally` blocks need to be run as part of disposing of iterators, we need async disposal. It's also just generally useful any time cleaning up of resources might take any period of time, e.g. closing files (requiring flushes), deregistering callbacks and providing a way to know when deregistration has completed, etc. The following interface is added to the core .NET libraries (e.g. System.Private.CoreLib / System.Runtime): ```csharp namespace System { public interface IAsyncDisposable { ValueTask DisposeAsync(); } } ``` As with `Dispose`, invoking `DisposeAsync` multiple times is acceptable, and subsequent invocations after the first should be treated as no-ops, returning a synchronously completed successful task (`DisposeAsync` need not be thread-safe, though, and need not support concurrent invocation). Further, types may implement both `IDisposable` and `IAsyncDisposable`, and if they do, it's similarly acceptable to invoke `Dispose` and then `DisposeAsync` or vice versa, but only the first should be meaningful and subsequent invocations of either should be a nop. As such, if a type does implement both, consumers are encouraged to call once and only once the more relevant method based on the context, `Dispose` in synchronous contexts and `DisposeAsync` in asynchronous ones. (How `IAsyncDisposable` interacts with `using` is a separate discussion. And coverage of how it interacts with `foreach` is handled later in this proposal.) Alternatives considered: - _`DisposeAsync` accepting a `CancellationToken`_: while in theory it makes sense that anything async can be canceled, disposal is about cleanup, closing things out, free'ing resources, etc., which is generally not something that should be canceled; cleanup is still important for work that's canceled. The same `CancellationToken` that caused the actual work to be canceled would typically be the same token passed to `DisposeAsync`, making `DisposeAsync` worthless because cancellation of the work would cause `DisposeAsync` to be a no-op. If someone wants to avoid being blocked waiting for disposal, they can avoid waiting on the resulting `ValueTask`, or wait on it only for some period of time. - _`DisposeAsync` returning a `Task`_: Now that a non-generic `ValueTask` exists and can be constructed from an `IValueTaskSource`, returning `ValueTask` from `DisposeAsync` allows an existing object to be reused as the promise representing the eventual async completion of `DisposeAsync`, saving a `Task` allocation in the case where `DisposeAsync` completes asynchronously. - _Configuring `DisposeAsync` with a `bool continueOnCapturedContext` (`ConfigureAwait`)_: While there may be issues related to how such a concept is exposed to `using`, `foreach`, and other language constructs that consume this, from an interface perspective it's not actually doing any `await`'ing and there's nothing to configure... consumers of the `ValueTask` can consume it however they wish. - _`IAsyncDisposable` inheriting `IDisposable`_: Since only one or the other should be used, it doesn't make sense to force types to implement both. - _`IDisposableAsync` instead of `IAsyncDisposable`_: We've been following the naming that things/types are an "async something" whereas operations are "done async", so types have "Async" as a prefix and methods have "Async" as a suffix. ### IAsyncEnumerable / IAsyncEnumerator Two interfaces are added to the core .NET libraries: ```csharp namespace System.Collections.Generic { public interface IAsyncEnumerable { IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default); } public interface IAsyncEnumerator : IAsyncDisposable { ValueTask MoveNextAsync(); T Current { get; } } } ``` Typical consumption (without additional language features) would look like: ```csharp IAsyncEnumerator enumerator = enumerable.GetAsyncEnumerator(); try { while (await enumerator.MoveNextAsync()) { Use(enumerator.Current); } } finally { await enumerator.DisposeAsync(); } ``` Discarded options considered: - _`Task MoveNextAsync(); T current { get; }`_: Using `Task` would support using a cached task object to represent synchronous, successful `MoveNextAsync` calls, but an allocation would still be required for asynchronous completion. By returning `ValueTask`, we enable the enumerator object to itself implement `IValueTaskSource` and be used as the backing for the `ValueTask` returned from `MoveNextAsync`, which in turn allows for significantly reduced overheads. - _`ValueTask<(bool, T)> MoveNextAsync();`_: It's not only harder to consume, but it means that `T` can no longer be covariant. - _`ValueTask TryMoveNextAsync();`_: Not covariant. - _`Task TryMoveNextAsync();`_: Not covariant, allocations on every call, etc. - _`ITask TryMoveNextAsync();`_: Not covariant, allocations on every call, etc. - _`ITask<(bool,T)> TryMoveNextAsync();`_: Not covariant, allocations on every call, etc. - _`Task TryMoveNextAsync(out T result);`_: The `out` result would need to be set when the operation returns synchronously, not when it asynchronously completes the task potentially sometime long in the future, at which point there'd be no way to communicate the result. - _`IAsyncEnumerator` not implementing `IAsyncDisposable`_: We could choose to separate these. However, doing so complicates certain other areas of the proposal, as code must then be able to deal with the possibility that an enumerator doesn't provide disposal, which makes it difficult to write pattern-based helpers. Further, it will be common for enumerators to have a need for disposal (e.g. any C# async iterator that has a finally block, most things enumerating data from a network connection, etc.), and if one doesn't, it is simple to implement the method purely as `public ValueTask DisposeAsync() => default(ValueTask);` with minimal additional overhead. - _ `IAsyncEnumerator GetAsyncEnumerator()`: No cancellation token parameter. The following subsection discuss alternatives that weren't chosen.
#### Viable alternative: ```csharp namespace System.Collections.Generic { public interface IAsyncEnumerable { IAsyncEnumerator GetAsyncEnumerator(); } public interface IAsyncEnumerator : IAsyncDisposable { ValueTask WaitForNextAsync(); T TryGetNext(out bool success); } } ``` `TryGetNext` is used in an inner loop to consume items with a single interface call as long as they're available synchronously. When the next item can't be retrieved synchronously, it returns false, and any time it returns false, a caller must subsequently invoke `WaitForNextAsync` to either wait for the next item to be available or to determine that there will never be another item. Typical consumption (without additional language features) would look like: ```csharp IAsyncEnumerator enumerator = enumerable.GetAsyncEnumerator(); try { while (await enumerator.WaitForNextAsync()) { while (true) { int item = enumerator.TryGetNext(out bool success); if (!success) break; Use(item); } } } finally { await enumerator.DisposeAsync(); } ``` The advantage of this is two-fold, one minor and one major: - _Minor: Allows for an enumerator to support multiple consumers_. There may be scenarios where it's valuable for an enumerator to support multiple concurrent consumers. That can't be achieved when `MoveNextAsync` and `Current` are separate such that an implementation can't make their usage atomic. In contrast, this approach provides a single method `TryGetNext` that supports pushing the enumerator forward and getting the next item, so the enumerator can enable atomicity if desired. However, it's likely that such scenarios could also be enabled by giving each consumer its own enumerator from a shared enumerable. Further, we don't want to enforce that every enumerator support concurrent usage, as that would add non-trivial overheads to the majority case that doesn't require it, which means a consumer of the interface generally couldn't rely on this any way. - _Major: Performance_. The `MoveNextAsync`/`Current` approach requires two interface calls per operation, whereas the best case for `WaitForNextAsync`/`TryGetNext` is that most iterations complete synchronously, enabling a tight inner loop with `TryGetNext`, such that we only have one interface call per operation. This can have a measurable impact in situations where the interface calls dominate the computation. However, there are non-trivial downsides, including significantly increased complexity when consuming these manually, and an increased chance of introducing bugs when using them. And while the performance benefits show up in microbenchmarks, we don't believe they'll be impactful in the vast majority of real usage. If it turns out they are, we can introduce a second set of interfaces in a light-up fashion. Discarded options considered: - `ValueTask WaitForNextAsync(); bool TryGetNext(out T result);`: `out` parameters can't be covariant. There's also a small impact here (an issue with the try pattern in general) that this likely incurs a runtime write barrier for reference type results.
#### Cancellation There are several possible approaches to supporting cancellation: 1. `IAsyncEnumerable`/`IAsyncEnumerator` are cancellation-agnostic: `CancellationToken` doesn't appear anywhere. Cancellation is achieved by logically baking the `CancellationToken` into the enumerable and/or enumerator in whatever manner is appropriate, e.g. when calling an iterator, passing the `CancellationToken` as an argument to the iterator method and using it in the body of the iterator, as is done with any other parameter. 2. `IAsyncEnumerator.GetAsyncEnumerator(CancellationToken)`: You pass a `CancellationToken` to `GetAsyncEnumerator`, and subsequent `MoveNextAsync` operations respect it however it can. 3. `IAsyncEnumerator.MoveNextAsync(CancellationToken)`: You pass a `CancellationToken` to each individual `MoveNextAsync` call. 4. 1 && 2: You both embed `CancellationToken`s into your enumerable/enumerator and pass `CancellationToken`s into `GetAsyncEnumerator`. 5. 1 && 3: You both embed `CancellationToken`s into your enumerable/enumerator and pass `CancellationToken`s into `MoveNextAsync`. From a purely theoretical perspective, (5) is the most robust, in that (a) `MoveNextAsync` accepting a `CancellationToken` enables the most fine-grained control over what's canceled, and (b) `CancellationToken` is just any other type that can passed as an argument into iterators, embedded in arbitrary types, etc. However, there are multiple problems with that approach: - How does a `CancellationToken` passed to `GetAsyncEnumerator` make it into the body of the iterator? We could expose a new `iterator` keyword that you could dot off of to get access to the `CancellationToken` passed to `GetEnumerator`, but a) that's a lot of additional machinery, b) we're making it a very first-class citizen, and c) the 99% case would seem to be the same code both calling an iterator and calling `GetAsyncEnumerator` on it, in which case it can just pass the `CancellationToken` as an argument into the method. - How does a `CancellationToken` passed to `MoveNextAsync` get into the body of the method? This is even worse, as if it's exposed off of an `iterator` local object, its value could change across awaits, which means any code that registered with the token would need to unregister from it prior to awaits and then re-register after; it's also potentially quite expensive to need to do such registering and unregistering in every `MoveNextAsync` call, regardless of whether implemented by the compiler in an iterator or by a developer manually. - How does a developer cancel a `foreach` loop? If it's done by giving a `CancellationToken` to an enumerable/enumerator, then either a) we need to support `foreach`'ing over enumerators, which raises them to being first-class citizens, and now you need to start thinking about an ecosystem built up around enumerators (e.g. LINQ methods) or b) we need to embed the `CancellationToken` in the enumerable anyway by having some `WithCancellation` extension method off of `IAsyncEnumerable` that would store the provided token and then pass it into the wrapped enumerable's `GetAsyncEnumerator` when the `GetAsyncEnumerator` on the returned struct is invoked (ignoring that token). Or, you can just use the `CancellationToken` you have in the body of the foreach. - If/when query comprehensions are supported, how would the `CancellationToken` supplied to `GetEnumerator` or `MoveNextAsync` be passed into each clause? The easiest way would simply be for the clause to capture it, at which point whatever token is passed to `GetAsyncEnumerator`/`MoveNextAsync` is ignored. An earlier version of this document recommended (1), but we since switched to (4). The two main problems with (1): - producers of cancellable enumerables have to implement some boilerplate, and can only leverage the compiler's support for async-iterators to implement a `IAsyncEnumerator GetAsyncEnumerator(CancellationToken)` method. - it is likely that many producers would be tempted to just add a `CancellationToken` parameter to their async-enumerable signature instead, which will prevent consumers from passing the cancellation token they want when they are given an `IAsyncEnumerable` type. There are two main consumption scenarios: 1. `await foreach (var i in GetData(token)) ...` where the consumer calls the async-iterator method, 2. `await foreach (var i in givenIAsyncEnumerable.WithCancellation(token)) ...` where the consumer deals with a given `IAsyncEnumerable` instance. We find that a reasonable compromise to support both scenarios in a way that is convenient for both producers and consumers of async-streams is to use a specially annotated parameter in the async-iterator method. The `[EnumeratorCancellation]` attribute is used for this purpose. Placing this attribute on a parameter tells the compiler that if a token is passed to the `GetAsyncEnumerator` method, that token should be used instead of the value originally passed for the parameter. Consider `IAsyncEnumerable GetData([EnumeratorCancellation] CancellationToken token = default)`. The implementer of this method can simply use the parameter in the method body. The consumer can use either consumption patterns above: 1. if you use `GetData(token)`, then the token is saved into the async-enumerable and will be used in iteration, 2. if you use `givenIAsyncEnumerable.WithCancellation(token)`, then the token passed to `GetAsyncEnumerator` will supersede any token saved in the async-enumerable. ## foreach `foreach` will be augmented to support `IAsyncEnumerable` in addition to its existing support for `IEnumerable`. And it will support the equivalent of `IAsyncEnumerable` as a pattern if the relevant members are exposed publicly, falling back to using the interface directly if not, in order to enable struct-based extensions that avoid allocating as well as using alternative awaitables as the return type of `MoveNextAsync` and `DisposeAsync`. ### Syntax Using the syntax: ```csharp foreach (var i in enumerable) ``` C# will continue to treat `enumerable` as a synchronous enumerable, such that even if it exposes the relevant APIs for async enumerables (exposing the pattern or implementing the interface), it will only consider the synchronous APIs. To force `foreach` to instead only consider the asynchronous APIs, `await` is inserted as follows: ```csharp await foreach (var i in enumerable) ``` No syntax would be provided that would support using either the async or the sync APIs; the developer must choose based on the syntax used. ### Semantics The compile-time processing of an `await foreach` statement first determines the ***collection type***, ***enumerator type*** and ***iteration type*** of the expression (very similar to https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement). This determination proceeds as follows: - If the type `X` of *expression* is `dynamic` or an array type, then an error is produced and no further steps are taken. - Otherwise, determine whether the type `X` has an appropriate `GetAsyncEnumerator` method: - Perform member lookup on the type `X` with identifier `GetAsyncEnumerator` and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. - Perform overload resolution using the resulting method group and an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, check for an enumerable interface as described below. - If the return type `E` of the `GetAsyncEnumerator` method is not a class, struct or interface type, an error is produced and no further steps are taken. - Member lookup is performed on `E` with the identifier `Current` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken. - Member lookup is performed on `E` with the identifier `MoveNextAsync` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. - Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not awaitable into `bool`, an error is produced and no further steps are taken. - The collection type is `X`, the enumerator type is `E`, and the iteration type is the type of the `Current` property. - Otherwise, check for an enumerable interface: - If among all the types `Tᵢ` for which there is an implicit conversion from `X` to `IAsyncEnumerable<ᵢ>`, there is a unique type `T` such that `T` is not dynamic and for all the other `Tᵢ` there is an implicit conversion from `IAsyncEnumerable` to `IAsyncEnumerable`, then the collection type is the interface `IAsyncEnumerable`, the enumerator type is the interface `IAsyncEnumerator`, and the iteration type is `T`. - Otherwise, if there is more than one such type `T`, then an error is produced and no further steps are taken. - Otherwise, an error is produced and no further steps are taken. The above steps, if successful, unambiguously produce a collection type `C`, enumerator type `E` and iteration type `T`. ```csharp await foreach (V v in x) «embedded_statement» ``` is then expanded to: ```csharp { E e = ((C)(x)).GetAsyncEnumerator(); try { while (await e.MoveNextAsync()) { V v = (V)(T)e.Current; «embedded_statement» } } finally { ... // Dispose e } } ``` The body of the `finally` block is constructed according to the following steps: - If the type `E` has an appropriate `DisposeAsync` method: - Perform member lookup on the type `E` with identifier `DisposeAsync` and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for the disposal interface as described below. - Perform overload resolution using the resulting method group and an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, check for the disposal interface as described below. - If the return type of the `DisposeAsync` method is not awaitable, an error is produced and no further steps are taken. - The `finally` clause is expanded to the semantic equivalent of: ```csharp finally { await e.DisposeAsync(); } ``` - Otherwise, if there is an implicit conversion from `E` to the `System.IAsyncDisposable` interface, then - If `E` is a non-nullable value type then the `finally` clause is expanded to the semantic equivalent of: ```csharp finally { await ((System.IAsyncDisposable)e).DisposeAsync(); } ``` - Otherwise the `finally` clause is expanded to the semantic equivalent of: ```csharp finally { System.IAsyncDisposable d = e as System.IAsyncDisposable; if (d != null) await d.DisposeAsync(); } ``` except that if `E` is a value type, or a type parameter instantiated to a value type, then the conversion of `e` to `System.IAsyncDisposable` shall not cause boxing to occur. - Otherwise, the `finally` clause is expanded to an empty block: ```csharp finally { } ``` ### ConfigureAwait This pattern-based compilation will allow `ConfigureAwait` to be used on all of the awaits, via a `ConfigureAwait` extension method: ```csharp await foreach (T item in enumerable.ConfigureAwait(false)) { ... } ``` This will be based on types we'll add to .NET as well, likely to System.Threading.Tasks.Extensions.dll: ```csharp // Approximate implementation, omitting arg validation and the like namespace System.Threading.Tasks { public static class AsyncEnumerableExtensions { public static ConfiguredAsyncEnumerable ConfigureAwait(this IAsyncEnumerable enumerable, bool continueOnCapturedContext) => new ConfiguredAsyncEnumerable(enumerable, continueOnCapturedContext); public struct ConfiguredAsyncEnumerable { private readonly IAsyncEnumerable _enumerable; private readonly bool _continueOnCapturedContext; internal ConfiguredAsyncEnumerable(IAsyncEnumerable enumerable, bool continueOnCapturedContext) { _enumerable = enumerable; _continueOnCapturedContext = continueOnCapturedContext; } public ConfiguredAsyncEnumerator GetAsyncEnumerator() => new ConfiguredAsyncEnumerator(_enumerable.GetAsyncEnumerator(), _continueOnCapturedContext); public struct ConfiguredAsyncEnumerator { private readonly IAsyncEnumerator _enumerator; private readonly bool _continueOnCapturedContext; internal ConfiguredAsyncEnumerator(IAsyncEnumerator enumerator, bool continueOnCapturedContext) { _enumerator = enumerator; _continueOnCapturedContext = continueOnCapturedContext; } public ConfiguredValueTaskAwaitable MoveNextAsync() => _enumerator.MoveNextAsync().ConfigureAwait(_continueOnCapturedContext); public T Current => _enumerator.Current; public ConfiguredValueTaskAwaitable DisposeAsync() => _enumerator.DisposeAsync().ConfigureAwait(_continueOnCapturedContext); } } } } ``` Note that this approach will not enable `ConfigureAwait` to be used with pattern-based enumerables, but then again it's already the case that the `ConfigureAwait` is only exposed as an extension on `Task`/`Task`/`ValueTask`/`ValueTask` and can't be applied to arbitrary awaitable things, as it only makes sense when applied to Tasks (it controls a behavior implemented in Task's continuation support), and thus doesn't make sense when using a pattern where the awaitable things may not be tasks. Anyone returning awaitable things can provide their own custom behavior in such advanced scenarios. (If we can come up with some way to support a scope- or assembly-level `ConfigureAwait` solution, then this won't be necessary.) ## Async Iterators The language / compiler will support producing `IAsyncEnumerable`s and `IAsyncEnumerator`s in addition to consuming them. Today the language supports writing an iterator like: ```csharp static IEnumerable MyIterator() { try { for (int i = 0; i < 100; i++) { Thread.Sleep(1000); yield return i; } } finally { Thread.Sleep(200); Console.WriteLine("finally"); } } ``` but `await` can't be used in the body of these iterators. We will add that support. ### Syntax The existing language support for iterators infers the iterator nature of the method based on whether it contains any `yield`s. The same will be true for async iterators. Such async iterators will be demarcated and differentiated from synchronous iterators via adding `async` to the signature, and must then also have either `IAsyncEnumerable` or `IAsyncEnumerator` as its return type. For example, the above example could be written as an async iterator as follows: ```csharp static async IAsyncEnumerable MyIterator() { try { for (int i = 0; i < 100; i++) { await Task.Delay(1000); yield return i; } } finally { await Task.Delay(200); Console.WriteLine("finally"); } } ``` Alternatives considered: - _Not using `async` in the signature_: Using `async` is likely technically required by the compiler, as it uses it to determine whether `await` is valid in that context. But even if it's not required, we've established that `await` may only be used in methods marked as `async`, and it seems important to keep the consistency. - _Enabling custom builders for `IAsyncEnumerable`_: That's something we could look at for the future, but the machinery is complicated and we don't support that for the synchronous counterparts. - _Having an `iterator` keyword in the signature_: Async iterators would use `async iterator` in the signature, and `yield` could only be used in `async` methods that included `iterator`; `iterator` would then be made optional on synchronous iterators. Depending on your perspective, this has the benefit of making it very clear by the signature of the method whether `yield` is allowed and whether the method is actually meant to return instances of type `IAsyncEnumerable` rather than the compiler manufacturing one based on whether the code uses `yield` or not. But it is different from synchronous iterators, which don't and can't be made to require one. Plus some developers don't like the extra syntax. If we were designing it from scratch, we'd probably make this required, but at this point there's much more value in keeping async iterators close to sync iterators. ## LINQ There are over ~200 overloads of methods on the `System.Linq.Enumerable` class, all of which work in terms of `IEnumerable`; some of these accept `IEnumerable`, some of them produce `IEnumerable`, and many do both. Adding LINQ support for `IAsyncEnumerable` would likely entail duplicating all of these overloads for it, for another ~200. And since `IAsyncEnumerator` is likely to be more common as a standalone entity in the asynchronous world than `IEnumerator` is in the synchronous world, we could potentially need another ~200 overloads that work with `IAsyncEnumerator`. Plus, a large number of the overloads deal with predicates (e.g. `Where` that takes a `Func`), and it may be desirable to have `IAsyncEnumerable`-based overloads that deal with both synchronous and asynchronous predicates (e.g. `Func>` in addition to `Func`). While this isn't applicable to all of the now ~400 new overloads, a rough calculation is that it'd be applicable to half, which means another ~200 overloads, for a total of ~600 new methods. That is a staggering number of APIs, with the potential for even more when extension libraries like Interactive Extensions (Ix) are considered. But Ix already has an implementation of many of these, and there doesn't seem to be a great reason to duplicate that work; we should instead help the community improve Ix and recommend it for when developers want to use LINQ with `IAsyncEnumerable`. There is also the issue of query comprehension syntax. The pattern-based nature of query comprehensions would allow them to "just work" with some operators, e.g. if Ix provides the following methods: ```csharp public static IAsyncEnumerable Select(this IAsyncEnumerable source, Func func); public static IAsyncEnumerable Where(this IAsyncEnumerable source, Func func); ``` then this C# code will "just work": ```csharp IAsyncEnumerable enumerable = ...; IAsyncEnumerable result = from item in enumerable where item % 2 == 0 select item * 2; ``` However, there is no query comprehension syntax that supports using `await` in the clauses, so if Ix added, for example: ```csharp public static IAsyncEnumerable Select(this IAsyncEnumerable source, Func> func); ``` then this would "just work": ```csharp IAsyncEnumerable result = from url in urls where item % 2 == 0 select SomeAsyncMethod(item); async ValueTask SomeAsyncMethod(int item) { await Task.Yield(); return item * 2; } ``` but there'd be no way to write it with the `await` inline in the `select` clause. As a separate effort, we could look into adding `async { ... }` expressions to the language, at which point we could allow them to be used in query comprehensions and the above could instead be written as: ```csharp IAsyncEnumerable result = from item in enumerable where item % 2 == 0 select async { await Task.Yield(); return item * 2; }; ``` or to enabling `await` to be used directly in expressions, such as by supporting `async from`. However, it's unlikely a design here would impact the rest of the feature set one way or the other, and this isn't a particularly high-value thing to invest in right now, so the proposal is to do nothing additional here right now. ## Integration with other asynchronous frameworks Integration with `IObservable` and other asynchronous frameworks (e.g. reactive streams) would be done at the library level rather than at the language level. For example, all of the data from an `IAsyncEnumerator` can be published to an `IObserver` simply by `await foreach`'ing over the enumerator and `OnNext`'ing the data to the observer, so an `AsObservable` extension method is possible. Consuming an `IObservable` in a `await foreach` requires buffering the data (in case another item is pushed while the previous item is still being processing), but such a push-pull adapter can easily be implemented to enable an `IObservable` to be pulled from with an `IAsyncEnumerator`. Etc. Rx/Ix already provide prototypes of such implementations, and libraries like https://github.com/dotnet/corefx/tree/master/src/System.Threading.Channels provide various kinds of buffering data structures. The language need not be involved at this stage. ================================================ FILE: proposals/csharp-8.0/async-using.md ================================================ ## Async using declaration Champion issue: In C# 8.0 we added support for an *async using* statements. There are two forms. One has a block body that is the scope of the using declaratation. The other declares a local and is implicitly scoped to the end of the block. This is a placeholder for their specification. ================================================ FILE: proposals/csharp-8.0/constraints-in-overrides.md ================================================ ## Override with constraints In C# 8.0, we added a feature to permit the specification of certain type parameter constraints in an `override` method declaration. This is a placeholder for its specification. ================================================ FILE: proposals/csharp-8.0/constructed-unmanaged.md ================================================ ## Unmanaged constructed types Champion issue: In C# 8.0, we extended the concept of an *unmanaged* type to include constructed (generic) types. This is a placeholder for its specification. ================================================ FILE: proposals/csharp-8.0/default-interface-methods.md ================================================ # default interface methods [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Add support for _virtual extension methods_ - methods in interfaces with concrete implementations. A class or struct that implements such an interface is required to have a single _most specific_ implementation for the interface method, either implemented by the class or struct, or inherited from its base classes or interfaces. Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface. These are similar to Java's ["Default Methods"](http://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html). (Based on the likely implementation technique) this feature requires corresponding support in the CLI/CLR. Programs that take advantage of this feature cannot run on earlier versions of the platform. ## Motivation [motivation]: #motivation The principal motivations for this feature are - Default interface methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface. - The feature enables C# to interoperate with APIs targeting [Android (Java)](http://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html) and [iOS (Swift)](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267), which support similar features. - As it turns out, adding default interface implementations provides the elements of the "traits" language feature (). Traits have proven to be a powerful programming technique (). ## Detailed design [design]: #detailed-design The syntax for an interface is extended to permit - member declarations that declare constants, operators, static constructors, and nested types; - a *body* for a method or indexer, property, or event accessor (that is, a "default" implementation); - member declarations that declare static fields, methods, properties, indexers, and events; - member declarations using the explicit interface implementation syntax; and - Explicit access modifiers (the default access is `public`). Members with bodies permit the interface to provide a "default" implementation for the method in classes and structs that do not provide their own implementation. Interfaces may not contain instance state. While static fields are now permitted, instance fields are not permitted in interfaces. Instance auto-properties are not supported in interfaces, as they would implicitly declare a hidden field. Static and private methods permit useful refactoring and organization of code used to implement the interface's public API. A method override in an interface must use the explicit interface implementation syntax. It is an error to declare a class type, struct type, or enum type within the scope of a type parameter that was declared with a *variance_annotation*. For example, the declaration of `C` below is an error. ```csharp interface IOuter { class C { } // error: class declaration within the scope of variant type parameter 'T' } ``` ### Concrete methods in interfaces The simplest form of this feature is the ability to declare a *concrete method* in an interface, which is a method with a body. ```csharp interface IA { void M() { WriteLine("IA.M"); } } ``` A class that implements this interface need not implement its concrete method. ```csharp class C : IA { } // OK IA i = new C(); i.M(); // prints "IA.M" ``` The final override for `IA.M` in class `C` is the concrete method `M` declared in `IA`. Note that a class does not inherit members from its interfaces; that is not changed by this feature: ```csharp new C().M(); // error: class 'C' does not contain a member 'M' ``` Within an instance member of an interface, `this` has the type of the enclosing interface. ### Modifiers in interfaces The syntax for an interface is relaxed to permit modifiers on its members. The following are permitted: `private`, `protected`, `internal`, `public`, `virtual`, `abstract`, `sealed`, `static`, `extern`, and `partial`. An interface member whose declaration includes a body is a `virtual` member unless the `sealed` or `private` modifier is used. The `virtual` modifier may be used on a function member that would otherwise be implicitly `virtual`. Similarly, although `abstract` is the default on interface members without bodies, that modifier may be given explicitly. A non-virtual member may be declared using the `sealed` keyword. It is an error for a `private` or `sealed` function member of an interface to have no body. A `private` function member may not have the modifier `sealed`. Access modifiers may be used on interface members of all kinds of members that are permitted. The access level `public` is the default but it may be given explicitly. > ***Open Issue:*** We need to specify the precise meaning of the access modifiers such as `protected` and `internal`, and which declarations do and do not override them (in a derived interface) or implement them (in a class that implements the interface). Interfaces may declare `static` members, including nested types, methods, indexers, properties, events, and static constructors. The default access level for all interface members is `public`. Interfaces may not declare instance constructors, destructors, or fields. > ***Closed Issue:*** Should operator declarations be permitted in an interface? Probably not conversion operators, but what about others? ***Decision***: Operators are permitted *except* for conversion, equality, and inequality operators. > ***Closed Issue:*** Should `new` be permitted on interface member declarations that hide members from base interfaces? ***Decision***: Yes. > ***Closed Issue:*** We do not currently permit `partial` on an interface or its members. That would require a separate proposal. ***Decision***: Yes. ### Explicit implementation in interfaces Explicit implementations allow the programmer to provide a most specific implementation of a virtual member in an interface where the compiler or runtime would not otherwise find one. An implementation declaration is permitted to *explicitly* implement a particular base interface method by qualifying the declaration with the interface name (no access modifier is permitted in this case). Implicit implementations are not permitted. ```csharp interface IA { void M() { WriteLine("IA.M"); } } interface IB : IA { void IA.M() { WriteLine("IB.M"); } // Explicit implementation } interface IC : IA { void M() { WriteLine("IC.M"); } // Creates a new M, unrelated to `IA.M`. Warning } ``` Explicit implementations in interfaces may not be declared `sealed`. Public `virtual` function members in an interface may only be implemented in a derived interface explicitly (by qualifying the name in the declaration with the interface type that originally declared the method, and omitting an access modifier). The member must be *accessible* where it is implemented. ### Reabstraction A virtual (concrete) method declared in an interface may be reabstracted in a derived interface ```csharp interface IA { void M() { WriteLine("IA.M"); } } interface IB : IA { abstract void IA.M(); } class C : IB { } // error: class 'C' does not implement 'IA.M'. ``` The `abstract` modifier is required in the declaration of `IB.M`, to indicate that `IA.M` is being reabstracted. This is useful in derived interfaces where the default implementation of a method is inappropriate and a more appropriate implementation should be provided by implementing classes. ### The most specific implementation rule We require that every interface and class have a *most specific implementation* for every virtual member among the implementations appearing in the type or its direct and indirect interfaces. The *most specific implementation* is a unique implementation that is more specific than every other implementation. If there is no implementation, the member itself is considered the most specific implementation. One implementation `M1` is considered *more specific* than another implementation `M2` if `M1` is declared on type `T1`, `M2` is declared on type `T2`, and either 1. `T1` contains `T2` among its direct or indirect interfaces, or 2. `T2` is an interface type but `T1` is not an interface type. For example: ```csharp interface IA { void M() { WriteLine("IA.M"); } } interface IB : IA { void IA.M() { WriteLine("IB.M"); } } interface IC : IA { void IA.M() { WriteLine("IC.M"); } } interface ID : IB, IC { } // compiles, but error when a class implements 'ID' abstract class C : IB, IC { } // error: no most specific implementation for 'IA.M' abstract class D : IA, IB, IC // ok { public abstract void M(); } public class E : ID { } // Error. No most specific implementation for 'IA.M' ``` The most specific implementation rule ensures that a conflict (i.e. an ambiguity arising from diamond inheritance) is resolved explicitly by the programmer at the point where the conflict arises. Because we support explicit reabstractions in interfaces, we could do so in classes as well ```csharp abstract class E : IA, IB, IC // ok { abstract void IA.M(); } ``` > ***Closed issue***: should we support explicit interface abstract implementations in classes? **Decision: NO** In addition, it is an error if in a class declaration the most specific implementation of some interface method is an abstract implementation that was declared in an interface. This is an existing rule restated using the new terminology. ```csharp interface IF { void M(); } abstract class F : IF { } // error: 'F' does not implement 'IF.M' ``` It is possible for a virtual property declared in an interface to have a most specific implementation for its `get` accessor in one interface and a most specific implementation for its `set` accessor in a different interface. This is considered a violation of the *most specific implementation* rule, and generates a compiler error. ### `static` and `private` methods Because interfaces may now contain executable code, it is useful to abstract common code into private and static methods. We now permit these in interfaces. > ***Closed issue***: Should we support private methods? Should we support static methods? **Decision: YES** > ***Open issue***: should we permit interface methods to be `protected` or `internal` or other access? If so, what are the semantics? Are they `virtual` by default? If so, is there a way to make them non-virtual? > ***Closed issue***: If we support static methods, should we support (static) operators? **Decision: YES** ### Base interface invocations The syntax in this section hasn't been implemented. It remains an active proposal.
Code in a type that derives from an interface with a default method can explicitly invoke that interface's "base" implementation. ```csharp interface I0 { void M() { Console.WriteLine("I0"); } } interface I1 : I0 { override void M() { Console.WriteLine("I1"); } } interface I2 : I0 { override void M() { Console.WriteLine("I2"); } } interface I3 : I1, I2 { // an explicit override that invoke's a base interface's default method void I0.M() { I2.base.M(); } } ``` An instance (nonstatic) method is permitted to invoke the implementation of an accessible instance method in a direct base interface nonvirtually by naming it using the syntax `base(Type).M`. This is useful when an override that is required to be provided due to diamond inheritance is resolved by delegating to one particular base implementation. ```csharp interface IA { void M() { WriteLine("IA.M"); } } interface IB : IA { override void IA.M() { WriteLine("IB.M"); } } interface IC : IA { override void IA.M() { WriteLine("IC.M"); } } class D : IA, IB, IC { void IA.M() { base(IB).M(); } } ``` When a `virtual` or `abstract` member is accessed using the syntax `base(Type).M`, it is required that `Type` contains a unique *most specific override* for `M`.
### Binding base clauses Interfaces now contain types. These types may be used in the base clause as base interfaces. When binding a base clause, we may need to know the set of base interfaces to bind those types (e.g. to lookup in them and to resolve protected access). The meaning of an interface's base clause is thus circularly defined. To break the cycle, we add a new language rules corresponding to a similar rule already in place for classes. While determining the meaning of the *interface_base* of an interface, the base interfaces are temporarily assumed to be empty. Intuitively this ensures that the meaning of a base clause cannot recursively depend on itself. **We used to have the following rules:** "When a class B derives from a class A, it is a compile-time error for A to depend on B. A class **directly depends on** its direct base class (if any) and **directly depends on** the ~~**class**~~ within which it is immediately nested (if any). Given this definition, the complete set of ~~**classes**~~ upon which a class depends is the reflexive and transitive closure of the **directly depends on** relationship." It is a compile-time error for an interface to directly or indirectly inherit from itself. The **base interfaces** of an interface are the explicit base interfaces and their base interfaces. In other words, the set of base interfaces is the complete transitive closure of the explicit base interfaces, their explicit base interfaces, and so on. **We are adjusting them as follows:** When a class B derives from a class A, it is a compile-time error for A to depend on B. A class **directly depends on** its direct base class (if any) and **directly depends on** the _**type**_ within which it is immediately nested (if any). When an interface IB extends an interface IA, it is a compile-time error for IA to depend on IB. An interface **directly depends on** its direct base interfaces (if any) and **directly depends on** the type within which it is immediately nested (if any). Given these definitions, the complete set of **types** upon which a type depends is the reflexive and transitive closure of the **directly depends on** relationship. ### Effect on existing programs The rules presented here are intended to have no effect on the meaning of existing programs. Example 1: ```csharp interface IA { void M(); } class C: IA // Error: IA.M has no concrete most specific override in C { public static void M() { } // method unrelated to 'IA.M' because static } ``` Example 2: ```csharp interface IA { void M(); } class Base: IA { void IA.M() { } } class Derived: Base, IA // OK, all interface members have a concrete most specific override { private void M() { } // method unrelated to 'IA.M' because private } ``` The same rules give similar results to the analogous situation involving default interface methods: ```csharp interface IA { void M() { } } class Derived: IA // OK, all interface members have a concrete most specific override { private void M() { } // method unrelated to 'IA.M' because private } ``` > ***Closed issue***: confirm that this is an intended consequence of the specification. **Decision: YES** ### Runtime method resolution > ***Closed Issue:*** The spec should describe the runtime method resolution algorithm in the face of interface default methods. We need to ensure that the semantics are consistent with the language semantics, e.g. which declared methods do and do not override or implement an `internal` method. ### CLR support API In order for compilers to detect when they are compiling for a runtime that supports this feature, libraries for such runtimes are modified to advertise that fact through the API discussed in . We add ```csharp namespace System.Runtime.CompilerServices { public static class RuntimeFeature { // Presence of the field indicates runtime support public const string DefaultInterfaceImplementation = nameof(DefaultInterfaceImplementation); } } ``` > ***Open issue***: Is that the best name for the *CLR* feature? The CLR feature does much more than just that (e.g. relaxes protection constraints, supports overrides in interfaces, etc). Perhaps it should be called something like "concrete methods in interfaces", or "traits"? ### Further areas to be specified - [ ] It would be useful to catalog the kinds of source and binary compatibility effects caused by adding default interface methods and overrides to existing interfaces. ## Drawbacks [drawbacks]: #drawbacks This proposal requires a coordinated update to the CLR specification (to support concrete methods in interfaces and method resolution). It is therefore fairly "expensive" and it may be worth doing in combination with other features that we also anticipate would require CLR changes. ## Alternatives [alternatives]: #alternatives None. ## Unresolved questions [unresolved]: #unresolved-questions - Open questions are called out throughout the proposal, above. - See also for a list of open questions. - The detailed specification must describe the resolution mechanism used at runtime to select the precise method to be invoked. - The interaction of metadata produced by new compilers and consumed by older compilers needs to be worked out in detail. For example, we need to ensure that the metadata representation that we use does not cause the addition of a default implementation in an interface to break an existing class that implements that interface when compiled by an older compiler. This may affect the metadata representation that we can use. - The design must consider interoperation with other languages and existing compilers for other languages. ## Resolved Questions ### Abstract Override The earlier draft spec contained the ability to "reabstract" an inherited method: ```csharp interface IA { void M(); } interface IB : IA { override void M() { } } interface IC : IB { override void M(); // make it abstract again } ``` My notes for 2017-03-20 showed that we decided not to allow this. However, there are at least two use cases for it: 1. The Java APIs, with which some users of this feature hope to interoperate, depend on this facility. 2. Programming with *traits* benefits from this. Reabstraction is one of the elements of the "traits" language feature (https://en.wikipedia.org/wiki/Trait_(computer_programming)). The following is permitted with classes: ```csharp public abstract class Base { public abstract void M(); } public abstract class A : Base { public override void M() { } } public abstract class B : A { public override abstract void M(); // reabstract Base.M } ``` Unfortunately this code cannot be refactored as a set of interfaces (traits) unless this is permitted. By the *Jared principle of greed*, it should be permitted. > ***Closed issue:*** Should reabstraction be permitted? [YES] My notes were wrong. The [LDM notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-03-21.md) say that reabstraction is permitted in an interface. Not in a class. ### Virtual Modifier vs Sealed Modifier From [Aleksey Tsingauz](https://github.com/AlekseyTs): > We decided to allow modifiers explicitly stated on interface members, unless there is a reason to disallow some of them. This brings an interesting question around virtual modifier. Should it be required on members with default implementation? > > We could say that: > > - if there is no implementation and neither virtual, nor sealed are specified, we assume the member is abstract. > - if there is an implementation and neither abstract, nor sealed are specified, we assume the member is virtual. > - sealed modifier is required to make a method neither virtual, nor abstract. > > Alternatively, we could say that virtual modifier is required for a virtual member. I.e, if there is a member with implementation not explicitly marked with virtual modifier, it is neither virtual, nor abstract. This approach might provide better experience when a method is moved from a class to an interface: > > - an abstract method stays abstract. > - a virtual method stays virtual. > - a method without any modifier stays neither virtual, nor abstract. > - sealed modifier cannot be applied to a method that is not an override. > > What do you think? > ***Closed Issue:*** Should a concrete method (with implementation) be implicitly `virtual`? [YES] ***Decisions:*** Made in the LDM 2017-04-05: 1. non-virtual should be explicitly expressed through `sealed` or `private`. 2. `sealed` is the keyword to make interface instance members with bodies non-virtual 3. We want to allow all modifiers in interfaces 4. Default accessibility for interface members is public, including nested types 5. private function members in interfaces are implicitly sealed, and `sealed` is not permitted on them. 6. Private classes (in interfaces) are permitted and can be sealed, and that means sealed in the class sense of sealed. 7. Absent a good proposal, partial is still not allowed on interfaces or their members. ### Binary Compatibility 1 When a library provides a default implementation ```csharp interface I1 { void M() { Impl1 } } interface I2 : I1 { } class C : I2 { } ``` We understand that the implementation of `I1.M` in `C` is `I1.M`. What if the assembly containing `I2` is changed as follows and recompiled ```csharp interface I2 : I1 { override void M() { Impl2 } } ``` but `C` is not recompiled. What happens when the program is run? An invocation of `(C as I1).M()` 1. Runs `I1.M` 2. Runs `I2.M` 3. Throws some kind of runtime error ***Decision:*** Made 2017-04-11: Runs `I2.M`, which is the unambiguously most specific override at runtime. ### Event accessors (closed) > ***Closed Issue:*** Can an event be overridden "piecewise"? Consider this case: ```csharp public interface I1 { event T e1; } public interface I2 : I1 { override event T { add { } // error: "remove" accessor missing } } ``` This "partial" implementation of the event is not permitted because, as in a class, the syntax for an event declaration does not permit only one accessor; both (or neither) must be provided. You could accomplish the same thing by permitting the abstract remove accessor in the syntax to be implicitly abstract by the absence of a body: ```csharp public interface I1 { event T e1; } public interface I2 : I1 { override event T { add { } remove; // implicitly abstract } } ``` Note that *this is a new (proposed) syntax*. In the current grammar, event accessors have a mandatory body. > ***Closed Issue:*** Can an event accessor be (implicitly) abstract by the omission of a body, similarly to the way that methods in interfaces and property accessors are (implicitly) abstract by the omission of a body? ***Decision:*** (2017-04-18) No, event declarations require both concrete accessors (or neither). ### Reabstraction in a Class (closed) ***Closed Issue:*** We should confirm that this is permitted (otherwise adding a default implementation would be a breaking change): ```csharp interface I1 { void M() { } } abstract class C : I1 { public abstract void M(); // implement I1.M with an abstract method in C } ``` ***Decision:*** (2017-04-18) Yes, adding a body to an interface member declaration shouldn't break C. ### Sealed Override (closed) The previous question implicitly assumes that the `sealed` modifier can be applied to an `override` in an interface. This contradicts the draft specification. Do we want to permit sealing an override? Source and binary compatibility effects of sealing should be considered. > ***Closed Issue:*** Should we permit sealing an override? ***Decision:*** (2017-04-18) Let's not allow `sealed` on overrides in interfaces. The only use of `sealed` on interface members is to make them non-virtual in their initial declaration. ### Diamond inheritance and classes (closed) The draft of the proposal prefers class overrides to interface overrides in diamond inheritance scenarios: > We require that every interface and class have a *most specific override* for every interface method among the overrides appearing in the type or its direct and indirect interfaces. The *most specific override* is a unique override that is more specific than every other override. If there is no override, the method itself is considered the most specific override. > > One override `M1` is considered *more specific* than another override `M2` if `M1` is declared on type `T1`, `M2` is declared on type `T2`, and either > > 1. `T1` contains `T2` among its direct or indirect interfaces, or > 2. `T2` is an interface type but `T1` is not an interface type. The scenario is this ```csharp interface IA { void M(); } interface IB : IA { override void M() { WriteLine("IB"); } } class Base : IA { void IA.M() { WriteLine("Base"); } } class Derived : Base, IB // allowed? { static void Main() { IA a = new Derived(); a.M(); // what does it do? } } ``` We should confirm this behavior (or decide otherwise) > ***Closed Issue:*** Confirm the draft spec, above, for *most specific override* as it applies to mixed classes and interfaces (a class takes priority over an interface). See . ### Interface methods vs structs (closed) There are some unfortunate interactions between default interface methods and structs. ```csharp interface IA { public void M() { } } struct S : IA { } ``` Note that interface members are not inherited: ```csharp var s = default(S); s.M(); // error: 'S' does not contain a member 'M' ``` Consequently, the client must box the struct to invoke interface methods ```csharp IA s = default(S); // an S, boxed s.M(); // ok ``` Boxing in this way defeats the principal benefits of a `struct` type. Moreover, any mutation methods will have no apparent effect, because they are operating on a *boxed copy* of the struct: ```csharp interface IB { public void Increment() { P += 1; } public int P { get; set; } } struct T : IB { public int P { get; set; } // auto-property } T t = default(T); Console.WriteLine(t.P); // prints 0 (t as IB).Increment(); Console.WriteLine(t.P); // prints 0 ``` > ***Closed Issue:*** What can we do about this: > > 1. Forbid a `struct` from inheriting a default implementation. All interface methods would be treated as abstract in a `struct`. Then we may take time later to decide how to make it work better. > 2. Come up with some kind of code generation strategy that avoids boxing. Inside a method like `IB.Increment`, the type of `this` would perhaps be akin to a type parameter constrained to `IB`. In conjunction with that, to avoid boxing in the caller, non-abstract methods would be inherited from interfaces. This may increase compiler and CLR implementation work substantially. > 3. Not worry about it and just leave it as a wart. > 4. Other ideas? ***Decision:*** Not worry about it and just leave it as a wart. See . ### Base interface invocations (closed) This decision was not implemented in C# 8. The `base(Interface).M()` syntax is not implemented.
The draft spec suggests a syntax for base interface invocations inspired by Java: `Interface.base.M()`. We need to select a syntax, at least for the initial prototype. My favorite is `base.M()`. > ***Closed Issue:*** What is the syntax for a base member invocation? ***Decision:*** The syntax is `base(Interface).M()`. See . The interface so named must be a base interface, but does not need to be a direct base interface. > ***Open Issue:*** Should base interface invocations be permitted in class members? ***Decision***: Yes.
### Overriding non-public interface members (closed) In an interface, non-public members from base interfaces are overridden using the `override` modifier. If it is an "explicit" override that names the interface containing the member, the access modifier is omitted. > ***Closed Issue:*** If it is an "implicit" override that does not name the interface, does the access modifier have to match? ***Decision:*** Only public members may be implicitly overridden, and the access must match. See . > ***Open Issue:*** Is the access modifier required, optional, or omitted on an explicit override such as `override void IB.M() {}`? > ***Open Issue:*** Is `override` required, optional, or omitted on an explicit override such as `void IB.M() {}`? How does one implement a non-public interface member in a class? Perhaps it must be done explicitly? ```csharp interface IA { internal void MI(); protected void MP(); } class C : IA { // are these implementations? Decision: NO internal void MI() {} protected void MP() {} } ``` > ***Closed Issue:*** How does one implement a non-public interface member in a class? ***Decision:*** You can only implement non-public interface members explicitly. See . ***Decision***: No `override` keyword permitted on interface members. ### Binary Compatibility 2 (closed) Consider the following code in which each type is in a separate assembly ```csharp interface I1 { void M() { Impl1 } } interface I2 : I1 { override void M() { Impl2 } } interface I3 : I1 { } class C : I2, I3 { } ``` We understand that the implementation of `I1.M` in `C` is `I2.M`. What if the assembly containing `I3` is changed as follows and recompiled ```csharp interface I3 : I1 { override void M() { Impl3 } } ``` but `C` is not recompiled. What happens when the program is run? An invocation of `(C as I1).M()` 1. Runs `I1.M` 2. Runs `I2.M` 3. Runs `I3.M` 4. Either 2 or 3, deterministically 5. Throws some kind of runtime exception ***Decision***: Throw an exception (5). See . ### Permit `partial` in interface? (closed) Given that interfaces may be used in ways analogous to the way abstract classes are used, it may be useful to declare them `partial`. This would be particularly useful in the face of generators. > ***Proposal:*** Remove the language restriction that interfaces and members of interfaces may not be declared `partial`. ***Decision***: Yes. See . ### `Main` in an interface? (closed) > ***Open Issue:*** Is a `static Main` method in an interface a candidate to be the program's entry point? ***Decision***: Yes. See . ### Confirm intent to support public non-virtual methods (closed) Can we please confirm (or reverse) our decision to permit non-virtual public methods in an interface? ```csharp interface IA { public sealed void M() { } } ``` > ***Semi-Closed Issue:*** (2017-04-18) We think it is going to be useful, but will come back to it. This is a mental model tripping block. ***Decision***: Yes. . ### Does an `override` in an interface introduce a new member? (closed) There are a few ways to observe whether an override declaration introduces a new member or not. ```csharp interface IA { void M(int x) { } } interface IB : IA { override void M(int y) { } // 'override' not permitted } interface IC : IB { static void M2() { M(y: 3); // permitted? Decision: No. } override void IB.M(int z) { } // permitted? What does it override? Decision: No. } ``` > ***Open Issue:*** Does an override declaration in an interface introduce a new member? (closed) In a class, an overriding method is "visible" in some senses. For example, the names of its parameters take precedence over the names of parameters in the overridden method. It may be possible to duplicate that behavior in interfaces, as there is always a most specific override. But do we want to duplicate that behavior? Also, it is possible to "override" an override method? [Moot] ***Decision***: No `override` keyword permitted on interface members. . ### Properties with a private accessor (closed) We say that private members are not virtual, and the combination of virtual and private is disallowed. But what about a property with a private accessor? ```csharp interface IA { public virtual int P { get => 3; private set { } } } ``` Is this allowed? Is the `set` accessor here `virtual` or not? Can it be overridden where it is accessible? Does the following implicitly implement only the `get` accessor? ```csharp class C : IA { public int P { get => 4; set { } } } ``` Is the following presumably an error because IA.P.set isn't virtual and also because it isn't accessible? ```csharp class C : IA { int IA.P { get => 4; set { } // Decision: Not valid } } ``` ***Decision***: The first example looks valid, while the last does not. This is resolved analogously to how it already works in C#. ### Base Interface Invocations, round 2 (closed) This was not implemented in C# 8.
Our previous "resolution" to how to handle base invocations doesn't actually provide sufficient expressiveness. It turns out that in C# and the CLR, unlike Java, you need to specify both the interface containing the method declaration and the location of the implementation you want to invoke. I propose the following syntax for base calls in interfaces. I’m not in love with it, but it illustrates what any syntax must be able to express: ```csharp interface I1 { void M(); } interface I2 { void M(); } interface I3 : I1, I2 { void I1.M() { } void I2.M() { } } interface I4 : I1, I2 { void I1.M() { } void I2.M() { } } interface I5 : I3, I4 { void I1.M() { base(I1).M(); // calls I3's implementation of I1.M base(I1).M(); // calls I4's implementation of I1.M } void I2.M() { base(I2).M(); // calls I3's implementation of I2.M base(I2).M(); // calls I4's implementation of I2.M } } ``` If there is no ambiguity, you can write it more simply ```csharp interface I1 { void M(); } interface I3 : I1 { void I1.M() { } } interface I4 : I1 { void I1.M() { } } interface I5 : I3, I4 { void I1.M() { base.M(); // calls I3's implementation of I1.M base.M(); // calls I4's implementation of I1.M } } ``` Or ```csharp interface I1 { void M(); } interface I2 { void M(); } interface I3 : I1, I2 { void I1.M() { } void I2.M() { } } interface I5 : I3 { void I1.M() { base(I1).M(); // calls I3's implementation of I1.M } void I2.M() { base(I2).M(); // calls I3's implementation of I2.M } } ``` Or ```csharp interface I1 { void M(); } interface I3 : I1 { void I1.M() { } } interface I5 : I3 { void I1.M() { base.M(); // calls I3's implementation of I1.M } } ``` ***Decision***: Decided on `base(N.I1).M(s)`, conceding that if we have an invocation binding there may be problem here later on.
### Warning for struct not implementing default method? (closed) @vancem asserts that we should seriously consider producing a warning if a value type declaration fails to override some interface method, even if it would inherit an implementation of that method from an interface. Because it causes boxing and undermines constrained calls. ***Decision***: This seems like something more suited for an analyzer. It also seems like this warning could be noisy, since it would fire even if the default interface method is never called and no boxing will ever occur. ### Interface static constructors (closed) When are interface static constructors run? The current CLI draft proposes that it occurs when the first static method or field is accessed. If there are neither of those then it might never be run?? [2018-10-09 The CLR team proposes "Going to mirror what we do for valuetypes (cctor check on access to each instance method)"] ***Decision***: Static constructors are also run on entry to instance methods, if the static constructor was not `beforefieldinit`, in which case static constructors are run before access to the first static field. ## Design meetings [2017-03-08 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-03-08.md) [2017-03-21 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-03-21.md) [2017-03-23 meeting "CLR Behavior for Default Interface Methods"](https://github.com/dotnet/csharplang/blob/master/meetings/2017/CLR-2017-03-23.md) [2017-04-05 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-05.md) [2017-04-11 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-11.md) [2017-04-18 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-18.md) [2017-04-19 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-04-19.md) [2017-05-17 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-17.md) [2017-05-31 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-31.md) [2017-06-14 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-06-14.md) [2018-10-17 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md) [2018-11-14 LDM Meeting Notes](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-11-14.md) ================================================ FILE: proposals/csharp-8.0/nested-stackalloc.md ================================================ # Permit `stackalloc` in nested contexts [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Stack allocation We modify the section *Stack allocation* ([§12.8.22 Stack allocation](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12822-stack-allocation)) of the C# language specification to relax the places when a `stackalloc` expression may appear. We delete ``` antlr local_variable_initializer_unsafe : stackalloc_initializer ; stackalloc_initializer : 'stackalloc' unmanaged_type '[' expression ']' ; ``` and replace them with ``` antlr primary_no_array_creation_expression : stackalloc_initializer ; stackalloc_initializer : 'stackalloc' unmanaged_type '[' expression? ']' array_initializer? | 'stackalloc' '[' expression? ']' array_initializer ; ``` Note that the addition of an *array_initializer* to *stackalloc_initializer* (and making the index expression optional) was an extension in C# 7.3 and is not described here. The *element type* of the `stackalloc` expression is the *unmanaged_type* named in the stackalloc expression, if any, or the common type among the elements of the *array_initializer* otherwise. The type of the *stackalloc_initializer* with *element type* `K` depends on its syntactic context: - If the *stackalloc_initializer* appears directly as the *local_variable_initializer* of a *local_variable_declaration* statement or a *for_initializer*, then its type is `K*`. - Otherwise its type is `System.Span`. ## Stackalloc Conversion The *stackalloc conversion* is a new built-in implicit conversion from expression. When the type of a *stackalloc_initializer* is `K*`, there is an implicit *stackalloc conversion* from the *stackalloc_initializer* to the type `System.Span`. ================================================ FILE: proposals/csharp-8.0/notnull-constraint.md ================================================ ## Notnull constraint Champion issue: In C# 8.0, we added a language feature that permits the specification of a new type parameter constraint `notnull`. This is a placeholder for its specification. ================================================ FILE: proposals/csharp-8.0/null-coalescing-assignment.md ================================================ # Null coalescing assignment [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Simplifies a common coding pattern where a variable is assigned a value if it is null. As part of this proposal, we will also loosen the type requirements on `??` to allow an expression whose type is an unconstrained type parameter to be used on the left-hand side. ## Motivation [motivation]: #motivation It is common to see code of the form ```csharp if (variable == null) { variable = expression; } ``` This proposal adds a non-overloadable binary operator to the language that performs this function. There have been at least eight separate community requests for this feature. ## Detailed design [design]: #detailed-design We add a new form of assignment operator ``` antlr assignment_operator : '??=' ; ``` Which follows the existing semantic rules for compound assignment operators ([§12.21.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12214-compound-assignment)), except that we elide the assignment if the left-hand side is non-null. The rules for this feature are as follows. Given `a ??= b`, where `A` is the type of `a`, `B` is the type of `b`, and `A0` is the underlying type of `A` if `A` is a nullable value type: 1. If `A` does not exist or is a non-nullable value type, a compile-time error occurs. 2. If `B` is not implicitly convertible to `A` or `A0` (if `A0` exists), a compile-time error occurs. 3. If `A0` exists and `B` is implicitly convertible to `A0`, and `B` is not dynamic, then the type of `a ??= b` is `A0`. `a ??= b` is evaluated at runtime as: ```C# var tmp = a.GetValueOrDefault(); if (!a.HasValue) { tmp = b; a = tmp; } tmp ``` Except that `a` is only evaluated once. 4. Otherwise, the type of `a ??= b` is `A`. `a ??= b` is evaluated at runtime as `a ?? (a = b)`, except that `a` is only evaluated once. For the relaxation of the type requirements of `??`, we update the spec where it currently states that, given `a ?? b`, where `A` is the type of `a`: > 1. If A exists and is not a nullable type or a reference type, a compile-time error occurs. We relax this requirement to: 1. If A exists and is a non-nullable value type, a compile-time error occurs. This allows the null coalescing operator to work on unconstrained type parameters, as the unconstrained type parameter `T` exists, is not a nullable type, and is not a reference type. ## Drawbacks [drawbacks]: #drawbacks As with any language feature, we must question whether the additional complexity to the language is repaid in the additional clarity offered to the body of C# programs that would benefit from the feature. ## Alternatives [alternatives]: #alternatives The programmer can write `(x = x ?? y)`, `if (x == null) x = y;`, or `x ?? (x = y)` by hand. ## Unresolved questions [unresolved]: #unresolved-questions - [ ] Should we also support `&&=` and `||=` operators? ## Design meetings None. ================================================ FILE: proposals/csharp-8.0/nullable-reference-types-specification.md ================================================ # Nullable Reference Types Specification ***This is a work in progress - several parts are missing or incomplete. An updated version of this document can be found in the C# 9 folder. *** ## Syntax ### Nullable reference types Nullable reference types have the same syntax `T?` as the short form of nullable value types, but do not have a corresponding long form. For the purposes of the specification, the current `nullable_type` production is renamed to `nullable_value_type`, and a `nullable_reference_type` production is added: ```antlr reference_type : ... | nullable_reference_type ; nullable_reference_type : non_nullable_reference_type '?' ; non_nullable_reference_type : type ; ``` The `non_nullable_reference_type` in a `nullable_reference_type` must be a non-nullable reference type (class, interface, delegate or array), or a type parameter that is constrained to be a non-nullable reference type (through the `class` constraint, or a class other than `object`). Nullable reference types cannot occur in the following positions: - as a base class or interface - as the receiver of a `member_access` - as the `type` in an `object_creation_expression` - as the `delegate_type` in a `delegate_creation_expression` - as the `type` in an `is_expression`, a `catch_clause` or a `type_pattern` - as the `interface` in a fully qualified interface member name A warning is given on a `nullable_reference_type` where the nullable annotation context is disabled. ### Nullable class constraint The `class` constraint has a nullable counterpart `class?`: ```antlr primary_constraint : ... | 'class' '?' ; ``` ### The null-forgiving operator The post-fix `!` operator is called the null-forgiving operator. ```antlr primary_expression : ... | null_forgiving_expression ; null_forgiving_expression : primary_expression '!' ; ``` The `primary_expression` must be of a reference type. The postfix `!` operator has no runtime effect - it evaluates to the result of the underlying expression. Its only role is to change the null state of the expression, and to limit warnings given on its use. ### nullable implicitly typed local variables `var` infers an annotated type for reference types. For instance, in `var s = "";` the `var` is inferred as `string?`. ### Nullable compiler directives `#nullable` directives control the nullable annotation and warning contexts. ```antlr pp_directive : ... | pp_nullable ; pp_nullable : whitespace? '#' whitespace? 'nullable' whitespace nullable_action pp_new_line ; nullable_action : 'disable' | 'enable' | 'restore' ; ``` `#pragma warning` directives are expanded to allow changing the nullable warning context, and to allow individual warnings to be enabled on even when they're disabled by default: ```antlr pragma_warning_body : ... | 'warning' whitespace nullable_action whitespace 'nullable' ; warning_action : ... | 'enable' ; ``` Note that the new form of `pragma_warning_body` uses `nullable_action`, not `warning_action`. ## Nullable contexts Every line of source code has a *nullable annotation context* and a *nullable warning context*. These control whether nullable annotations have effect, and whether nullability warnings are given. The annotation context of a given line is either *disabled* or *enabled*. The warning context of a given line is either *disabled* or *enabled*. Both contexts can be specified at the project level (outside of C# source code), or anywhere within a source file via `#nullable` pre-processor directives. If no project level settings are provided the default is for both contexts to be *disabled*. The `#nullable` directive controls the annotation and warning contexts within the source text, and take precedence over the project-level settings. A directive sets the context(s) it controls for subsequent lines of code, until another directive overrides it, or until the end of the source file. The effect of the directives is as follows: - `#nullable disable`: Sets the nullable annotation and warning contexts to *disabled* - `#nullable enable`: Sets the nullable annotation and warning contexts to *enabled* - `#nullable restore`: Restores the nullable annotation and warning contexts to project settings - `#nullable disable annotations`: Sets the nullable annotation context to *disabled* - `#nullable enable annotations`: Sets the nullable annotation context to *enabled* - `#nullable restore annotations`: Restores the nullable annotation context to project settings - `#nullable disable warnings`: Sets the nullable warning context to *disabled* - `#nullable enable warnings`: Sets the nullable warning context to *enabled* - `#nullable restore warnings`: Restores the nullable warning context to project settings ## Nullability of types A given type can have one of four nullabilities: *Oblivious*, *nonnullable*, *nullable* and *unknown*. *Nonnullable* and *unknown* types may cause warnings if a potential `null` value is assigned to them. *Oblivious* and *nullable* types, however, are "*null-assignable*" and can have `null` values assigned to them without warnings. *Oblivious* and *nonnullable* types can be dereferenced or assigned without warnings. Values of *nullable* and *unknown* types, however, are "*null-yielding*" and may cause warnings when dereferenced or assigned without proper null checking. The *default null state* of a null-yielding type is "maybe null". The default null state of a non-null-yielding type is "not null". The kind of type and the nullable annotation context it occurs in determine its nullability: - A nonnullable value type `S` is always *nonnullable* - A nullable value type `S?` is always *nullable* - An unannotated reference type `C` in a *disabled* annotation context is *oblivious* - An unannotated reference type `C` in an *enabled* annotation context is *nonnullable* - A nullable reference type `C?` is *nullable* (but a warning may be yielded in a *disabled* annotation context) Type parameters additionally take their constraints into account: - A type parameter `T` where all constraints (if any) are either null-yielding types (*nullable* and *unknown*) or the `class?` constraint is *unknown* - A type parameter `T` where at least one constraint is either *oblivious* or *nonnullable* or one of the `struct` or `class` constraints is - *oblivious* in a *disabled* annotation context - *nonnullable* in an *enabled* annotation context - A nullable type parameter `T?` where at least one of `T`'s constraints is *oblivious* or *nonnullable* or one of the `struct` or `class` constraints, is - *nullable* in a *disabled* annotation context (but a warning is yielded) - *nullable* in an *enabled* annotation context For a type parameter `T`, `T?` is only allowed if `T` is known to be a value type or known to be a reference type. ### Nested functions Nested functions (lambdas and local functions) are treated like methods, except in regards to their captured variables. The default state of a captured variable inside a lambda or local function is the intersection of the nullable state of the variable at all the "uses" of that nested function. A use of a function is either a call to that function, or where it is converted to a delegate. ### Oblivious vs nonnullable A `type` is deemed to occur in a given annotation context when the last token of the type is within that context. Whether a given reference type `C` in source code is interpreted as oblivious or nonnullable depends on the annotation context of that source code. But once established, it is considered part of that type, and "travels with it" e.g. during substitution of generic type arguments. It is as if there is an annotation like `?` on the type, but invisible. ## Constraints Nullable reference types can be used as generic constraints. Furthermore `object` is now valid as an explicit constraint. Absence of a constraint is now equivalent to an `object?` constraint (instead of `object`), but (unlike `object` before) `object?` is not prohibited as an explicit constraint. `class?` is a new constraint denoting "possibly nullable reference type", whereas `class` denotes "nonnullable reference type". The nullability of a type argument or of a constraint does not impact whether the type satisfies the constraint, except where that is already the case today (nullable value types do not satisfy the `struct` constraint). However, if the type argument does not satisfy the nullability requirements of the constraint, a warning may be given. ## Null state and null tracking Every expression in a given source location has a *null state*, which indicated whether it is believed to potentially evaluate to null. The null state is either "not null" or "maybe null". The null state is used to determine whether a warning should be given about null-unsafe conversions and dereferences. ### Null tracking for variables For certain expressions denoting variables or properties, the null state is tracked between occurrences, based on assignments to them, tests performed on them and the control flow between them. This is similar to how definite assignment is tracked for variables. The tracked expressions are the ones of the following form: ```antlr tracked_expression : simple_name | this | base | tracked_expression '.' identifier ; ``` Where the identifiers denote fields or properties. ***Describe null state transitions similar to definite assignment*** ### Null state for expressions The null state of an expression is derived from its form and type, and from the null state of variables involved in it. ### Literals The null state of a `null` literal is "maybe null". The null state of a `default` literal that is being converted to a type that is known not to be a nonnullable value type is "maybe null". The null state of any other literal is "not null". ### Simple names If a `simple_name` is not classified as a value, its null state is "not null". Otherwise it is a tracked expression, and its null state is its tracked null state at this source location. ### Member access If a `member_access` is not classified as a value, its null state is "not null". Otherwise, if it is a tracked expression, its null state is its tracked null state at this source location. Otherwise its null state is the default null state of its type. ### Invocation expressions If an `invocation_expression` invokes a member that is declared with one or more attributes for special null behavior, the null state is determined by those attributes. Otherwise the null state of the expression is the default null state of its type. ### Element access If an `element_access` invokes an indexer that is declared with one or more attributes for special null behavior, the null state is determined by those attributes. Otherwise the null state of the expression is the default null state of its type. ### Base access If `B` denotes the base type of the enclosing type, `base.I` has the same null state as `((B)this).I` and `base[E]` has the same null state as `((B)this)[E]`. ### Default expressions `default(T)` has the null state "non-null" if `T` is known to be a nonnullable value type. Otherwise it has the null state "maybe null". ### Null-conditional expressions A `null_conditional_expression` has the null state "maybe null". ### Cast expressions If a cast expression `(T)E` invokes a user-defined conversion, then the null state of the expression is the default null state for its type. Otherwise, if `T` is null-yielding (*nullable* or *unknown*) then the null state is "maybe null". Otherwise the null state is the same as the null state of `E`. ### Await expressions The null state of `await E` is the default null state of its type. ### The `as` operator An `as` expression has the null state "maybe null". ### The null-coalescing operator `E1 ?? E2` has the same null state as `E2` ### The conditional operator The null state of `E1 ? E2 : E3` is "not null" if the null state of both `E2` and `E3` are "not null". Otherwise it is "maybe null". ### Query expressions The null state of a query expression is the default null state of its type. ### Assignment operators `E1 = E2` and `E1 op= E2` have the same null state as `E2` after any implicit conversions have been applied. ### Unary and binary operators If a unary or binary operator invokes an user-defined operator that is declared with one or more attributes for special null behavior, the null state is determined by those attributes. Otherwise the null state of the expression is the default null state of its type. ***Something special to do for binary `+` over strings and delegates?*** ### Expressions that propagate null state `(E)`, `checked(E)` and `unchecked(E)` all have the same null state as `E`. ### Expressions that are never null The null state of the following expression forms is always "not null": - `this` access - interpolated strings - `new` expressions (object, delegate, anonymous object and array creation expressions) - `typeof` expressions - `nameof` expressions - anonymous functions (anonymous methods and lambda expressions) - null-forgiving expressions - `is` expressions ## Type inference ### Type inference for `var` The type inferred for local variables declared with `var` is informed by the null state of the initializing expression. ```csharp var x = E; ``` If the type of `E` is a nullable reference type `C?` and the null state of `E` is "not null" then the type inferred for `x` is `C`. Otherwise, the inferred type is the type of `E`. The nullability of the type inferred for `x` is determined as described above, based on the annotation context of the `var`, just as if the type had been given explicitly in that position. ### Type inference for `var?` The type inferred for local variables declared with `var?` is independent of the null state of the initializing expression. ```csharp var? x = E; ``` If the type `T` of `E` is a nullable value type or a nullable reference type then the type inferred for `x` is `T`. Otherwise, if `T` is a nonnullable value type `S` the type inferred is `S?`. Otherwise, if `T` is a nonnullable reference type `C` the type inferred is `C?`. Otherwise, the declaration is illegal. The nullability of the type inferred for `x` is always *nullable*. ### Generic type inference Generic type inference is enhanced to help decide whether inferred reference types should be nullable or not. This is a best effort, and does not in and of itself yield warnings, but may lead to nullable warnings when the inferred types of the selected overload are applied to the arguments. The type inference does not rely on the annotation context of incoming types. Instead a `type` is inferred which acquires its own annotation context from where it "would have been" if it had been expressed explicitly. This underscores the role of type inference as a convenience for what you could have written yourself. More precisely, the annotation context for an inferred type argument is the context of the token that would have been followed by the `<...>` type parameter list, had there been one; i.e. the name of the generic method being called. For query expressions that translate to such calls, the context is taken from the initial contextual keyword of the query clause from which the call is generated. ### The first phase Nullable reference types flow into the bounds from the initial expressions, as described below. In addition, two new kinds of bounds, namely `null` and `default` are introduced. Their purpose is to carry through occurrences of `null` or `default` in the input expressions, which may cause an inferred type to be nullable, even when it otherwise wouldn't. This works even for nullable *value* types, which are enhanced to pick up "nullness" in the inference process. The determination of what bounds to add in the first phase are enhanced as follows: If an argument `Ei` has a reference type, the type `U` used for inference depends on the null state of `Ei` as well as its declared type: - If the declared type is a nonnullable reference type `U0` or a nullable reference type `U0?` then - if the null state of `Ei` is "not null" then `U` is `U0` - if the null state of `Ei` is "maybe null" then `U` is `U0?` - Otherwise if `Ei` has a declared type, `U` is that type - Otherwise if `Ei` is `null` then `U` is the special bound `null` - Otherwise if `Ei` is `default` then `U` is the special bound `default` - Otherwise no inference is made. ### Exact, upper-bound and lower-bound inferences In inferences *from* the type `U` *to* the type `V`, if `V` is a nullable reference type `V0?`, then `V0` is used instead of `V` in the following clauses. - If `V` is one of the unfixed type variables, `U` is added as an exact, upper or lower bound as before - Otherwise, if `U` is `null` or `default`, no inference is made - Otherwise, if `U` is a nullable reference type `U0?`, then `U0` is used instead of `U` in the subsequent clauses. The essence is that nullability that pertains directly to one of the unfixed type variables is preserved into its bounds. For the inferences that recurse further into the source and target types, on the other hand, nullability is ignored. It may or may not match, but if it doesn't, a warning will be issued later if the overload is chosen and applied. ### Fixing The spec currently does not do a good job of describing what happens when multiple bounds are identity convertible to each other, but are different. This may happen between `object` and `dynamic`, between tuple types that differ only in element names, between types constructed thereof and now also between `C` and `C?` for reference types. In addition we need to propagate "nullness" from the input expressions to the result type. To handle these we add more phases to fixing, which is now: 1. Gather all the types in all the bounds as candidates, removing `?` from all that are nullable reference types 2. Eliminate candidates based on requirements of exact, lower and upper bounds (keeping `null` and `default` bounds) 3. Eliminate candidates that do not have an implicit conversion to all the other candidates 4. If the remaining candidates do not all have identity conversions to one another, then type inference fails 5. *Merge* the remaining candidates as described below 6. If the resulting candidate is a reference type or a nonnullable value type and *all* of the exact bounds or *any* of the lower bounds are nullable value types, nullable reference types, `null` or `default`, then `?` is added to the resulting candidate, making it a nullable value type or reference type. *Merging* is described between two candidate types. It is transitive and commutative, so the candidates can be merged in any order with the same ultimate result. It is undefined if the two candidate types are not identity convertible to each other. The *Merge* function takes two candidate types and a direction (*+* or *-*): - *Merge*(`T`, `T`, *d*) = T - *Merge*(`S`, `T?`, *+*) = *Merge*(`S?`, `T`, *+*) = *Merge*(`S`, `T`, *+*)`?` - *Merge*(`S`, `T?`, *-*) = *Merge*(`S?`, `T`, *-*) = *Merge*(`S`, `T`, *-*) - *Merge*(`C`, `C`, *+*) = `C<`*Merge*(`S1`, `T1`, *d1*)`,...,`*Merge*(`Sn`, `Tn`, *dn*)`>`, *where* - `di` = *+* if the `i`'th type parameter of `C<...>` is covariant - `di` = *-* if the `i`'th type parameter of `C<...>` is contra- or invariant - *Merge*(`C`, `C`, *-*) = `C<`*Merge*(`S1`, `T1`, *d1*)`,...,`*Merge*(`Sn`, `Tn`, *dn*)`>`, *where* - `di` = *-* if the `i`'th type parameter of `C<...>` is covariant - `di` = *+* if the `i`'th type parameter of `C<...>` is contra- or invariant - *Merge*(`(S1 s1,..., Sn sn)`, `(T1 t1,..., Tn tn)`, *d*) = `(`*Merge*(`S1`, `T1`, *d*)`n1,...,`*Merge*(`Sn`, `Tn`, *d*) `nn)`, *where* - `ni` is absent if `si` and `ti` differ, or if both are absent - `ni` is `si` if `si` and `ti` are the same - *Merge*(`object`, `dynamic`) = *Merge*(`dynamic`, `object`) = `dynamic` ## Warnings ### Potential null assignment ### Potential null dereference ### Constraint nullability mismatch ### Nullable types in disabled annotation context ## Attributes for special null behavior ================================================ FILE: proposals/csharp-8.0/nullable-reference-types.md ================================================ # Nullable reference types in C# # [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: The goal of this feature is to: * Allow developers to express whether a variable, parameter or result of a reference type is intended to be null or not. * Provide warnings when such variables, parameters and results are not used according to that intent. ## Expression of intent The language already contains the `T?` syntax for value types. It is straightforward to extend this syntax to reference types. It is assumed that the intent of an unadorned reference type `T` is for it to be non-null. ## Checking of nullable references A flow analysis tracks nullable reference variables. Where the analysis deems that they would not be null (e.g. after a check or an assignment), their value will be considered a non-null reference. A nullable reference can also explicitly be treated as non-null with the postfix `x!` operator (the "damnit" operator), for when flow analysis cannot establish a non-null situation that the developer knows is there. Otherwise, a warning is given if a nullable reference is dereferenced, or is converted to a non-null type. A warning is given when converting from `S[]` to `T?[]` and from `S?[]` to `T[]`. A warning is given when converting from `C` to `C` except when the type parameter is covariant (`out`), and when converting from `C` to `C` except when the type parameter is contravariant (`in`). A warning is given on `C` if the type parameter has non-null constraints. ## Checking of non-null references A warning is given if a null literal is assigned to a non-null variable or passed as a non-null parameter. A warning is also given if a constructor does not explicitly initialize non-null reference fields. We cannot adequately track that all elements of an array of non-null references are initialized. However, we could issue a warning if no element of a newly created array is assigned to before the array is read from or passed on. That might handle the common case without being too noisy. We need to decide whether `default(T)` generates a warning, or is simply treated as being of the type `T?`. ## Metadata representation Nullability adornments should be represented in metadata as attributes. This means that downlevel compilers will ignore them. We need to decide if only nullable annotations are included, or there's also some indication of whether non-null was "on" in the assembly. ## Generics If a type parameter `T` has non-nullable constraints, it is treated as non-nullable within its scope. If a type parameter is unconstrained or has only nullable constraints, the situation is a little more complex: this means that the corresponding type argument could be *either* nullable or non-nullable. The safe thing to do in that situation is to treat the type parameter as *both* nullable and non-nullable, giving warnings when either is violated. It is worth considering whether explicit nullable reference constraints should be allowed. Note, however, that we cannot avoid having nullable reference types *implicitly* be constraints in certain cases (inherited constraints). The `class` constraint is non-null. We can consider whether `class?` should be a valid nullable constraint denoting "nullable reference type". ## Type inference In type inference, if a contributing type is a nullable reference type, the resulting type should be nullable. In other words, nullness is propagated. We should consider whether the `null` literal as a participating expression should contribute nullness. It doesn't today: for value types it leads to an error, whereas for reference types the null successfully converts to the plain type. ```csharp string? n = "world"; var x = b ? "Hello" : n; // string? var y = b ? "Hello" : null; // string? or error var z = b ? 7 : null; // Error today, could be int? ``` ## Null guard guidance As a feature, nullable reference types allow developers to express their intent, and provide warnings through flow analysis if that intent is contradicted. There is a common question as to whether or not null guards are necessary. ### Example of null guard ```csharp public void DoWork(Worker worker) { // Guard against worker being null if (worker is null) { throw new ArgumentNullException(nameof(worker)); } // Otherwise use worker argument } ``` In the previous example, the `DoWork` function accepts a `Worker` and guards against it potentially being `null`. If the `worker` argument is `null`, the `DoWork` function will `throw`. With nullable reference types, the code in the previous example makes the intent that the `Worker` parameter would *not* be `null`. If the `DoWork` function was a public API, such as a NuGet package or a shared library - as guidance you should leave null guards in place. As a public API, the only guarantee that a caller isn't passing `null` is to guard against it. ### Express intent A more compelling use of the previous example is to express that the `Worker` parameter could be `null`, thus making the null guard more appropriate. If you remove the null guard in the following example, the compiler warns that you may be dereferencing null. Regardless, both null guards are still valid. ```csharp public void DoWork(Worker? worker) { // Guard against worker being null if (worker is null) { throw new ArgumentNullException(nameof(worker)); } // Otherwise use worker argument } ``` For non-public APIs, such as source code entirely in control by a developer or dev team - the nullable reference types could allow for the safe removal of null guards where the developers can guarantee it is not necessary. The feature can help with warnings, but it cannot guarantee that at runtime code execution could result in a `NullReferenceException`. ## Breaking changes Non-null warnings are an obvious breaking change on existing code, and should be accompanied with an opt-in mechanism. Less obviously, warnings from nullable types (as described above) are a breaking change on existing code in certain scenarios where the nullability is implicit: * Unconstrained type parameters will be treated as implicitly nullable, so assigning them to `object` or accessing e.g. `ToString` will yield warnings. * if type inference infers nullness from `null` expressions, then existing code will sometimes yield nullable rather than non-nullable types, which can lead to new warnings. So nullable warnings also need to be optional Finally, adding annotations to an existing API will be a breaking change to users who have opted in to warnings, when they upgrade the library. This, too, merits the ability to opt in or out. "I want the bug fixes, but I am not ready to deal with their new annotations" In summary, you need to be able to opt in/out of: * Nullable warnings * Non-null warnings * Warnings from annotations in other files The granularity of the opt-in suggests an analyzer-like model, where swaths of code can opt in and out with pragmas and severity levels can be chosen by the user. Additionally, per-library options ("ignore the annotations from JSON.NET until I'm ready to deal with the fall out") may be expressible in code as attributes. The design of the opt-in/transition experience is crucial to the success and usefulness of this feature. We need to make sure that: * Users can adopt nullability checking gradually as they want to * Library authors can add nullability annotations without fear of breaking customers * Despite these, there is not a sense of "configuration nightmare" ## Tweaks We could consider not using the `?` annotations on locals, but just observing whether they are used in accordance with what gets assigned to them. I don't favor this; I think we should uniformly let people express their intent. We could consider a shorthand `T! x` on parameters, that auto-generates a runtime null check. Certain patterns on generic types, such as `FirstOrDefault` or `TryGet`, have slightly weird behavior with non-nullable type arguments, because they explicitly yield default values in certain situations. We could try to nuance the type system to accommodate these better. For instance, we could allow `?` on unconstrained type parameters, even though the type argument could already be nullable. I doubt that it is worth it, and it leads to weirdness related to interaction with nullable *value* types. ## Nullable value types We could consider adopting some of the above semantics for nullable value types as well. We already mentioned type inference, where we could infer `int?` from `(7, null)`, instead of just giving an error. Another opportunity is to apply the flow analysis to nullable value types. When they are deemed non-null, we could actually allow using as the non-nullable type in certain ways (e.g. member access). We just have to be careful that the things that you can *already* do on a nullable value type will be preferred, for back compat reasons. ================================================ FILE: proposals/csharp-8.0/obsolete-accessor.md ================================================ ## Obsolete on property accessor Champion issue: In C# 8.0, we added support for declaring a property accessor `[Obsolete]`. This is a placeholder for the specification. ================================================ FILE: proposals/csharp-8.0/patterns.md ================================================ # Recursive Pattern Matching [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Pattern matching extensions for C# enable many of the benefits of algebraic data types and pattern matching from functional languages, but in a way that smoothly integrates with the feel of the underlying language. Elements of this approach are inspired by related features in the programming languages [F#](https://www.microsoft.com/research/wp-content/uploads/2016/02/p29-syme.pdf "Extensible Pattern Matching Via a Lightweight Language") and [Scala](https://link.springer.com/content/pdf/10.1007%2F978-3-540-73589-2.pdf "Matching Objects With Patterns, page 273"). ## Detailed design [design]: #detailed-design ### Is Expression The `is` operator is extended to test an expression against a *pattern*. ```antlr relational_expression : is_pattern_expression ; is_pattern_expression : relational_expression 'is' pattern ; ``` This form of *relational_expression* is in addition to the existing forms in the C# specification. It is a compile-time error if the *relational_expression* to the left of the `is` token does not designate a value or does not have a type. Every *identifier* of the pattern introduces a new local variable that is *definitely assigned* after the `is` operator is `true` (i.e. *definitely assigned when true*). > Note: There is technically an ambiguity between *type* in an `is-expression` and *constant_pattern*, either of which might be a valid parse of a qualified identifier. We try to bind it as a type for compatibility with previous versions of the language; only if that fails do we resolve it as we do an expression in other contexts, to the first thing found (which must be either a constant or a type). This ambiguity is only present on the right-hand-side of an `is` expression. ### Patterns Patterns are used in the *is_pattern* operator, in a *switch_statement*, and in a *switch_expression* to express the shape of data against which incoming data (which we call the input value) is to be compared. Patterns may be recursive so that parts of the data may be matched against sub-patterns. ```antlr pattern : declaration_pattern | constant_pattern | var_pattern | positional_pattern | property_pattern | discard_pattern ; declaration_pattern : type simple_designation ; constant_pattern : constant_expression ; var_pattern : 'var' designation ; positional_pattern : type? '(' subpatterns? ')' property_subpattern? simple_designation? ; subpatterns : subpattern | subpattern ',' subpatterns ; subpattern : pattern | identifier ':' pattern ; property_subpattern : '{' '}' | '{' subpatterns ','? '}' ; property_pattern : type? property_subpattern simple_designation? ; simple_designation : single_variable_designation | discard_designation ; discard_pattern : '_' ; ``` #### Declaration Pattern ```antlr declaration_pattern : type simple_designation ; ``` The *declaration_pattern* both tests that an expression is of a given type and casts it to that type if the test succeeds. This may introduce a local variable of the given type named by the given identifier, if the designation is a *single_variable_designation*. That local variable is *definitely assigned* when the result of the pattern-matching operation is `true`. The runtime semantic of this expression is that it tests the runtime type of the left-hand *relational_expression* operand against the *type* in the pattern. If it is of that runtime type (or some subtype) and not `null`, the result of the `is operator` is `true`. Certain combinations of static type of the left-hand-side and the given type are considered incompatible and result in compile-time error. A value of static type `E` is said to be *pattern-compatible* with a type `T` if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from `E` to `T`, or if one of those types is an open type. It is a compile-time error if an input of type `E` is not *pattern-compatible* with the *type* in a type pattern that it is matched with. The type pattern is useful for performing run-time type tests of reference types, and replaces the idiom ```csharp var v = expr as Type; if (v != null) { // code using v ``` With the slightly more concise ```csharp if (expr is Type v) { // code using v ``` It is an error if *type* is a nullable value type. The type pattern can be used to test values of nullable types: a value of type `Nullable` (or a boxed `T`) matches a type pattern `T2 id` if the value is non-null and the type of `T2` is `T`, or some base type or interface of `T`. For example, in the code fragment ```csharp int? x = 3; if (x is int v) { // code using v ``` The condition of the `if` statement is `true` at runtime and the variable `v` holds the value `3` of type `int` inside the block. After the block the variable `v` is in scope but not definitely assigned. #### Constant Pattern ```antlr constant_pattern : constant_expression ; ``` A constant pattern tests the value of an expression against a constant value. The constant may be any constant expression, such as a literal, the name of a declared `const` variable, or an enumeration constant. When the input value is not an open type, the constant expression is implicitly converted to the type of the matched expression; if the type of the input value is not *pattern-compatible* with the type of the constant expression, the pattern-matching operation is an error. The pattern *c* is considered matching the converted input value *e* if `object.Equals(c, e)` would return `true`. We expect to see `e is null` as the most common way to test for `null` in newly written code, as it cannot invoke a user-defined `operator==`. #### Var Pattern ```antlr var_pattern : 'var' designation ; designation : simple_designation | tuple_designation ; simple_designation : single_variable_designation | discard_designation ; single_variable_designation : identifier ; discard_designation : _ ; tuple_designation : '(' designations? ')' ; designations : designation | designations ',' designation ; ``` If the *designation* is a *simple_designation*, an expression *e* matches the pattern. In other words, a match to a *var pattern* always succeeds with a *simple_designation*. If the *simple_designation* is a *single_variable_designation*, the value of *e* is bound to a newly introduced local variable. The type of the local variable is the static type of *e*. If the *designation* is a *tuple_designation*, then the pattern is equivalent to a *positional_pattern* of the form `(var` *designation*, ... `)` where the *designation*s are those found within the *tuple_designation*. For example, the pattern `var (x, (y, z))` is equivalent to `(var x, (var y, var z))`. It is an error if the name `var` binds to a type. #### Discard Pattern ```antlr discard_pattern : '_' ; ``` An expression *e* matches the pattern `_` always. In other words, every expression matches the discard pattern. A discard pattern may not be used as the pattern of an *is_pattern_expression*. #### Positional Pattern A positional pattern checks that the input value is not `null`, invokes an appropriate `Deconstruct` method, and performs further pattern matching on the resulting values. It also supports a tuple-like pattern syntax (without the type being provided) when the type of the input value is the same as the type containing `Deconstruct`, or if the type of the input value is a tuple type, or if the type of the input value is `object` or `ITuple` and the runtime type of the expression implements `ITuple`. ```antlr positional_pattern : type? '(' subpatterns? ')' property_subpattern? simple_designation? ; subpatterns : subpattern | subpattern ',' subpatterns ; subpattern : pattern | identifier ':' pattern ; ``` If the *type* is omitted, we take it to be the static type of the input value. Given a match of an input value to the pattern *type* `(` *subpattern_list* `)`, a method is selected by searching in *type* for accessible declarations of `Deconstruct` and selecting one among them using the same rules as for the deconstruction declaration. It is an error if a *positional_pattern* omits the type, has a single *subpattern* without an *identifier*, has no *property_subpattern* and has no *simple_designation*. This disambiguates between a *constant_pattern* that is parenthesized and a *positional_pattern*. In order to extract the values to match against the patterns in the list, - If *type* was omitted and the input value's type is a tuple type, then the number of subpatterns is required to be the same as the cardinality of the tuple. Each tuple element is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, then that must name a tuple element at the corresponding position in the tuple type. - Otherwise, if a suitable `Deconstruct` exists as a member of *type*, it is a compile-time error if the type of the input value is not *pattern-compatible* with *type*. At runtime the input value is tested against *type*. If this fails then the positional pattern match fails. If it succeeds, the input value is converted to this type and `Deconstruct` is invoked with fresh compiler-generated variables to receive the `out` parameters. Each value that was received is matched against the corresponding *subpattern*, and the match succeeds if all of these succeed. If any *subpattern* has an *identifier*, then that must name a parameter at the corresponding position of `Deconstruct`. - Otherwise if *type* was omitted, and the input value is of type `object` or `ITuple` or some type that can be converted to `ITuple` by an implicit reference conversion, and no *identifier* appears among the subpatterns, then we match using `ITuple`. - Otherwise the pattern is a compile-time error. The order in which subpatterns are matched at runtime is unspecified, and a failed match may not attempt to match all subpatterns. ##### Example This example uses many of the features described in this specification ``` c# var newState = (GetState(), action, hasKey) switch { (DoorState.Closed, Action.Open, _) => DoorState.Opened, (DoorState.Opened, Action.Close, _) => DoorState.Closed, (DoorState.Closed, Action.Lock, true) => DoorState.Locked, (DoorState.Locked, Action.Unlock, true) => DoorState.Closed, (var state, _, _) => state }; ``` #### Property Pattern A property pattern checks that the input value is not `null` and recursively matches values extracted by the use of accessible properties or fields. ```antlr property_pattern : type? property_subpattern simple_designation? ; property_subpattern : '{' '}' | '{' subpatterns ','? '}' ; ``` It is an error if any _subpattern_ of a _property_pattern_ does not contain an _identifier_ (it must be of the second form, which has an _identifier_). A trailing comma after the last subpattern is optional. Note that a null-checking pattern falls out of a trivial property pattern. To check if the string `s` is non-null, you can write any of the following forms ```csharp if (s is object o) ... // o is of type object if (s is string x) ... // x is of type string if (s is {} x) ... // x is of type string if (s is {}) ... ``` Given a match of an expression *e* to the pattern *type* `{` *property_pattern_list* `}`, it is a compile-time error if the expression *e* is not *pattern-compatible* with the type *T* designated by *type*. If the type is absent, we take it to be the static type of *e*. If the *identifier* is present, it declares a pattern variable of type *type*. Each of the identifiers appearing on the left-hand-side of its *property_pattern_list* must designate an accessible readable property or field of *T*. If the *simple_designation* of the *property_pattern* is present, it defines a pattern variable of type *T*. At runtime, the expression is tested against *T*. If this fails then the property pattern match fails and the result is `false`. If it succeeds, then each *property_subpattern* field or property is read and its value matched against its corresponding pattern. The result of the whole match is `false` only if the result of any of these is `false`. The order in which subpatterns are matched is not specified, and a failed match may not match all subpatterns at runtime. If the match succeeds and the *simple_designation* of the *property_pattern* is a *single_variable_designation*, it defines a variable of type *T* that is assigned the matched value. > Note: The property pattern can be used to pattern-match with anonymous types. ##### Example ```csharp if (o is string { Length: 5 } s) ``` ### Switch Expression A *switch_expression* is added to support `switch`-like semantics for an expression context. The C# language syntax is augmented with the following syntactic productions: ```antlr multiplicative_expression : switch_expression | multiplicative_expression '*' switch_expression | multiplicative_expression '/' switch_expression | multiplicative_expression '%' switch_expression ; switch_expression : range_expression 'switch' '{' '}' | range_expression 'switch' '{' switch_expression_arms ','? '}' ; switch_expression_arms : switch_expression_arm | switch_expression_arms ',' switch_expression_arm ; switch_expression_arm : pattern case_guard? '=>' expression ; case_guard : 'when' null_coalescing_expression ; ``` The *switch_expression* is not permitted as an *expression_statement*. > We are looking at relaxing this in a future revision. The type of the *switch_expression* is the *best common type* ([§12.6.3.15](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#126315-finding-the-best-common-type-of-a-set-of-expressions)) of the expressions appearing to the right of the `=>` tokens of the *switch_expression_arm*s if such a type exists and the expression in every arm of the switch expression can be implicitly converted to that type. In addition, we add a new *switch expression conversion*, which is a predefined implicit conversion from a switch expression to every type `T` for which there exists an implicit conversion from each arm's expression to `T`. It is an error if some *switch_expression_arm*'s pattern cannot affect the result because some previous pattern and guard will always match. A switch expression is said to be *exhaustive* if some arm of the switch expression handles every value of its input. The compiler shall produce a warning if a switch expression is not *exhaustive*. At runtime, the result of the *switch_expression* is the value of the *expression* of the first *switch_expression_arm* for which the expression on the left-hand-side of the *switch_expression* matches the *switch_expression_arm*'s pattern, and for which the *case_guard* of the *switch_expression_arm*, if present, evaluates to `true`. If there is no such *switch_expression_arm*, the *switch_expression* throws an instance of the exception `System.Runtime.CompilerServices.SwitchExpressionException`. ### Optional parens when switching on a tuple literal In order to switch on a tuple literal using the *switch_statement*, you have to write what appear to be redundant parens ```csharp switch ((a, b)) { ``` To permit ```csharp switch (a, b) { ``` the parentheses of the switch statement are optional when the expression being switched on is a tuple literal. ### Order of evaluation in pattern-matching Giving the compiler flexibility in reordering the operations executed during pattern-matching can permit flexibility that can be used to improve the efficiency of pattern-matching. The (unenforced) requirement would be that properties accessed in a pattern, and the Deconstruct methods, are required to be "pure" (side-effect free, idempotent, etc). That doesn't mean that we would add purity as a language concept, only that we would allow the compiler flexibility in reordering operations. **Resolution 2018-04-04 LDM**: confirmed: the compiler is permitted to reorder calls to `Deconstruct`, property accesses, and invocations of methods in `ITuple`, and may assume that returned values are the same from multiple calls. The compiler should not invoke functions that cannot affect the result, and we will be very careful before making any changes to the compiler-generated order of evaluation in the future. ### Some Possible Optimizations The compilation of pattern matching can take advantage of common parts of patterns. For example, if the top-level type test of two successive patterns in a *switch_statement* is the same type, the generated code can skip the type test for the second pattern. When some of the patterns are integers or strings, the compiler can generate the same kind of code it generates for a switch-statement in earlier versions of the language. For more on these kinds of optimizations, see [[Scott and Ramsey (2000)]](https://www.cs.tufts.edu/~nr/cs257/archive/norman-ramsey/match.pdf "When Do Match-Compilation Heuristics Matter?"). ================================================ FILE: proposals/csharp-8.0/ranges.cs ================================================ namespace System { public readonly struct Index { private readonly int _value; public int Value => _value < 0 ? ~_value : _value; public bool FromEnd => _value < 0; public Index(int value, bool fromEnd) { if (value < 0) throw new ArgumentException("Index must not be negative.", nameof(value)); _value = fromEnd ? ~value : value; } public static implicit operator Index(int value) => new Index(value, fromEnd: false); } public readonly struct Range { public Index Start { get; } public Index End { get; } private Range(Index start, Index end) { this.Start = start; this.End = end; } public static Range Create(Index start, Index end) => new Range(start, end); public static Range FromStart(Index start) => new Range(start, new Index(0, fromEnd: true)); public static Range ToEnd(Index end) => new Range(new Index(0, fromEnd: false), end); public static Range All() => new Range(new Index(0, fromEnd: false), new Index(0, fromEnd: true)); } static class Extensions { public static int get_IndexerExtension(this int[] array, Index index) => index.FromEnd ? array[array.Length - index.Value] : array[index.Value]; public static int get_IndexerExtension(this Span span, Index index) => index.FromEnd ? span[span.Length - index.Value] : span[index.Value]; public static char get_IndexerExtension(this string s, Index index) => index.FromEnd ? s[s.Length - index.Value] : s[index.Value]; public static Span get_IndexerExtension(this int[] array, Range range) => array.Slice(range); public static Span get_IndexerExtension(this Span span, Range range) => span.Slice(range); public static string get_IndexerExtension(this string s, Range range) => s.Substring(range); public static Span Slice(this T[] array, Range range) => array.AsSpan().Slice(range); public static Span Slice(this Span span, Range range) { var (start, length) = GetStartAndLength(range, span.Length); return span.Slice(start, length); } public static string Substring(this string s, Range range) { var (start, length) = GetStartAndLength(range, s.Length); return s.Substring(start, length); } private static (int start, int length) GetStartAndLength(Range range, int entityLength) { var start = range.Start.FromEnd ? entityLength - range.Start.Value : range.Start.Value; var end = range.End.FromEnd ? entityLength - range.End.Value : range.End.Value; var length = end - start; return (start, length); } } } ================================================ FILE: proposals/csharp-8.0/ranges.md ================================================ # Ranges [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary This feature is about delivering two new operators that allow constructing `System.Index` and `System.Range` objects, and using them to index/slice collections at runtime. ## Overview ### Well-known types and members To use the new syntactic forms for `System.Index` and `System.Range`, new well-known types and members may be necessary, depending on which syntactic forms are used. To use the "hat" operator (`^`), the following is required ```csharp namespace System { public readonly struct Index { public Index(int value, bool fromEnd); } } ``` To use the `System.Index` type as an argument in an array element access, the following member is required: ```csharp int System.Index.GetOffset(int length); ``` The `..` syntax for `System.Range` will require the `System.Range` type, as well as one or more of the following members: ```csharp namespace System { public readonly struct Range { public Range(System.Index start, System.Index end); public static Range StartAt(System.Index start); public static Range EndAt(System.Index end); public static Range All { get; } } } ``` The `..` syntax allows for either, both, or none of its arguments to be absent. Regardless of the number of arguments, the `Range` constructor is always sufficient for using the `Range` syntax. However, if any of the other members are present and one or more of the `..` arguments are missing, the appropriate member may be substituted. Finally, for a value of type `System.Range` to be used in an array element access expression, the following member must be present: ```csharp namespace System.Runtime.CompilerServices { public static class RuntimeHelpers { public static T[] GetSubArray(T[] array, System.Range range); } } ``` ### System.Index C# has no way of indexing a collection from the end, but rather most indexers use the "from start" notion, or do a "length - i" expression. We introduce a new Index expression that means "from the end". The feature will introduce a new unary prefix "hat" operator. Its single operand must be convertible to `System.Int32`. It will be lowered into the appropriate `System.Index` factory method call. We augment the grammar for *unary_expression* with the following additional syntax form: ```antlr unary_expression : '^' unary_expression ; ``` We call this the *index from end* operator. The predefined *index from end* operators are as follows: ```csharp System.Index operator ^(int fromEnd); ``` The behavior of this operator is only defined for input values greater than or equal to zero. Examples: ```csharp var array = new int[] { 1, 2, 3, 4, 5 }; var thirdItem = array[2]; // array[2] var lastItem = array[^1]; // array[new Index(1, fromEnd: true)] ``` #### System.Range C# has no syntactic way to access "ranges" or "slices" of collections. Usually users are forced to implement complex structures to filter/operate on slices of memory, or resort to LINQ methods like `list.Skip(5).Take(2)`. With the addition of `System.Span` and other similar types, it becomes more important to have this kind of operation supported on a deeper level in the language/runtime, and have the interface unified. The language will introduce a new range operator `x..y`. It is a binary infix operator that accepts two expressions. Either operand can be omitted (examples below), and they have to be convertible to `System.Index`. It will be lowered to the appropriate `System.Range` factory method call. We replace the C# grammar rules for *multiplicative_expression* with the following (in order to introduce a new precedence level): ```antlr range_expression : unary_expression | range_expression? '..' range_expression? ; multiplicative_expression : range_expression | multiplicative_expression '*' range_expression | multiplicative_expression '/' range_expression | multiplicative_expression '%' range_expression ; ``` All forms of the *range operator* have the same precedence. This new precedence group is lower than the *unary operators* and higher than the *multiplicative arithmetic operators*. We call the `..` operator the *range operator*. The built-in range operator can roughly be understood to correspond to the invocation of a built-in operator of this form: ```csharp System.Range operator ..(Index start = 0, Index end = ^0); ``` Examples: ```csharp var array = new int[] { 1, 2, 3, 4, 5 }; var slice1 = array[2..^3]; // array[new Range(2, new Index(3, fromEnd: true))] var slice2 = array[..^3]; // array[Range.EndAt(new Index(3, fromEnd: true))] var slice3 = array[2..]; // array[Range.StartAt(2)] var slice4 = array[..]; // array[Range.All] ``` Moreover, `System.Index` should have an implicit conversion from `System.Int32`, in order to avoid the need to overload mixing integers and indexes over multi-dimensional signatures. ## Adding Index and Range support to existing library types ### Implicit Index support The language will provide an instance indexer member with a single parameter of type `Index` for types which meet the following criteria: - The type is Countable. - The type has an accessible instance indexer which takes a single `int` as the argument. - The type does not have an accessible instance indexer which takes an `Index` as the first parameter. The `Index` must be the only parameter or the remaining parameters must be optional. A type is ***Countable*** if it has a property named `Length` or `Count` with an accessible getter and a return type of `int`. The language can make use of this property to convert an expression of type `Index` into an `int` at the point of the expression without the need to use the type `Index` at all. In case both `Length` and `Count` are present, `Length` will be preferred. For simplicity going forward, the proposal will use the name `Length` to represent `Count` or `Length`. For such types, the language will act as if there is an indexer member of the form `T this[Index index]` where `T` is the return type of the `int` based indexer including any `ref` style annotations. The new member will have the same `get` and `set` members with matching accessibility as the `int` indexer. The new indexer will be implemented by converting the argument of type `Index` into an `int` and emitting a call to the `int` based indexer. For discussion purposes, let's use the example of `receiver[expr]`. The conversion of `expr` to `int` will occur as follows: - When the argument is of the form `^expr2` and the type of `expr2` is `int`, it will be translated to `receiver.Length - expr2`. - Otherwise, it will be translated as `expr.GetOffset(receiver.Length)`. Regardless of the specific conversion strategy, the order of evaluation should be equivalent to the following: 1. `receiver` is evaluated; 2. `expr` is evaluated; 3. `length` is evaluated, if needed; 4. the `int` based indexer is invoked. This allows for developers to use the `Index` feature on existing types without the need for modification. For example: ``` csharp List list = ...; var value = list[^1]; // Gets translated to var value = list[list.Count - 1]; ``` The `receiver` and `Length` expressions will be spilled as appropriate to ensure any side effects are only executed once. For example: ``` csharp class Collection { private int[] _array = new[] { 1, 2, 3 }; public int Length { get { Console.Write("Length "); return _array.Length; } } public int this[int index] => _array[index]; } class SideEffect { Collection Get() { Console.Write("Get "); return new Collection(); } void Use() { int i = Get()[^1]; Console.WriteLine(i); } } ``` This code will print "Get Length 3". ### Implicit Range support The language will provide an instance indexer member with a single parameter of type `Range` for types which meet the following criteria: - The type is Countable. - The type has an accessible member named `Slice` which has two parameters of type `int`. - The type does not have an instance indexer which takes a single `Range` as the first parameter. The `Range` must be the only parameter or the remaining parameters must be optional. For such types, the language will bind as if there is an indexer member of the form `T this[Range range]` where `T` is the return type of the `Slice` method including any `ref` style annotations. The new member will also have matching accessibility with `Slice`. When the `Range` based indexer is bound on an expression named `receiver`, it will be lowered by converting the `Range` expression into two values that are then passed to the `Slice` method. For discussion purposes, let's use the example of `receiver[expr]`. The first argument of `Slice` will be obtained by converting the range typed expression in the following way: - When `expr` is of the form `expr1..expr2` (where `expr2` can be omitted) and `expr1` has type `int`, then it will be emitted as `expr1`. - When `expr` is of the form `^expr1..expr2` (where `expr2` can be omitted), then it will be emitted as `receiver.Length - expr1`. - When `expr` is of the form `..expr2` (where `expr2` can be omitted), then it will be emitted as `0`. - Otherwise, it will be emitted as `expr.Start.GetOffset(receiver.Length)`. This value will be re-used in the calculation of the second `Slice` argument. When doing so it will be referred to as `start`. The second argument of `Slice` will be obtained by converting the range typed expression in the following way: - When `expr` is of the form `expr1..expr2` (where `expr1` can be omitted) and `expr2` has type `int`, then it will be emitted as `expr2 - start`. - When `expr` is of the form `expr1..^expr2` (where `expr1` can be omitted), then it will be emitted as `(receiver.Length - expr2) - start`. - When `expr` is of the form `expr1..` (where `expr1` can be omitted), then it will be emitted as `receiver.Length - start`. - Otherwise, it will be emitted as `expr.End.GetOffset(receiver.Length) - start`. Regardless of the specific conversion strategy, the order of evaluation should be equivalent to the following: 1. `receiver` is evaluated; 2. `expr` is evaluated; 3. `length` is evaluated, if needed; 4. the `Slice` method is invoked. The `receiver`, `expr`, and `length` expressions will be spilled as appropriate to ensure any side effects are only executed once. For example: ``` csharp class Collection { private int[] _array = new[] { 1, 2, 3 }; public int Length { get { Console.Write("Length "); return _array.Length; } } public int[] Slice(int start, int length) { var slice = new int[length]; Array.Copy(_array, start, slice, 0, length); return slice; } } class SideEffect { Collection Get() { Console.Write("Get "); return new Collection(); } void Use() { var array = Get()[0..2]; Console.WriteLine(array.Length); } } ``` This code will print "Get Length 2". The language will special case the following known types: - `string`: the method `Substring` will be used instead of `Slice`. - `array`: the method `System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray` will be used instead of `Slice`. ## Alternatives The new operators (`^` and `..`) are syntactic sugar. The functionality can be implemented by explicit calls to `System.Index` and `System.Range` factory methods, but it will result in a lot more boilerplate code, and the experience will be unintuitive. ## IL Representation These two operators will be lowered to regular indexer/method calls, with no change in subsequent compiler layers. ## Runtime behavior - Compiler can optimize indexers for built-in types like arrays and strings, and lower the indexing to the appropriate existing methods. - `System.Index` will throw if constructed with a negative value. - `^0` does not throw, but it translates to the length of the collection/enumerable it is supplied to. - `Range.All` is semantically equivalent to `0..^0`, and can be deconstructed to these indices. ## Considerations ### Detect Indexable based on ICollection The inspiration for this behavior was collection initializers. Using the structure of a type to convey that it had opted into a feature. In the case of collection initializers types can opt into the feature by implementing the interface `IEnumerable` (non generic). This proposal initially required that types implement `ICollection` in order to qualify as Indexable. That required a number of special cases though: - `ref struct`: these cannot implement interfaces yet types like `Span` are ideal for index / range support. - `string`: does not implement `ICollection` and adding that interface has a large cost. This means to support key types special casing is already needed. The special casing of `string` is less interesting as the language does this in other areas (`foreach` lowering, constants, etc ...). The special casing of `ref struct` is more concerning as it's special casing an entire class of types. They get labeled as Indexable if they simply have a property named `Count` with a return type of `int`. After consideration the design was normalized to say that any type which has a property `Count` / `Length` with a return type of `int` is Indexable. That removes all special casing, even for `string` and arrays. ### Detect just Count Detecting on the property names `Count` or `Length` does complicate the design a bit. Picking just one to standardize though is not sufficient as it ends up excluding a large number of types: - Use `Length`: excludes pretty much every collection in System.Collections and sub-namespaces. Those tend to derive from `ICollection` and hence prefer `Count` over length. - Use `Count`: excludes `string`, arrays, `Span` and most `ref struct` based types The extra complication on the initial detection of Indexable types is outweighed by its simplification in other aspects. ### Choice of Slice as a name The name `Slice` was chosen as it's the de-facto standard name for slice style operations in .NET. Starting with netcoreapp2.1 all span style types use the name `Slice` for slicing operations. Prior to netcoreapp2.1 there really aren't any examples of slicing to look to for an example. Types like `List`, `ArraySegment`, `SortedList` would've been ideal for slicing but the concept didn't exist when types were added. Thus, `Slice` being the sole example, it was chosen as the name. ### Index target type conversion Another way to view the `Index` transformation in an indexer expression is as a target type conversion. Instead of binding as if there is a member of the form `return_type this[Index]`, the language instead assigns a target typed conversion to `int`. This concept could be generalized to all member access on Countable types. Whenever an expression with type `Index` is used as an argument to an instance member invocation and the receiver is Countable then the expression will have a target type conversion to `int`. The member invocations applicable for this conversion include methods, indexers, properties, extension methods, etc ... Only constructors are excluded as they have no receiver. The target type conversion will be implemented as follows for any expression which has a type of `Index`. For discussion purposes lets use the example of `receiver[expr]`: - When `expr` is of the form `^expr2` and the type of `expr2` is `int`, it will be translated to `receiver.Length - expr2`. - Otherwise, it will be translated as `expr.GetOffset(receiver.Length)`. The `receiver` and `Length` expressions will be spilled as appropriate to ensure any side effects are only executed once. For example: ``` csharp class Collection { private int[] _array = new[] { 1, 2, 3 }; public int Length { get { Console.Write("Length "); return _array.Length; } } public int GetAt(int index) => _array[index]; } class SideEffect { Collection Get() { Console.Write("Get "); return new Collection(); } void Use() { int i = Get().GetAt(^1); Console.WriteLine(i); } } ``` This code will print "Get Length 3". This feature would be beneficial to any member which had a parameter that represented an index. For example `List.InsertAt`. This also has the potential for confusion as the language can't give any guidance as to whether or not an expression is meant for indexing. All it can do is convert any `Index` expression to `int` when invoking a member on a Countable type. Restrictions: - This conversion is only applicable when the expression with type `Index` is directly an argument to the member. It would not apply to any nested expressions. ## Decisions made during implementation - All members in the pattern must be instance members - If a Length method is found but it has the wrong return type, continue looking for Count - The indexer used for the Index pattern must have exactly one int parameter - The `Slice` method used for the Range pattern must have exactly two int parameters - When looking for the pattern members, we look for original definitions, not constructed members ## Design meetings - [Jan 10, 2018](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-01-10.md) - [Jan 18, 2018](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-01-18.md) - [Jan 22, 2018](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-01-22.md) - [Dec 3, 2018](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-12-03.md) - [Mar 25, 2019](https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-03-25.md#pattern-based-indexing-with-index-and-range) - [April 1st, 2019](https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-04-01.md) - [April 15, 2019](https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-04-15.md#follow-up-decisions-for-pattern-based-indexrange) ================================================ FILE: proposals/csharp-8.0/readonly-instance-members.md ================================================ # Readonly Instance Members [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: <> ## Summary [summary]: #summary Provide a way to specify individual instance members on a struct do not modify state, in the same way that `readonly struct` specifies no instance members modify state. It is worth noting that *readonly instance member* != *pure instance member*. A *pure* instance member guarantees no state will be modified. A `readonly` instance member only guarantees that instance state will not be modified. All instance members on a `readonly struct` could be considered implicitly *readonly instance members* Explicit *readonly instance members* declared on non-readonly structs would behave in the same manner. For example, they would still create hidden copies if you called an instance member (on the current instance or on a field of the instance) which was itself not-readonly. ## Motivation [motivation]: #motivation Before C# 8.0, users have the ability to create `readonly struct` types which the compiler enforces that all fields are readonly (and by extension, that no instance members modify the state). However, there are some scenarios where you have an existing API that exposes accessible fields or that has a mix of mutating and non-mutating members. Under these circumstances, you cannot mark the type as `readonly` (it would be a breaking change). This normally doesn't have much impact, except in the case of `in` parameters. With `in` parameters for non-readonly structs, the compiler will make a copy of the parameter for each instance member invocation, since it cannot guarantee that the invocation does not modify internal state. This can lead to a multitude of copies and worse overall performance than if you had just passed the struct directly by value. For an example, see this code on [sharplab](https://sharplab.io/#v2:CYLg1APgAgDABFAjAbgLACgNQMxwM4AuATgK4DGBcAagKYUD2RATBgN4ZycK4BmANvQCGlAB5p0XbnH5DKAT3GSOXHNIHC4AGRoA7AOYEAFgGUAjiUFEawZZ3YTJXPTQK3H9x54QB2OAAoROAAqOBEASjgwNy8YvzlguDkwxS8AXzd09EysXCgmOABhOA8VXnVKAFk/AEsdajoCRnyAN0E+EhoIks8oX1b2mgA6bX0jMwsrYEi4fo7h3QMTc0trFM5M1KA==) Some other scenarios where hidden copies can occur include *`static readonly` fields* and *literals*. If they are supported in the future, *blittable constants* would end up in the same boat; that is they all currently necessitate a full copy (on instance member invocation) if the struct is not marked `readonly`. ## Design [design]: #design Allow a user to specify that an instance member is, itself, `readonly` and does not modify the state of the instance (with all the appropriate verification done by the compiler, of course). For example: ```csharp public struct Vector2 { public float x; public float y; public readonly float GetLengthReadonly() { return MathF.Sqrt(LengthSquared); } public float GetLength() { return MathF.Sqrt(LengthSquared); } public readonly float GetLengthIllegal() { var tmp = MathF.Sqrt(LengthSquared); x = tmp; // Compiler error, cannot write x y = tmp; // Compiler error, cannot write y return tmp; } public readonly float LengthSquared { get { return (x * x) + (y * y); } } } public static class MyClass { public static float ExistingBehavior(in Vector2 vector) { // This code causes a hidden copy, the compiler effectively emits: // var tmpVector = vector; // return tmpVector.GetLength(); // // This is done because the compiler doesn't know that `GetLength()` // won't mutate `vector`. return vector.GetLength(); } public static float ReadonlyBehavior(in Vector2 vector) { // This code is emitted exactly as listed. There are no hidden // copies as the `readonly` modifier indicates that the method // won't mutate `vector`. return vector.GetLengthReadonly(); } } ``` Readonly can be applied to property accessors to indicate that `this` will not be mutated in the accessor. The following examples have readonly setters because those accessors modify the state of member field, but do not modify the value of that member field. ```csharp public readonly int Prop1 { get { return this._store["Prop1"]; } set { this._store["Prop1"] = value; } } ``` When `readonly` is applied to the property syntax, it means that all accessors are `readonly`. ```csharp public readonly int Prop2 { get { return this._store["Prop2"]; } set { this._store["Prop2"] = value; } } ``` Readonly can only be applied to accessors which do not mutate the containing type. ```csharp public int Prop3 { readonly get { return this._prop3; } set { this._prop3 = value; } } ``` Readonly can be applied to some auto-implemented properties, but it won't have a meaningful effect. The compiler will treat all auto-implemented getters as readonly whether or not the `readonly` keyword is present. ```csharp // Allowed public readonly int Prop4 { get; } public int Prop5 { readonly get; set; } // Not allowed public int Prop6 { readonly get; } public readonly int Prop7 { get; set; } public int Prop8 { get; readonly set; } ``` Readonly can be applied to manually-implemented events, but not field-like events. Readonly cannot be applied to individual event accessors (add/remove). ```csharp // Allowed public readonly event Action Event1 { add { } remove { } } // Not allowed public readonly event Action Event2; public event Action Event3 { readonly add { } readonly remove { } } public static readonly event Event4 { add { } remove { } } ``` Some other syntax examples: * Expression bodied members: `public readonly float ExpressionBodiedMember => (x * x) + (y * y);` * Generic constraints: `public readonly void GenericMethod(T value) where T : struct { }` The compiler would emit the instance member, as usual, and would additionally emit a compiler recognized attribute indicating that the instance member does not modify state. This effectively causes the hidden `this` parameter to become `in T` instead of `ref T`. This would allow the user to safely call said instance method without the compiler needing to make a copy. The restrictions would include: * The `readonly` modifier cannot be applied to static methods, constructors or destructors. * The `readonly` modifier cannot be applied to delegates. * The `readonly` modifier cannot be applied to members of class or interface. ## Drawbacks [drawbacks]: #drawbacks Same drawbacks as exist with `readonly struct` methods today. Certain code may still cause hidden copies. ## Notes [notes]: #notes Using an attribute or another keyword may also be possible. This proposal is somewhat related to (but is more a subset of) `functional purity` and/or `constant expressions`, both of which have had some existing proposals. ================================================ FILE: proposals/csharp-8.0/shadowing-in-nested-functions.md ================================================ # Name shadowing in nested functions Champion issue: ## Summary Permit variable names in lambdas and local functions to reuse (and shadow) names from the enclosing method or function. ## Detailed design With `-langversion:8`, names of locals, local functions, parameters, type parameters, and range variables within a lambda or local function can reuse names of locals, local functions, parameters, type parameters, and range variables from an enclosing method or function. The name in the nested function hides the symbol of the same name from the enclosing function within the nested function. Shadowing is supported for `static` and non-`static` local functions and lambdas. There is no change in behavior using `-langversion:7.3` or earlier: names in nested functions that shadow names from the enclosing method or function are reported as errors in those cases. Any shadowing previously permitted is still supported with `-langversion:8`. For instance: variable names may shadow type and member names; and variable names may shadow enclosing method or local function names. Shadowing a name declared in an enclosing scope in the same lambda or local function is still reported as an error. A warning is reported for a type parameter in a local function that shadows a type parameter in the enclosing type, method, or function. ## Design meetings - https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-09-10.md#static-local-functions - https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-01-16.md#shadowing-in-nested-functions ================================================ FILE: proposals/csharp-8.0/static-local-functions.md ================================================ # Static local functions [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Support local functions that disallow capturing state from the enclosing scope. ## Motivation Avoid unintentionally capturing state from the enclosing context. Allow local functions to be used in scenarios where a `static` method is required. ## Detailed design A local function declared `static` cannot capture state from the enclosing scope. As a result, locals, parameters, and `this` from the enclosing scope are not available within a `static` local function. A `static` local function cannot reference instance members from an implicit or explicit `this` or `base` reference. A `static` local function may reference `static` members from the enclosing scope. A `static` local function may reference `constant` definitions from the enclosing scope. `nameof()` in a `static` local function may reference locals, parameters, or `this` or `base` from the enclosing scope. Accessibility rules for `private` members in the enclosing scope are the same for `static` and non-`static` local functions. A `static` local function definition is emitted as a `static` method in metadata, even if only used in a delegate. A non-`static` local function or lambda can capture state from an enclosing `static` local function but cannot capture state outside the enclosing `static` local function. A `static` local function cannot be invoked in an expression tree. A call to a local function is emitted as `call` rather than `callvirt`, regardless of whether the local function is `static`. Overload resolution of a call within a local function not affected by whether the local function is `static`. Removing the `static` modifier from a local function in a valid program does not change the meaning of the program. ## Design meetings https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-09-10.md#static-local-functions ================================================ FILE: proposals/csharp-8.0/unconstrained-null-coalescing.md ================================================ ## Unconstrained type parameter in null coalescing operator Champion issue: In C# 8.0 we introduced a feature that permits a null coalescing operator to have a left operand that is not known to be either a reference or value type (i.e. an unconstrained type parameter). This is a placeholder for its specification. ================================================ FILE: proposals/csharp-8.0/using.md ================================================ # "pattern-based using" and "using declarations" [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary The language will add two new capabilities around the `using` statement in order to make resource management simpler: `using` should recognize a disposable pattern in addition to `IDisposable` and add a `using` declaration to the language. ## Motivation The `using` statement is an effective tool for resource management today but it requires quite a bit of ceremony. Methods that have a number of resources to manage can get syntactically bogged down with a series of `using` statements. This syntax burden is enough that most coding style guidelines explicitly have an exception around braces for this scenario. The `using` declaration removes much of the ceremony here and gets C# on par with other languages that include resource management blocks. Additionally the pattern-based `using` lets developers expand the set of types that can participate here. In many cases removing the need to create wrapper types that only exist to allow for a values use in a `using` statement. Together these features allow developers to simplify and expand the scenarios where `using` can be applied. ## Detailed Design ### using declaration The language will allow for `using` to be added to a local variable declaration. Such a declaration will have the same effect as declaring the variable in a `using` statement at the same location. ```csharp if (...) { using FileStream f = new FileStream(@"C:\source\using.md"); // statements } // Equivalent to if (...) { using (FileStream f = new FileStream(@"C:\source\using.md")) { // statements } } ``` The lifetime of a `using` local will extend to the end of the scope in which it is declared. The `using` locals will then be disposed in the reverse order in which they are declared. ```csharp { using var f1 = new FileStream("..."); using var f2 = new FileStream("..."); using var f3 = new FileStream("..."); ... // Dispose f3 // Dispose f2 // Dispose f1 } ``` There are no restrictions around `goto`, or any other control flow construct in the face of a `using` declaration. Instead the code acts just as it would for the equivalent `using` statement: ```csharp { using var f1 = new FileStream("..."); target: using var f2 = new FileStream("..."); if (someCondition) { // Causes f2 to be disposed but has no effect on f1 goto target; } } ``` A local declared in a `using` local declaration will be implicitly read-only. This matches the behavior of locals declared in a `using` statement. The language grammar for `using` declarations will be the following: ```antlr local-using-declaration: 'using' type using-declarators using-declarators: using-declarator using-declarators , using-declarator using-declarator: identifier = expression ``` Restrictions around `using` declaration: - May not appear directly inside a `case` label but instead must be within a block inside the `case` label. - May not appear as part of an `out` variable declaration. - Must have an initializer for each declarator. - The local type must be implicitly convertible to `IDisposable` or fulfill the `using` pattern. ### pattern-based using The language will add the notion of a disposable pattern for `ref struct` types: that is a `ref struct` which has an accessible `Dispose` instance method. Types which fit the disposable pattern can participate in a `using` statement or declaration without being required to implement `IDisposable`. ```csharp ref struct Resource { public void Dispose() { ... } } using (var r = new Resource()) { // statements } ``` This will allow developers to leverage `using` for `ref struct` types. These types can't implement interfaces in C# 8 and hence can't participate in `using` statements. The same restrictions from a traditional `using` statement apply here as well: local variables declared in the `using` are read-only, a `null` value will not cause an exception to be thrown, etc ... The code generation will be different only in that there will not be a cast to `IDisposable` before calling Dispose: ```csharp { Resource r = new Resource(); try { // statements } finally { if (r != null) r.Dispose(); } } ``` In order to fit the disposable pattern the `Dispose` method must be an accessible instance member, parameterless and have a `void` return type. It cannot be an extension method. ## Considerations Neither of these considerations were implemented in C# 8
### case labels without blocks A `using declaration` is illegal directly inside a `case` label due to complications around its actual lifetime. One potential solution is to simply give it the same lifetime as an `out var` in the same location. It was deemed the extra complexity to the feature implementation and the ease of the work around (just add a block to the `case` label) didn't justify taking this route. ## Future Expansions ### fixed locals A `fixed` statement has all of the properties of `using` statements that motivated the ability to have `using` locals. Consideration should be given to extending this feature to `fixed` locals as well. The lifetime and ordering rules should apply equally well for `using` and `fixed` here.
================================================ FILE: proposals/csharp-9.0/covariant-returns.md ================================================ # Covariant returns [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Support _covariant return types_. Specifically, permit the override of a method to declare a more derived return type than the method it overrides, and similarly to permit the override of a read-only property to declare a more derived type. Override declarations appearing in more derived types would be required to provide a return type at least as specific as that appearing in overrides in its base types. Callers of the method or property would statically receive the more refined return type from an invocation. ## Motivation [motivation]: #motivation It is a common pattern in code that different method names have to be invented to work around the language constraint that overrides must return the same type as the overridden method. This would be useful in the factory pattern. For example, in the Roslyn code base we would have ``` cs class Compilation ... { public virtual Compilation WithOptions(Options options)... } ``` ``` cs class CSharpCompilation : Compilation { public override CSharpCompilation WithOptions(Options options)... } ``` ## Detailed design [design]: #detailed-design This is a specification for [covariant return types](https://github.com/dotnet/csharplang/issues/49) in C#. Our intent is to permit the override of a method to return a more derived return type than the method it overrides, and similarly to permit the override of a read-only property to return a more derived return type. Callers of the method or property would statically receive the more refined return type from an invocation, and overrides appearing in more derived types would be required to provide a return type at least as specific as that appearing in overrides in its base types. -------------- ### Class Method Override The existing constraint on class override ([§15.6.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1565-override-methods)) methods > - The override method and the overridden base method have the same return type. is modified to > - The override method must have a return type that is convertible by an identity conversion or (if the method has a value return - not a [ref return](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/ref-locals-returns.md) see [§13.1.0.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#13105-the-return-statement) implicit reference conversion to the return type of the overridden base method. And the following additional requirements are appended to that list: > - The override method must have a return type that is convertible by an identity conversion or (if the method has a value return - not a [ref return](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/ref-locals-returns.md), [§13.1.0.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#13105-the-return-statement)) implicit reference conversion to the return type of every override of the overridden base method that is declared in a (direct or indirect) base type of the override method. > - The override method's return type must be at least as accessible as the override method (Accessibility domains - [§7.5.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#753-accessibility-domains)). This constraint permits an override method in a `private` class to have a `private` return type. However it requires a `public` override method in a `public` type to have a `public` return type. ### Class Property and Indexer Override The existing constraint on class override ([§15.7.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1576-virtual-sealed-override-and-abstract-accessors)) properties > An overriding property declaration shall specify the exact same accessibility modifiers and name as the inherited property, and there shall be an identity conversion ~~between the type of the overriding and the inherited property~~. If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property shall include only that accessor. If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors. is modified to > An overriding property declaration shall specify the exact same accessibility modifiers and name as the inherited property, and there shall be an identity conversion **or (if the inherited property is read-only and has a value return - not a [ref return](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/ref-locals-returns.md) [§13.1.0.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#13105-the-return-statement)) implicit reference conversion from the type of the overriding property to the type of the inherited property**. If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property shall include only that accessor. If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors. **The overriding property's type must be at least as accessible as the overriding property (Accessibility domains - [§7.5.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#753-accessibility-domains)).** ----------------- ***The remainder of the draft specification below proposes a further extension to covariant returns of interface methods to be considered later.*** ### Interface Method, Property, and Indexer Override Adding to the kinds of members that are permitted in an interface with the addition of the DIM feature in C# 8.0, we further add support for `override` members along with covariant returns. These follow the rules of `override` members as specified for classes, with the following differences: The following text in classes: > The method overridden by an override declaration is known as the ***overridden base method***. For an override method `M` declared in a class `C`, the overridden base method is determined by examining each base class of `C`, starting with the direct base class of `C` and continuing with each successive direct base class, until in a given base class type at least one accessible method is located which has the same signature as `M` after substitution of type arguments. is given the corresponding specification for interfaces: > The method overridden by an override declaration is known as the ***overridden base method***. For an override method `M` declared in an interface `I`, the overridden base method is determined by examining each direct or indirect base interface of `I`, collecting the set of interfaces declaring an accessible method which has the same signature as `M` after substitution of type arguments. If this set of interfaces has a *most derived type*, to which there is an identity or implicit reference conversion from every type in this set, and that type contains a unique such method declaration, then that is the *overridden base method*. We similarly permit `override` properties and indexers in interfaces as specified for classes in [§15.7.6 Virtual, sealed, override, and abstract accessors](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1576-virtual-sealed-override-and-abstract-accessors). ### Name Lookup Name lookup in the presence of class `override` declarations currently modify the result of name lookup by imposing on the found member details from the most derived `override` declaration in the class hierarchy starting from the type of the identifier's qualifier (or `this` when there is no qualifier). For example, in [§12.6.2.2 Corresponding parameters](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12622-corresponding-parameters) we have > For virtual methods and indexers defined in classes, the parameter list is picked from the first declaration or override of the function member found when starting with the static type of the receiver, and searching through its base classes. to this we add > For virtual methods and indexers defined in interfaces, the parameter list is picked from the declaration or override of the function member found in the most derived type among those types containing the declaration of override of the function member. It is a compile-time error if no unique such type exists. For the result type of a property or indexer access, the existing text > - If `I` identifies an instance property, then the result is a property access with an associated instance expression of `E` and an associated type that is the type of the property. If `T` is a class type, the associated type is picked from the first declaration or override of the property found when starting with `T`, and searching through its base classes. is augmented with > If `T` is an interface type, the associated type is picked from the declaration or override of the property found in the most derived of `T` or its direct or indirect base interfaces. It is a compile-time error if no unique such type exists. A similar change should be made in [§12.8.12.3 Indexer access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128123-indexer-access) In [§12.8.10 Invocation expressions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12810-invocation-expressions) we augment the existing text > - Otherwise, the result is a value, with an associated type of the return type of the method or delegate. If the invocation is of an instance method, and the receiver is of a class type `T`, the associated type is picked from the first declaration or override of the method found when starting with `T` and searching through its base classes. with > If the invocation is of an instance method, and the receiver is of an interface type `T`, the associated type is picked from the declaration or override of the method found in the most derived interface from among `T` and its direct and indirect base interfaces. It is a compile-time error if no unique such type exists. ### Implicit Interface Implementations This section of the specification > For purposes of interface mapping, a class member `A` matches an interface member `B` when: > > - `A` and `B` are methods, and the name, type, and formal parameter lists of `A` and `B` are identical. > - `A` and `B` are properties, the name and type of `A` and `B` are identical, and `A` has the same accessors as `B` (`A` is permitted to have additional accessors if it is not an explicit interface member implementation). > - `A` and `B` are events, and the name and type of `A` and `B` are identical. > - `A` and `B` are indexers, the type and formal parameter lists of `A` and `B` are identical, and `A` has the same accessors as `B` (`A` is permitted to have additional accessors if it is not an explicit interface member implementation). is modified as follows: > For purposes of interface mapping, a class member `A` matches an interface member `B` when: > > - `A` and `B` are methods, and the name and formal parameter lists of `A` and `B` are identical, and the return type of `A` is convertible to the return type of `B` via an identity of implicit reference convertion to the return type of `B`. > - `A` and `B` are properties, the name of `A` and `B` are identical, `A` has the same accessors as `B` (`A` is permitted to have additional accessors if it is not an explicit interface member implementation), and the type of `A` is convertible to the return type of `B` via an identity conversion or, if `A` is a readonly property, an implicit reference conversion. > - `A` and `B` are events, and the name and type of `A` and `B` are identical. > - `A` and `B` are indexers, the formal parameter lists of `A` and `B` are identical, `A` has the same accessors as `B` (`A` is permitted to have additional accessors if it is not an explicit interface member implementation), and the type of `A` is convertible to the return type of `B` via an identity conversion or, if `A` is a readonly indexer, an implicit reference conversion. This is technically a breaking change, as the program below prints "C1.M" today, but would print "C2.M" under the proposed revision. ``` c# using System; interface I1 { object M(); } class C1 : I1 { public object M() { return "C1.M"; } } class C2 : C1, I1 { public new string M() { return "C2.M"; } } class Program { static void Main() { I1 i = new C2(); Console.WriteLine(i.M()); } } ``` Due to this breaking change, we might consider not supporting covariant return types on implicit implementations. ### Constraints on Interface Implementation **We will need a rule that an explicit interface implementation must declare a return type no less derived than the return type declared in any override in its base interfaces.** ### API Compatibility Implications *TBD* ### Open Issues The specification does not say how the caller gets the more refined return type. Presumably that would be done in a way similar to the way that callers get the most derived override's parameter specifications. -------------- If we have the following interfaces: ```csharp interface I1 { I1 M(); } interface I2 { I2 M(); } interface I3: I1, I2 { override I3 M(); } ``` Note that in `I3`, the methods `I1.M()` and `I2.M()` have been “merged”. When implementing `I3`, it is necessary to implement them both together. Generally, we require an explicit implementation to refer to the original method. The question is, in a class ```csharp class C : I1, I2, I3 { C IN.M(); } ``` What does that mean here? What should *N* be? I suggest that we permit implementing either `I1.M` or `I2.M` (but not both), and treat that as an implementation of both. ## Drawbacks [drawbacks]: #drawbacks - [ ] Every language change must pay for itself. - [ ] We should ensure that the performance is reasonable, even in the case of deep inheritance hierarchies - [ ] We should ensure that artifacts of the translation strategy do not affect language semantics, even when consuming new IL from old compilers. ## Alternatives [alternatives]: #alternatives We could relax the language rules slightly to allow, in source, ```csharp // Possible alternative. This was not implemented. abstract class Cloneable { public abstract Cloneable Clone(); } class Digit : Cloneable { public override Cloneable Clone() { return this.Clone(); } public new Digit Clone() // Error: 'Digit' already defines a member called 'Clone' with the same parameter types { return this; } } ``` ## Unresolved questions [unresolved]: #unresolved-questions - [ ] How will APIs that have been compiled to use this feature work in older versions of the language? ## Design meetings - some discussion at . - https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-01-08.md - Offline discussion toward a decision to support overriding of class methods only in C# 9.0. ================================================ FILE: proposals/csharp-9.0/extending-partial-methods.md ================================================ # Extending Partial Methods [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary This proposal aims to remove all restrictions around the signatures of `partial` methods in C#. The goal being to expand the set of scenarios in which these methods can work with source generators as well as being a more general declaration form for C# methods. See also the original partial methods specification ([§15.6.9](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1569-partial-methods)). ## Motivation C# has limited support for developers splitting methods into declarations and definitions / implementations. ```cs partial class C { // The declaration of C.M partial void M(string message); } partial class C { // The definition of C.M partial void M(string message) => Console.WriteLine(message); } ``` One behavior of `partial` methods is that when the definition is absent then the language will simply erase any calls to the `partial` method. Essentially it behaves like a call to a `[Conditional]` method where the condition was evaluated to false. ```cs partial class D { partial void M(string message); void Example() { M(GetIt()); // Call to M and GetIt erased at compile time } string GetIt() => "Hello World"; } ``` The original motivation for this feature was source generation in the form of designer generated code. Users were constantly editing the generated code because they wanted to hook some aspect of the generated code. Most notably parts of the Windows Forms startup process, after components were initialized. Editing the generated code was error prone because any action which caused the designer to regenerate the code would cause the user edit to be erased. The `partial` method feature eased this tension because it allowed designers to emit hooks in the form of `partial` methods. Designers could emit hooks like `partial void OnComponentInit()` and developers could define declarations for them or not define them. In either case though the generated code would compile and developers who were interested in the process could hook in as needed. This does mean that partial methods have several restrictions: 1. Must have a `void` return type. 1. Cannot have `out` parameters. 1. Cannot have any accessibility (implicitly `private`). These restrictions exist because the language must be able to emit code when the call site is erased. Given they can be erased `private` is the only possible accessibility because the member can't be exposed in assembly metadata. These restrictions also serve to limit the set of scenarios in which `partial` methods can be applied. The proposal here is to remove all of the existing restrictions around `partial` methods. Essentially let them have `out` parameters, non-void return types or any type of accessibility. Such `partial` declarations would then have the added requirement that a definition must exist. That means the language does not have to consider the impact of erasing the call sites. This would expand the set of generator scenarios that `partial` methods could participate in and hence link in nicely with our source generators feature. For example a regex could be defined using the following pattern: ```cs [RegexGenerated("(dog|cat|fish)")] partial bool IsPetMatch(string input); ``` This gives both the developer a simple declarative way of opting into generators as well as giving generators a very easy set of declarations to look through in the source code to drive their generated output. Compare that with the difficulty that a generator would have hooking up the following snippet of code. ```cs var regex = new RegularExpression("(dog|cat|fish)"); if (regex.IsMatch(someInput)) { } ``` Given that the compiler doesn't allow generators to modify code hooking up this pattern would be pretty much impossible for generators. They would need to resort to reflection in the `IsMatch` implementation, or asking users to change their call sites to a new method + refactor the regex to pass the string literal as an argument. It's pretty messy. ## Detailed Design The language will change to allow `partial` methods to be annotated with an explicit accessibility modifier. This means they can be labeled as `private`, `public`, etc ... When a `partial` method has an explicit accessibility modifier the language will require that the declaration has a matching definition even when the accessibility is `private`: ```cs partial class C { // Okay because no definition is required here partial void M1(); // Okay because M2 has a definition private partial void M2(); // Error: partial method M3 must have a definition private partial void M3(); } partial class C { private partial void M2() { } } ``` Further the language will remove all restrictions on what can appear on a `partial` method which has an explicit accessibility. Such declarations can contain non-void return types, `out` parameters, `extern` modifier, etc ... These signatures will have the full expressivity of the C# language. ```cs partial class D { // Okay internal partial bool TryParse(string s, out int i); } partial class D { internal partial bool TryParse(string s, out int i) { ... } } ``` This explicitly allows for `partial` methods to participate in `overrides` and `interface` implementations: ```cs interface IStudent { string GetName(); } partial class C : IStudent { public virtual partial string GetName(); } partial class C { public virtual partial string GetName() => "Jarde"; } ``` The compiler will change the error it emits when a `partial` method contains an illegal element to essentially say: > Cannot use `ref` on a `partial` method that lacks explicit accessibility This will help point developers in the right direction when using this feature. Restrictions: - `partial` declarations with explicit accessibility must have a definition - `partial` declarations and definition signatures must match on all method and parameter modifiers. The only aspects which can differ are parameter names and attribute lists (this is not new but rather an existing requirement of `partial` methods). ## Questions ### partial on all members Given that we're expanding `partial` to be more friendly to source generators should we also expand it to work on all class members? For example should we be able to declare `partial` constructors, operators, etc ... **Resolution** The idea is sound but at this point in the C# 9 schedule we're trying to avoid unnecessary feature creep. Want to solve the immediate problem of expanding the feature to work with modern source generators. Extending `partial` to support other members will be considered for the C# 10 release. Seems likely that we will consider this extension. This remains an active proposal, but it has not yet been implemented. ### Use abstract instead of partial The crux of this proposal is essentially ensuring that a declaration has a corresponding definition / implementation. Given that should we use `abstract` since it's already a language keyword that forces the developer to think about having an implementation? **Resolution** There was a healthy discussion about this but eventually it was decided against. Yes the requirements are familiar but the concepts are significantly different. Could easily lead the developer to believe they were creating virtual slots when they were not doing so. ================================================ FILE: proposals/csharp-9.0/extension-getenumerator.md ================================================ # Extension `GetEnumerator` support for `foreach` loops. [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Allow `foreach` loops to recognize an extension method `GetEnumerator` method that otherwise satisfies the foreach pattern, and loop over the expression when it would otherwise be an error. ## Motivation [motivation]: #motivation This will bring `foreach` inline with how other features in C# are implemented, including async and pattern-based deconstruction. ## Detailed design [design]: #detailed-design The spec change is relatively straightforward. We modify `The foreach statement` [§13.9.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement) section to this text: >The compile-time processing of a foreach statement first determines the ***collection type***, ***enumerator type*** and ***element type*** of the expression. This determination proceeds as follows: > >* If the type `X` of *expression* is an array type then there is an implicit reference conversion from `X` to the `IEnumerable` interface (since `System.Array` implements this interface). The ***collection type*** is the `IEnumerable` interface, the ***enumerator type*** is the `IEnumerator` interface and the ***element type*** is the element type of the array type `X`. >* If the type `X` of *expression* is `dynamic` then there is an implicit conversion from *expression* to the `IEnumerable` interface ([§10.2.10](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#10210-implicit-dynamic-conversions)). The ***collection type*** is the `IEnumerable` interface and the ***enumerator type*** is the `IEnumerator` interface. If the `var` identifier is given as the *local_variable_type* then the ***element type*** is `dynamic`, otherwise it is `object`. >* Otherwise, determine whether the type `X` has an appropriate `GetEnumerator` method: > * Perform member lookup on the type `X` with identifier `GetEnumerator` and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match. > * Perform overload resolution using the resulting method group and an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, check for an enumerable interface as described below. It is recommended that a warning be issued if overload resolution produces anything except an unambiguous public instance method or no applicable methods. > * If the return type `E` of the `GetEnumerator` method is not a class, struct or interface type, an error is produced and no further steps are taken. > * Member lookup is performed on `E` with the identifier `Current` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken. > * Member lookup is performed on `E` with the identifier `MoveNext` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. > * Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not `bool`, an error is produced and no further steps are taken. > * The ***collection type*** is `X`, the ***enumerator type*** is `E`, and the ***element type*** is the type of the `Current` property. > >* Otherwise, check for an enumerable interface: > * If among all the types `Ti` for which there is an implicit conversion from `X` to `IEnumerable`, there is a unique type `T` such that `T` is not `dynamic` and for all the other `Ti` there is an implicit conversion from `IEnumerable` to `IEnumerable`, then the ***collection type*** is the interface `IEnumerable`, the ***enumerator type*** is the interface `IEnumerator`, and the ***element type*** is `T`. > * Otherwise, if there is more than one such type `T`, then an error is produced and no further steps are taken. > * Otherwise, if there is an implicit conversion from `X` to the `System.Collections.IEnumerable` interface, then the ***collection type*** is this interface, the ***enumerator type*** is the interface `System.Collections.IEnumerator`, and the ***element type*** is `object`. >* Otherwise, determine whether the type 'X' has an appropriate `GetEnumerator` extension method: > * Perform extension method lookup on the type `X` with identifier `GetEnumerator`. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match which is not a method group, an error is produced and no further steps are taken. It is recommended that a warning be issues if member lookup produces anything except a method group or no match. > * Perform overload resolution using the resulting method group and a single argument of type `X`. If overload resolution produces no applicable methods, results in an ambiguity, or results in a single best method but that method is not accessible, an error is produced an no further steps are taken. > * This resolution permits the first argument to be passed by ref if `X` is a struct type, and the ref kind is `in`. > * If the return type `E` of the `GetEnumerator` method is not a class, struct or interface type, an error is produced and no further steps are taken. > * Member lookup is performed on `E` with the identifier `Current` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken. > * Member lookup is performed on `E` with the identifier `MoveNext` and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken. > * Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not `bool`, an error is produced and no further steps are taken. > * The ***collection type*** is `X`, the ***enumerator type*** is `E`, and the ***element type*** is the type of the `Current` property. >* Otherwise, an error is produced and no further steps are taken. For `await foreach`, the rules are similarly modified. The only change that is required to that spec is removing the `Extension methods do not contribute.` line from the description, as the rest of that spec is based on the above rules with different names substituted for the pattern methods. ## Drawbacks [drawbacks]: #drawbacks Every change adds additional complexity to the language, and this potentially allows things that weren't designed to be `foreach`ed to be `foreach`ed, like `Range`. ## Alternatives [alternatives]: #alternatives Doing nothing. ## Unresolved questions [unresolved]: #unresolved-questions None at this point. ================================================ FILE: proposals/csharp-9.0/function-pointers.md ================================================ # Function Pointers [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] ## Summary This proposal provides language constructs that expose IL opcodes that cannot currently be accessed efficiently, or at all, in C# today: `ldftn` and `calli`. These IL opcodes can be important in high performance code and developers need an efficient way to access them. ## Motivation The motivations and background for this feature are described in the following issue (as is a potential implementation of the feature): [dotnet/csharplang#191](https://github.com/dotnet/csharplang/issues/191) This is an alternate design proposal to [compiler intrinsics](https://github.com/dotnet/csharplang/blob/main/proposals/rejected/intrinsics.md) ## Detailed Design ### Function pointers The language will allow for the declaration of function pointers using the `delegate*` syntax. The full syntax is described in detail in the next section but it is meant to resemble the syntax used by `Func` and `Action` type declarations. ``` csharp unsafe class Example { void M(Action a, delegate* f) { a(42); f(42); } } ``` These types are represented using the function pointer type as outlined in ECMA-335. This means invocation of a `delegate*` will use `calli` where invocation of a `delegate` will use `callvirt` on the `Invoke` method. Syntactically though invocation is identical for both constructs. The ECMA-335 definition of method pointers includes the calling convention as part of the type signature (section 7.1). The default calling convention will be `managed`. Unmanaged calling conventions can be specified by putting an `unmanaged` keyword afer the `delegate*` syntax, which will use the runtime platform default. Specific unmanaged conventions can then be specified in brackets to the `unmanaged` keyword by specifying any type starting with `CallConv` in the `System.Runtime.CompilerServices` namespace, leaving off the `CallConv` prefix. These types must come from the program's core library, and the set of valid combinations is platform-dependent. ``` csharp //This method has a managed calling convention. This is the same as leaving the managed keyword off. delegate* managed; // This method will be invoked using whatever the default unmanaged calling convention on the runtime // platform is. This is platform and architecture dependent and is determined by the CLR at runtime. delegate* unmanaged; // This method will be invoked using the cdecl calling convention // Cdecl maps to System.Runtime.CompilerServices.CallConvCdecl delegate* unmanaged[Cdecl] ; // This method will be invoked using the stdcall calling convention, and suppresses GC transition // Stdcall maps to System.Runtime.CompilerServices.CallConvStdcall // SuppressGCTransition maps to System.Runtime.CompilerServices.CallConvSuppressGCTransition delegate* unmanaged[Stdcall, SuppressGCTransition] ; ``` Conversions between `delegate*` types is done based on their signature including the calling convention. ``` csharp unsafe class Example { void Conversions() { delegate* p1 = ...; delegate* managed p2 = ...; delegate* unmanaged p3 = ...; p1 = p2; // okay p1 and p2 have compatible signatures Console.WriteLine(p2 == p1); // True p2 = p3; // error: calling conventions are incompatible } } ``` A `delegate*` type is a pointer type which means it has all of the capabilities and restrictions of a standard pointer type: - Only valid in an `unsafe` context. - Methods which contain a `delegate*` parameter or return type can only be called from an `unsafe` context. - Cannot be converted to `object`. - Cannot be used as a generic argument. - Can implicitly convert `delegate*` to `void*`. - Can explicitly convert from `void*` to `delegate*`. Restrictions: - Custom attributes cannot be applied to a `delegate*` or any of its elements. - A `delegate*` parameter cannot be marked as `params` - A `delegate*` type has all of the restrictions of a normal pointer type. - Pointer arithmetic cannot be performed directly on function pointer types. ### Function pointer syntax The full function pointer syntax is represented by the following grammar: ```antlr pointer_type : ... | funcptr_type ; funcptr_type : 'delegate' '*' calling_convention_specifier? '<' funcptr_parameter_list funcptr_return_type '>' ; calling_convention_specifier : 'managed' | 'unmanaged' ('[' unmanaged_calling_convention ']')? ; unmanaged_calling_convention : 'Cdecl' | 'Stdcall' | 'Thiscall' | 'Fastcall' | identifier (',' identifier)* ; funptr_parameter_list : (funcptr_parameter ',')* ; funcptr_parameter : funcptr_parameter_modifier? type ; funcptr_return_type : funcptr_return_modifier? return_type ; funcptr_parameter_modifier : 'ref' | 'out' | 'in' ; funcptr_return_modifier : 'ref' | 'ref readonly' ; ``` If no `calling_convention_specifier` is provided, the default is `managed`. The precise metadata encoding of the `calling_convention_specifier` and what `identifier`s are valid in the `unmanaged_calling_convention` is covered in [Metadata Representation of Calling Conventions](#metadata-representation-of-calling-conventions). ``` csharp delegate int Func1(string s); delegate Func1 Func2(Func1 f); // Function pointer equivalent without calling convention delegate*; delegate*, delegate*>; // Function pointer equivalent with calling convention delegate* managed; delegate*, delegate*>; ``` ### Function pointer conversions In an unsafe context, the set of available implicit conversions (Implicit conversions) is extended to include the following implicit pointer conversions: - _Existing conversions_ - ([§23.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#235-pointer-conversions)) - From _funcptr\_type_ `F0` to another _funcptr\_type_ `F1`, provided all of the following are true: - `F0` and `F1` have the same number of parameters, and each parameter `D0n` in `F0` has the same `ref`, `out`, or `in` modifiers as the corresponding parameter `D1n` in `F1`. - For each value parameter (a parameter with no `ref`, `out`, or `in` modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in `F0` to the corresponding parameter type in `F1`. - For each `ref`, `out`, or `in` parameter, the parameter type in `F0` is the same as the corresponding parameter type in `F1`. - If the return type is by value (no `ref` or `ref readonly`), an identity, implicit reference, or implicit pointer conversion exists from the return type of `F1` to the return type of `F0`. - If the return type is by reference (`ref` or `ref readonly`), the return type and `ref` modifiers of `F1` are the same as the return type and `ref` modifiers of `F0`. - The calling convention of `F0` is the same as the calling convention of `F1`. ### Allow address-of to target methods Method groups will now be allowed as arguments to an address-of expression. The type of such an expression will be a `delegate*` which has the equivalent signature of the target method and a managed calling convention: ``` csharp unsafe class Util { public static void Log() { } void Use() { delegate* ptr1 = &Util.Log; // Error: type "delegate*" not compatible with "delegate*"; delegate* ptr2 = &Util.Log; } } ``` In an unsafe context, a method `M` is compatible with a function pointer type `F` if all of the following are true: - `M` and `F` have the same number of parameters, and each parameter in `M` has the same `ref`, `out`, or `in` modifiers as the corresponding parameter in `F`. - For each value parameter (a parameter with no `ref`, `out`, or `in` modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in `M` to the corresponding parameter type in `F`. - For each `ref`, `out`, or `in` parameter, the parameter type in `M` is the same as the corresponding parameter type in `F`. - If the return type is by value (no `ref` or `ref readonly`), an identity, implicit reference, or implicit pointer conversion exists from the return type of `F` to the return type of `M`. - If the return type is by reference (`ref` or `ref readonly`), the return type and `ref` modifiers of `F` are the same as the return type and `ref` modifiers of `M`. - The calling convention of `M` is the same as the calling convention of `F`. This includes both the calling convention bit, as well as any calling convention flags specified in the unmanaged identifier. - `M` is a static method. In an unsafe context, an implicit conversion exists from an address-of expression whose target is a method group `E` to a compatible function pointer type `F` if `E` contains at least one method that is applicable in its normal form to an argument list constructed by use of the parameter types and modifiers of `F`, as described in the following. - A single method `M` is selected corresponding to a method invocation of the form `E(A)` with the following modifications: - The arguments list `A` is a list of expressions, each classified as a variable and with the type and modifier (`ref`, `out`, or `in`) of the corresponding _funcptr\_parameter\_list_ of `F`. - The candidate methods are only those methods that are applicable in their normal form, not those applicable in their expanded form. - The candidate methods are only those methods that are static. - If the algorithm of overload resolution produces an error, then a compile-time error occurs. Otherwise, the algorithm produces a single best method `M` having the same number of parameters as `F` and the conversion is considered to exist. - The selected method `M` must be compatible (as defined above) with the function pointer type `F`. Otherwise, a compile-time error occurs. - The result of the conversion is a function pointer of type `F`. This means developers can depend on overload resolution rules to work in conjunction with the address-of operator: ``` csharp unsafe class Util { public static void Log() { } public static void Log(string p1) { } public static void Log(int i) { } void Use() { delegate* a1 = &Log; // Log() delegate* a2 = &Log; // Log(int i) // Error: ambiguous conversion from method group Log to "void*" void* v = &Log; } } ``` The address-of operator will be implemented using the `ldftn` instruction. Restrictions of this feature: - Only applies to methods marked as `static`. - Non-`static` local functions cannot be used in `&`. The implementation details of these methods are deliberately not specified by the language. This includes whether they are static vs. instance or exactly what signature they are emitted with. ### Operators on Function Pointer Types The section in unsafe code on expressions is modified as such: > In an unsafe context, several constructs are available for operating on all _pointer\_type_s that are not _funcptr\_type_s: > > * The `*` operator may be used to perform pointer indirection ([§23.6.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2362-pointer-indirection)). > * The `->` operator may be used to access a member of a struct through a pointer ([§23.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2363-pointer-member-access)). > * The `[]` operator may be used to index a pointer ([§23.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2364-pointer-element-access)). > * The `&` operator may be used to obtain the address of a variable ([§23.6.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2365-the-address-of-operator)). > * The `++` and `--` operators may be used to increment and decrement pointers ([§23.6.6](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2366-pointer-increment-and-decrement)). > * The `+` and `-` operators may be used to perform pointer arithmetic ([§23.6.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2367-pointer-arithmetic)). > * The `==`, `!=`, `<`, `>`, `<=`, and `=>` operators may be used to compare pointers ([§23.6.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2368-pointer-comparison)). > * The `stackalloc` operator may be used to allocate memory from the call stack ([§23.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers)). > * The `fixed` statement may be used to temporarily fix a variable so its address can be obtained ([§23.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#237-the-fixed-statement)). > > In an unsafe context, several constructs are available for operating on all _funcptr\_type_s: > * The `&` operator may be used to obtain the address of static methods ([Allow address-of to target methods](#allow-address-of-to-target-methods)) > * The `==`, `!=`, `<`, `>`, `<=`, and `=>` operators may be used to compare pointers ([§23.6.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2368-pointer-comparison)). Additionally, we modify all the sections in `Pointers in expressions` to forbid function pointer types, except `Pointer comparison` and `The sizeof operator`. ### Better function member [§12.6.4.3 Better function member](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12643-better-function-member) will be changed to include the following line: > A `delegate*` is more specific than `void*` This means that it is possible to overload on `void*` and a `delegate*` and still sensibly use the address-of operator. ### Type Inference In unsafe code, the following changes are made to the type inference algorithms: #### Input types [§12.6.3.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12634-input-types) The following is added: > If `E` is an address-of method group and `T` is a function pointer type then all the parameter types of `T` are input types of `E` with type `T`. #### Output types [§12.6.3.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12635-output-types) The following is added: > If `E` is an address-of method group and `T` is a function pointer type then the return type of `T` is an output type of `E` with type `T`. #### Output type inferences [§12.6.3.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12637-output-type-inferences) The following bullet is added between bullets 2 and 3: > * If `E` is an address-of method group and `T` is a function pointer type with parameter types `T1...Tk` and return type `Tb`, and overload resolution of `E` with the types `T1..Tk` yields a single method with return type `U`, then a _lower-bound inference_ is made from `U` to `Tb`. #### Better conversion from expression [§12.6.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12645-better-conversion-from-expression) The following sub-bullet is added as a case to bullet 2: > * `V` is a function pointer type `delegate*` and `U` is a function pointer type `delegate*`, and the calling convention of `V` is identical to `U`, and the refness of `Vi` is identical to `Ui`. #### Lower-bound inferences [§12.6.3.10](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#126310-lower-bound-inferences) The following case is added to bullet 3: > * `V` is a function pointer type `delegate*` and there is a function pointer type `delegate*` such that `U` is identical to `delegate*`, and the calling convention of `V` is identical to `U`, and the refness of `Vi` is identical to `Ui`. The first bullet of inference from `Ui` to `Vi` is modified to: > * If `U` is not a function pointer type and `Ui` is not known to be a reference type, or if `U` is a function pointer type and `Ui` is not known to be > a function pointer type or a reference type, then an _exact inference_ is made Then, added after the 3rd bullet of inference from `Ui` to `Vi`: > * Otherwise, if `V` is `delegate*` then inference depends on the i-th parameter of `delegate*`: > * If V1: > * If the return is by value, then a _lower-bound inference_ is made. > * If the return is by reference, then an _exact inference_ is made. > * If V2..Vk: > * If the parameter is by value, then an _upper-bound inference_ is made. > * If the parameter is by reference, then an _exact inference_ is made. #### Upper-bound inferences [§12.6.3.11](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#126311-upper-bound-inferences) The following case is added to bullet 2: > * `U` is a function pointer type `delegate*` and `V` is a function pointer type which is identical to `delegate*`, and the calling convention of `U` is identical to `V`, and the refness of `Ui` is identical to `Vi`. The first bullet of inference from `Ui` to `Vi` is modified to: > * If `U` is not a function pointer type and `Ui` is not known to be a reference type, or if `U` is a function pointer type and `Ui` is not known to be > a function pointer type or a reference type, then an _exact inference_ is made Then added after the 3rd bullet of inference from `Ui` to `Vi`: > * Otherwise, if `U` is `delegate*` then inference depends on the i-th parameter of `delegate*`: > * If U1: > * If the return is by value, then an _upper-bound inference_ is made. > * If the return is by reference, then an _exact inference_ is made. > * If U2..Uk: > * If the parameter is by value, then a _lower-bound inference_ is made. > * If the parameter is by reference, then an _exact inference_ is made. ## Metadata representation of `in`, `out`, and `ref readonly` parameters and return types Function pointer signatures have no parameter flags location, so we must encode whether parameters and the return type are `in`, `out`, or `ref readonly` by using modreqs. ### `in` We reuse `System.Runtime.InteropServices.InAttribute`, applied as a `modreq` to the ref specifier on a parameter or return type, to mean the following: * If applied to a parameter ref specifier, this parameter is treated as `in`. * If applied to the return type ref specifier, the return type is treated as `ref readonly`. ### `out` We use `System.Runtime.InteropServices.OutAttribute`, applied as a `modreq` to the ref specifier on a parameter type, to mean that the parameter is an `out` parameter. ### Errors * It is an error to apply `OutAttribute` as a modreq to a return type. * It is an error to apply both `InAttribute` and `OutAttribute` as a modreq to a parameter type. * If either are specified via modopt, they are ignored. ### Metadata Representation of Calling Conventions Calling conventions are encoded in a method signature in metadata by a combination of the `CallKind` flag in the signature and zero or more `modopt`s at the start of the signature. ECMA-335 currently declares the following elements in the `CallKind` flag: ```antlr CallKind : default | unmanaged cdecl | unmanaged fastcall | unmanaged thiscall | unmanaged stdcall | varargs ; ``` Of these, function pointers in C# will support all but `varargs`. In addition, the runtime (and eventually 335) will be updated to include a new `CallKind` on new platforms. This does not have a formal name currently, but this document will use `unmanaged ext` as a placeholder to stand for the new extensible calling convention format. With no `modopt`s, `unmanaged ext` is the platform default calling convention, `unmanaged` without the square brackets. #### Mapping the `calling_convention_specifier` to a `CallKind` A `calling_convention_specifier` that is omitted, or specified as `managed`, maps to the `default` `CallKind`. This is default `CallKind` of any method not attributed with `UnmanagedCallersOnly`. C# recognizes 4 special identifiers that map to specific existing unmanaged `CallKind`s from ECMA 335. In order for this mapping to occur, these identifiers must be specified on their own, with no other identifiers, and this requirement is encoded into the spec for `unmanaged_calling_convention`s. These identifiers are `Cdecl`, `Thiscall`, `Stdcall`, and `Fastcall`, which correspond to `unmanaged cdecl`, `unmanaged thiscall`, `unmanaged stdcall`, and `unmanaged fastcall`, respectively. If more than one `identifer` is specified, or the single `identifier` is not of the specially recognized identifiers, we perform special name lookup on the identifier with the following rules: * We prepend the `identifier` with the string `CallConv` * We look only at types defined in the `System.Runtime.CompilerServices` namespace. * We look only at types defined in the core library of the application, which is the library that defines `System.Object` and has no dependencies. * We look only at public types. If lookup succeeds on all of the `identifier`s specified in an `unmanaged_calling_convention`, we encode the `CallKind` as `unmanaged ext`, and encode each of the resolved types in the set of `modopt`s at the beginning of the function pointer signature. As a note, these rules mean that users cannot prefix these `identifier`s with `CallConv`, as that will result in looking up `CallConvCallConvVectorCall`. When interpreting metadata, we first look at the `CallKind`. If it is anything other than `unmanaged ext`, we ignore all `modopt`s on the return type for the purposes of determining the calling convention, and use only the `CallKind`. If the `CallKind` is `unmanaged ext`, we look at the modopts at the start of the function pointer type, taking the union of all types that meet the following requirements: * The is defined in the core library, which is the library that references no other libraries and defines `System.Object`. * The type is defined in the `System.Runtime.CompilerServices` namespace. * The type starts with the prefix `CallConv`. * The type is public. These represent the types that must be found when performing lookup on the `identifier`s in an `unmanaged_calling_convention` when defining a function pointer type in source. It is an error to attempt to use a function pointer with a `CallKind` of `unmanaged ext` if the target runtime does not support the feature. This will be determined by looking for the presence of the `System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind` constant. If this constant is present, the runtime is considered to support the feature. ### `System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute` `System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute` is an attribute used by the CLR to indicate that a method should be called with a specific calling convention. Because of this, we introduce the following support for working with the attribute: * It is an error to directly call a method annotated with this attribute from C#. Users must obtain a function pointer to the method and then invoke that pointer. * It is an error to apply the attribute to anything other than an ordinary static method or ordinary static local function. The C# compiler will mark any non-static or static non-ordinary methods imported from metadata with this attribute as unsupported by the language. * It is an error for a method marked with the attribute to have a parameter or return type that is not an `unmanaged_type`. * It is an error for a method marked with the attribute to have type parameters, even if those type parameters are constrained to `unmanaged`. * It is an error for a method in a generic type to be marked with the attribute. * It is an error to convert a method marked with the attribute to a delegate type. * It is an error to specify any types for `UnmanagedCallersOnly.CallConvs` that do not meet the requirements for calling convention `modopt`s in metadata. When determining the calling convention of a method marked with a valid `UnmanagedCallersOnly` attribute, the compiler performs the following checks on the types specified in the `CallConvs` property to determine the effective `CallKind` and `modopt`s that should be used to determine the calling convention: * If no types are specified, the `CallKind` is treated as `unmanaged ext`, with no calling convention `modopt`s at the start of the function pointer type. * If there is one type specified, and that type is named `CallConvCdecl`, `CallConvThiscall`, `CallConvStdcall`, or `CallConvFastcall`, the `CallKind` is treated as `unmanaged cdecl`, `unmanaged thiscall`, `unmanaged stdcall`, or `unmanaged fastcall`, respectively, with no calling convention `modopt`s at the start of the function pointer type. * If multiple types are specified or the single type is not named one of the specially called out types above, the `CallKind` is treated as `unmanaged ext`, with the union of the types specified treated as `modopt`s at the start of the function pointer type. The compiler then looks at this effective `CallKind` and `modopt` collection and uses normal metadata rules to determine the final calling convention of the function pointer type. ## Open Questions ### Detecting runtime support for `unmanaged ext` https://github.com/dotnet/runtime/issues/38135 tracks adding this flag. Depending on the feedback from review, we will either use the property specified in the issue, or use the presence of `UnmanagedCallersOnlyAttribute` as the flag that determines whether the runtimes supports `unmanaged ext`. ## Considerations ### Allow instance methods The proposal could be extended to support instance methods by taking advantage of the `EXPLICITTHIS` CLI calling convention (named `instance` in C# code). This form of CLI function pointers puts the `this` parameter as an explicit first parameter of the function pointer syntax. ``` csharp unsafe class Instance { void Use() { delegate* instance f = &ToString; f(this); } } ``` This is sound but adds some complication to the proposal. Particularly because function pointers which differed by the calling convention `instance` and `managed` would be incompatible even though both cases are used to invoke managed methods with the same C# signature. Also in every case considered where this would be valuable to have there was a simple work around: use a `static` local function. ``` csharp unsafe class Instance { void Use() { static string toString(Instance i) => i.ToString(); delegate* f = &toString; f(this); } } ``` ### Don't require unsafe at declaration Instead of requiring `unsafe` at every use of a `delegate*`, only require it at the point where a method group is converted to a `delegate*`. This is where the core safety issues come into play (knowing that the containing assembly cannot be unloaded while the value is alive). Requiring `unsafe` on the other locations can be seen as excessive. This is how the design was originally intended. But the resulting language rules felt very awkward. It's impossible to hide the fact that this is a pointer value and it kept peeking through even without the `unsafe` keyword. For example the conversion to `object` can't be allowed, it can't be a member of a `class`, etc ... The C# design is to require `unsafe` for all pointer uses and hence this design follows that. Developers will still be capable of presenting a _safe_ wrapper on top of `delegate*` values the same way that they do for normal pointer types today. Consider: ``` csharp unsafe struct Action { delegate* _ptr; Action(delegate* ptr) => _ptr = ptr; public void Invoke() => _ptr(); } ``` ### Using delegates Instead of using a new syntax element, `delegate*`, simply use existing `delegate` types with a `*` following the type: ``` csharp Func* ptr = &object.ReferenceEquals; ``` Handling calling convention can be done by annotating the `delegate` types with an attribute that specifies a `CallingConvention` value. The lack of an attribute would signify the managed calling convention. Encoding this in IL is problematic. The underlying value needs to be represented as a pointer yet it also must: 1. Have a unique type to allow for overloads with different function pointer types. 1. Be equivalent for OHI purposes across assembly boundaries. The last point is particularly problematic. This mean that every assembly which uses `Func*` must encode an equivalent type in metadata even though `Func*` is defined in an assembly though don't control. Additionally any other type which is defined with the name `System.Func` in an assembly that is not mscorlib must be different than the version defined in mscorlib. One option that was explored was emitting such a pointer as `mod_req(Func) void*`. This doesn't work though as a `mod_req` cannot bind to a `TypeSpec` and hence cannot target generic instantiations. ### Named function pointers The function pointer syntax can be cumbersome, particularly in complex cases like nested function pointers. Rather than have developers type out the signature every time the language could allow for named declarations of function pointers as is done with `delegate`. ``` csharp func* void Action(); unsafe class NamedExample { void M(Action a) { a(); } } ``` Part of the problem here is the underlying CLI primitive doesn't have names hence this would be purely a C# invention and require a bit of metadata work to enable. That is doable but is a significant about of work. It essentially requires C# to have a companion to the type def table purely for these names. Also when the arguments for named function pointers were examined we found they could apply equally well to a number of other scenarios. For example it would be just as convenient to declare named tuples to reduce the need to type out the full signature in all cases. ``` csharp (int x, int y) Point; class NamedTupleExample { void M(Point p) { Console.WriteLine(p.x); } } ``` After discussion we decided to not allow named declaration of `delegate*` types. If we find there is significant need for this based on customer usage feedback then we will investigate a naming solution that works for function pointers, tuples, generics, etc. This is likely to be similar in form to other suggestions like full `typedef` support in the language. ## Future Considerations ### static delegates This refers to [the proposal](https://github.com/dotnet/csharplang/issues/302) to allow for the declaration of `delegate` types which can only refer to `static` members. The advantage being that such `delegate` instances can be allocation free and better in performance sensitive scenarios. If the function pointer feature is implemented the `static delegate` proposal will likely be closed out. The proposed advantage of that feature is the allocation free nature. However recent investigations have found that is not possible to achieve due to assembly unloading. There must be a strong handle from the `static delegate` to the method it refers to in order to keep the assembly from being unloaded out from under it. To maintain every `static delegate` instance would be required to allocate a new handle which runs counter to the goals of the proposal. There were some designs where the allocation could be amortized to a single allocation per call-site but that was a bit complex and didn't seem worth the trade off. That means developers essentially have to decide between the following trade offs: 1. Safety in the face of assembly unloading: this requires allocations and hence `delegate` is already a sufficient option. 1. No safety in face of assembly unloading: use a `delegate*`. This can be wrapped in a `struct` to allow usage outside an `unsafe` context in the rest of the code. ================================================ FILE: proposals/csharp-9.0/init.md ================================================ # Init Only Setters [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary This proposal adds the concept of init only properties and indexers to C#. These properties and indexers can be set at the point of object creation but become effectively `get` only once object creation has completed. This allows for a much more flexible immutable model in C#. ## Motivation The underlying mechanisms for building immutable data in C# haven't changed since 1.0. They remain: 1. Declaring fields as `readonly`. 1. Declaring properties that contain only a `get` accessor. These mechanisms are effective at allowing the construction of immutable data but they do so by adding cost to the boilerplate code of types and opting such types out of features like object and collection initializers. This means developers must choose between ease of use and immutability. A simple immutable object like `Point` requires twice as much boiler plate code to support construction as it does to declare the type. The bigger the type the bigger the cost of this boiler plate: ```cs struct Point { public int X { get; } public int Y { get; } public Point(int x, int y) { this.X = x; this.Y = y; } } ``` The `init` accessor makes immutable objects more flexible by allowing the caller to mutate the members during the act of construction. That means the object's immutable properties can participate in object initializers and thus removes the need for all constructor boilerplate in the type. The `Point` type is now simply: ```cs struct Point { public int X { get; init; } public int Y { get; init; } } ``` The consumer can then use object initializers to create the object ```cs var p = new Point() { X = 42, Y = 13 }; ``` ## Detailed Design ### init accessors An init only property (or indexer) is declared by using the `init` accessor in place of the `set` accessor: ```cs class Student { public string FirstName { get; init; } public string LastName { get; init; } } ``` An instance property containing an `init` accessor is considered settable in the following circumstances, except when in a local function or lambda: - During an object initializer - During a `with` expression initializer - Inside an instance constructor of the containing or derived type, on `this` or `base` - Inside the `init` accessor of any property, on `this` or `base` - Inside attribute usages with named parameters The times above in which the `init` accessors are settable are collectively referred to in this document as the construction phase of the object. This means the `Student` class can be used in the following ways: ```cs var s = new Student() { FirstName = "Jared", LastName = "Parosns", }; s.LastName = "Parsons"; // Error: LastName is not settable ``` The rules around when `init` accessors are settable extend across type hierarchies. If the member is accessible and the object is known to be in the construction phase then the member is settable. That specifically allows for the following: ```cs class Base { public bool Value { get; init; } } class Derived : Base { public Derived() { // Not allowed with get only properties but allowed with init Value = true; } } class Consumption { void Example() { var d = new Derived() { Value = true }; } } ``` At the point an `init` accessor is invoked, the instance is known to be in the open construction phase. Hence an `init` accessor is allowed to take the following actions in addition to what a normal `set` accessor can do: 1. Call other `init` accessors available through `this` or `base` 1. Assign `readonly` fields declared on the same type through `this` ```cs class Complex { readonly int Field1; int Field2; int Prop1 { get; init; } int Prop2 { get => 42; init { Field1 = 13; // okay Field2 = 13; // okay Prop1 = 13; // okay } } } ``` The ability to assign `readonly` fields from an `init` accessor is limited to those fields declared on the same type as the accessor. It cannot be used to assign `readonly` fields in a base type. This rule ensures that type authors remain in control over the mutability behavior of their type. Developers who do not wish to utilize `init` cannot be impacted from other types choosing to do so: ```cs class Base { internal readonly int Field; internal int Property { get => Field; init => Field = value; // Okay } internal int OtherProperty { get; init; } } class Derived : Base { internal readonly int DerivedField; internal int DerivedProperty { get => DerivedField; init { DerivedField = 42; // Okay Property = 0; // Okay Field = 13; // Error Field is readonly } } public Derived() { Property = 42; // Okay Field = 13; // Error Field is readonly } } ``` When `init` is used in a virtual property then all the overrides must also be marked as `init`. Likewise it is not possible to override a simple `set` with `init`. ```cs class Base { public virtual int Property { get; init; } } class C1 : Base { public override int Property { get; init; } } class C2 : Base { // Error: Property must have init to override Base.Property public override int Property { get; set; } } ``` An `interface` declaration can also participate in `init` style initialization via the following pattern: ```cs interface IPerson { string Name { get; init; } } class Init { void M() where T : IPerson, new() { var local = new T() { Name = "Jared" }; local.Name = "Jraed"; // Error } } ``` Restrictions of this feature: - The `init` accessor can only be used on instance properties - A property cannot contain both an `init` and `set` accessor - All overrides of a property must have `init` if the base had `init`. This rule also applies to interface implementation. ### Readonly structs `init` accessors (both auto-implemented accessors and manually-implemented accessors) are permitted on properties of `readonly struct`s, as well as `readonly` properties. `init` accessors are not permitted to be marked `readonly` themselves, in both `readonly` and non-`readonly` `struct` types. ```cs readonly struct ReadonlyStruct1 { public int Prop1 { get; init; } // Allowed } struct ReadonlyStruct2 { public readonly int Prop2 { get; init; } // Allowed public int Prop3 { get; readonly init; } // Error } ``` ### Metadata encoding Property `init` accessors will be emitted as a standard `set` accessor with the return type marked with a modreq of `IsExternalInit`. This is a new type which will have the following definition: ```cs namespace System.Runtime.CompilerServices { public sealed class IsExternalInit { } } ``` The compiler will match the type by full name. There is no requirement that it appear in the core library. If there are multiple types by this name then the compiler will tie break in the following order: 1. The one defined in the project being compiled 1. The one defined in corelib If neither of these exist then a type ambiguity error will be issued. The design for `IsExternalInit` is futher covered in [this issue](https://github.com/dotnet/runtime/issues/34978) ## Questions ### Breaking changes One of the main pivot points in how this feature is encoded will come down to the following question: > Is it a binary breaking change to replace `init` with `set`? Replacing `init` with `set` and thus making a property fully writable is never a source breaking change on a non-virtual property. It simply expands the set of scenarios where the property can be written. The only behavior in question is whether or not this remains a binary breaking change. If we want to make the change of `init` to `set` a source and binary compatible change then it will force our hand on the modreq vs. attributes decision below because it will rule out modreqs as a solution. If on the other hand this is seen as a non-interesting then this will make the modreq vs. attribute decision less impactful. **Resolution** This scenario is not seen as compelling by LDM. ### Modreqs vs. attributes The emit strategy for `init` property accessors must choose between using attributes or modreqs when emitting during metadata. These have different trade offs that need to be considered. Annotating a property set accessor with a modreq declaration means CLI compliant compilers will ignore the accessor unless it understands the modreq. That means only compilers aware of `init` will read the member. Compilers unaware of `init` will ignore the `set` accessor and hence will not accidentally treat the property as read / write. The downside of modreq is `init` becomes a part of the binary signature of the `set` accessor. Adding or removing `init` will break binary compatbility of the application. Using attributes to annotate the `set` accessor means that only compilers which understand the attribute will know to limit access to it. A compiler unaware of `init` will see it as a simple read / write property and allow access. This would seemingly mean this decision is a choice between extra safety at the expense of binary compatibility. Digging in a bit the extra safety is not exactly what it seems. It will not protect against the following circumstances: 1. Reflection over `public` members 1. The use of `dynamic` 1. Compilers that don't recognize modreqs It should also be considered that, when we complete the IL verification rules for .NET 5, `init` will be one of those rules. That means extra enforcement will be gained from simply verifying compilers emitting verifiable IL. The primary languages for .NET (C#, F# and VB) will all be updated to recognize these `init` accessors. Hence the only realistic scenario here is when a C# 9 compiler emits `init` properties and they are seen by an older toolset such as C# 8, VB 15, etc ... C# 8. That is the trade off to consider and weigh against binary compatibility. **Note** This discussion primarily applies to members only, not to fields. While `init` fields were rejected by LDM they are still interesting to consider for the modreq vs. attribute discussion. The `init` feature for fields is a relaxation of the existing restriction of `readonly`. That means if we emit the fields as `readonly` + an attribute there is no risk of older compilers mis-using the field because they would already recognize `readonly`. Hence using a modreq here doesn't add any extra protection. **Resolution** The feature will use a modreq to encode the property `init` setter. The compelling factors were (in no particular order): * Desire to discourage older compilers from violating `init` semantics * Desire to make adding or removing `init` in a `virtual` declaration or `interface` both a source and binary breaking change. Given there was also no significant support for removing `init` to be a binary compatible change it made the choice of using modreq straight forward. ### init vs. initonly There were three syntax forms which got significant consideration during our LDM meeting: ```cs // 1. Use init int Option1 { get; init; } // 2. Use init set int Option2 { get; init set; } // 3. Use initonly int Option3 { get; initonly; } ``` **Resolution** There was no syntax which was overwhelmingly favored in LDM. One point which got significant attention was how the choice of syntax would impact our ability to do `init` members as a general feature in the future. Choosing option 1 would mean that it would be difficult to define a property which had an `init` style `get` method in the future. Eventually it was decided that if we decided to go forward with general `init` members in future, we could allow `init` to be a modifier in the property accessor list as well as a short hand for `init set`. Essentially the following two declarations would be identical. ```cs int Property1 { get; init; } int Property1 { get; init set; } ``` The decision was made to move forward with `init` as a standalone accessor in the property accessor list. ### Warn on failed init Consider the following scenario. A type declares an `init` only member which is not set in the constructor. Should the code which constructs the object get a warning if they failed to initialize the value? At that point it is clear the field will never be set and hence has a lot of similarities with the warning around failing to initialize `private` data. Hence a warning would seemingly have some value here? There are significant downsides to this warning though: 1. It complicates the compatibility story of changing `readonly` to `init`. 1. It requires carrying additional metadata around to denote the members which are required to be initialized by the caller. Further if we believe there is value here in the overall scenario of forcing object creators to be warned / error'd about specific fields then this likely makes sense as a general feature. There is no reason it should be limited to just `init` members. **Resolution** There will be no warning on consumption of `init` fields and properties. LDM wants to have a broader discussion on the idea of required fields and properties. That may cause us to come back and reconsider our position on `init` members and validation. ## Allow init as a field modifier In the same way `init` can serve as a property accessor it could also serve as a designation on fields to give them similar behaviors as `init` properties. That would allow for the field to be assigned before construction was complete by the type, derived types, or object initializers. ```cs class Student { public init string FirstName; public init string LastName; } var s = new Student() { FirstName = "Jarde", LastName = "Parsons", } s.FirstName = "Jared"; // Error FirstName is readonly ``` In metadata these fields would be marked in the same way as `readonly` fields but with an additional attribute or modreq to indicate they are `init` style fields. **Resolution** LDM agrees this proposal is sound but overall the scenario felt disjoint from properties. The decision was to proceed only with `init` properties for now. This has a suitable level of flexibility as an `init` property can mutate a `readonly` field on the declaring type of the property. This will be reconsidered if there is significant customer feedback that justifies the scenario. ### Allow init as a type modifier In the same way the `readonly` modifier can be applied to a `struct` to automatically declare all fields as `readonly`, the `init` only modifier can be declared on a `struct` or `class` to automatically mark all fields as `init`. This means the following two type declarations are equivalent: ```cs struct Point { public init int X; public init int Y; } // vs. init struct Point { public int X; public int Y; } ``` **Resolution** This feature is too *cute* here and conflicts with the `readonly struct` feature on which it is based. The `readonly struct` feature is simple in that it applies `readonly` to all members: fields, methods, etc ... The `init struct` feature would only apply to properties. This actually ends up making it confusing for users. Given that `init` is only valid on certain aspects of a type, we rejected the idea of having it as a type modifier. ## Considerations ### Compatibility The `init` feature is designed to be compatible with existing `get` only properties. Specifically it is meant to be a completely additive change for a property which is `get` only today but desires more flexbile object creation semantics. For example consider the following type: ```cs class Name { public string First { get; } public string Last { get; } public Name(string first, string last) { First = first; Last = last; } } ``` Adding `init` to these properties is a non-breaking change: ```cs class Name { public string First { get; init; } public string Last { get; init; } public Name(string first, string last) { First = first; Last = last; } } ``` ### IL verification When .NET Core decides to re-implement IL verification, the rules will need to be adjusted to account for `init` members. This will need to be included in the rule changes for non-mutating access to `readonly` data. The IL verification rules will need to be broken into two parts: 1. Allowing `init` members to set a `readonly` field. 1. Determining when an `init` member can be legally called. The first is a simple adjustment to the existing rules. The IL verifier can be taught to recognize `init` members and from there it just needs to consider a `readonly` field to be settable on `this` in such a member. The second rule is more complicated. In the simple case of object initializers the rule is straight forward. It should be legal to call `init` members when the result of a `new` expression is still on the stack. That is until the value has been stored in a local, array element or field or passed as an argument to another method it will still be legal to call `init` members. This ensures that once the result of the `new` expression is published to a named identifier (other than `this`) then it will no longer be legal to call `init` members. The more complicated case though is when we mix `init` members, object initializers and `await`. That can cause the newly created object to be temporarily hoisted into a state machine and hence put into a field. ```cs var student = new Student() { Name = await SomeMethod() }; ``` Here the result of `new Student()` will be hoisted into a state machine as a field before the set of `Name` occurs. The compiler will need to mark such hoisted fields in a way that the IL verifier understands they're not user accessible and hence doesn't violate the intended semantics of `init`. ### init members The `init` modifier could be extended to apply to all instance members. This would generalize the concept of `init` during object construction and allow types to declare helper methods that could participate in the construction process to initialize `init` fields and properties. Such members would have all the restrictions that an `init` accessor does in this design. The need is questionable though and this can be safely added in a future version of the language in a compatible manner. ### Generate three accessors One potential implementation of `init` properties is to make `init` completely separate from `set`. That means that a property can potentially have three different accessors: `get`, `set`, and `init`. This has the potential advantage of allowing the use of modreq to enforce correctness while maintaining binary compatibility. The implementation would roughly be the following: 1. An `init` accessor is always emitted if there is a `set`. When not defined by the developer it is simply a reference to `set`. 1. The set of a property in an object initializer will always use `init` if present but fall back to `set` if it's missing. This means that a developer can always safely delete `init` from a property. The downside of this design is that is only useful if `init` is **always** emitted when there is a `set`. The language can't know if `init` was deleted in the past, it has to assume it was and hence the `init` must always be emitted. That would cause a significant metadata expansion and is simply not worth the cost of the compatibility here. ================================================ FILE: proposals/csharp-9.0/lambda-discard-parameters.md ================================================ # Lambda discard parameters [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Allow discards (`_`) to be used as parameters of lambdas and anonymous methods. For example: - lambdas: `(_, _) => 0`, `(int _, int _) => 0` - anonymous methods: `delegate(int _, int _) { return 0; }` ## Motivation Unused parameters do not need to be named. The intent of discards is clear, i.e. they are unused/discarded. ## Detailed design Method parameters - [§15.6.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1562-method-parameters) In the parameter list of a lambda or anonymous method with more than one parameter named `_`, such parameters are discard parameters. Note: if a single parameter is named `_` then it is a regular parameter for backwards compatibility reasons. Discard parameters do not introduce any names to any scopes. Note this implies they do not cause any `_` (underscore) names to be hidden. Simple names ([§12.8.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1284-simple-names)) If `K` is zero and the *simple_name* appears within a *block* and if the *block*'s (or an enclosing *block*'s) local variable declaration space (Declarations - [§7.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#73-declarations)) contains a local variable, parameter (with the exception of discard parameters) or constant with name `I`, then the *simple_name* refers to that local variable, parameter or constant and is classified as a variable or value. Scopes - [§7.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#77-scopes) With the exception of discard parameters, the scope of a parameter declared in a *lambda_expression* ([§12.19](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1219-anonymous-function-expressions)) is the *anonymous_function_body* of that *lambda_expression* With the exception of discard parameters, the scope of a parameter declared in an *anonymous_method_expression* ([§12.19](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1219-anonymous-function-expressions)) is the *block* of that *anonymous_method_expression*. ## Related spec sections - Corresponding parameters - [§12.6.2.2](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12622-corresponding-parameters) ================================================ FILE: proposals/csharp-9.0/local-function-attributes.md ================================================ # Attributes on local functions [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Attributes Local function declarations are now permitted to have attributes ([§22](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/attributes.md#22-attributes)). Parameters and type parameters on local functions are also allowed to have attributes. Attributes with a specified meaning when applied to a method, its parameters, or its type parameters will have the same meaning when applied to a local function, its parameters, or its type parameters, respectively. A local function can be made conditional in the same sense as a conditional method ([§22.5.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/attributes.md#2253-the-conditional-attribute)) by decorating it with a `[ConditionalAttribute]`. A conditional local function must also be `static`. All restrictions on conditional methods also apply to conditional local functions, including that the return type must be `void`. ## Extern The `extern` modifier is now permitted on local functions. This makes the local function external in the same sense as an external method ([§15.6.8](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1468-external-methods)). Similarly to an external method, the *local-function-body* of an external local function must be a semicolon. A semicolon *local-function-body* is only permitted on an external local function. An external local function must also be `static`. ## Syntax The [§13.6.4](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1364-local-function-declarations), or [local functions grammar](../csharp-7.0/local-functions.md#syntax-grammar) is modified as follows: ``` local-function-header : attributes? local-function-modifiers? return-type identifier type-parameter-list? ( formal-parameter-list? ) type-parameter-constraints-clauses ; local-function-modifiers : (async | unsafe | static | extern)* ; local-function-body : block | arrow-expression-body | ';' ; ``` ================================================ FILE: proposals/csharp-9.0/module-initializers.md ================================================ # Module Initializers [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Although the .NET platform has a [feature](https://github.com/dotnet/runtime/blob/master/docs/design/specs/Ecma-335-Augments.md#module-initializer) that directly supports writing initialization code for the assembly (technically, the module), it is not exposed in C#. This is a rather niche scenario, but once you run into it the solutions appear to be pretty painful. There are reports of [a number of customers](https://www.google.com/search?q=.net+module+constructor+c%23&oq=.net+module+constructor) (inside and outside Microsoft) struggling with the problem, and there are no doubt more undocumented cases. ## Motivation [motivation]: #motivation - Enable libraries to do eager, one-time initialization when loaded, with minimal overhead and without the user needing to explicitly call anything - One particular pain point of current `static` constructor approaches is that the runtime must do additional checks on usage of a type with a static constructor, in order to decide whether the static constructor needs to be run or not. This adds measurable overhead. - Enable source generators to run some global initialization logic without the user needing to explicitly call anything ## Detailed design [design]: #detailed-design A method can be designated as a module initializer by decorating it with a `[ModuleInitializer]` attribute. ```cs using System; namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class ModuleInitializerAttribute : Attribute { } } ``` The attribute can be used like this: ```cs using System.Runtime.CompilerServices; class C { [ModuleInitializer] internal static void M1() { // ... } } ``` Some requirements are imposed on the method targeted with this attribute: 1. The method must be `static`. 1. The method must be parameterless. 1. The method must return `void`. 1. The method must not be generic or be contained in a generic type. 1. The method must be accessible from the containing module. - This means the method's effective accessibility must be `internal` or `public`. - This also means the method cannot be a local function. When one or more valid methods with this attribute are found in a compilation, the compiler will emit a module initializer which calls each of the attributed methods. The calls will be emitted in a reserved, but deterministic order. ## Drawbacks [drawbacks]: #drawbacks Why should we *not* do this? - Perhaps the existing third-party tooling for "injecting" module initializers is sufficient for users who have been asking for this feature. ## Design meetings ### [April 8th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-08.md#module-initializers) ================================================ FILE: proposals/csharp-9.0/native-integers.md ================================================ # Native-sized integers [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Language support for a native-sized signed and unsigned integer types. The motivation is for interop scenarios and for low-level libraries. ## Design [design]: #design The identifiers `nint` and `nuint` are new contextual keywords that represent native signed and unsigned integer types. The identifiers are only treated as keywords when name lookup does not find a viable result at that program location. ```C# nint x = 3; _ = nint.Equals(x, 3); ``` The types `nint` and `nuint` are represented by the underlying types `System.IntPtr` and `System.UIntPtr` with compiler surfacing additional conversions and operations for those types as native ints. ### Constants Constant expressions may be of type `nint` or `nuint`. There is no direct syntax for native int literals. Implicit or explicit casts of other integral constant values can be used instead: `const nint i = (nint)42;`. `nint` constants are in the range [ `int.MinValue`, `int.MaxValue` ]. `nuint` constants are in the range [ `uint.MinValue`, `uint.MaxValue` ]. There are no `MinValue` or `MaxValue` fields on `nint` or `nuint` because, other than `nuint.MinValue`, those values cannot be emitted as constants. Constant folding is supported for all unary operators { `+`, `-`, `~` } and binary operators { `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `&`, `|`, `^`, `<<`, `>>` }. Constant folding operations are evaluated with `Int32` and `UInt32` operands rather than native ints for consistent behavior regardless of compiler platform. If the operation results in a constant value in 32-bits, constant folding is performed at compile-time. Otherwise the operation is executed at runtime and not considered a constant. ### Conversions There is an identity conversion between `nint` and `IntPtr`, and between `nuint` and `UIntPtr`. There is an identity conversion between compound types that differ by native ints and underlying types only: arrays, `Nullable<>`, constructed types, and tuples. The tables below cover the conversions between special types. (The IL for each conversion includes the variants for `unchecked` and `checked` contexts if different.) General notes on the table below: - `conv.u` is a zero-extending conversion to native integer and `conv.i` is sign-extending conversion to native integer. - `checked` contexts for both **widening** and **narrowing** are: - `conv.ovf.*` for `signed to *` - `conv.ovf.*.un` for `unsigned to *` - `unchecked` contexts for **widening** are: - `conv.i*` for `signed to *` (where * is the target width) - `conv.u*` for `unsigned to *` (where * is the target width) - `unchecked` contexts for **narrowing** are: - `conv.i*` for `any to signed *` (where * is the target width) - `conv.u*` for `any to unsigned *` (where * is the target width) Taking a few examples: - `sbyte to nint` and `sbyte to nuint` use `conv.i` while `byte to nint` and `byte to nuint` use `conv.u` because they are all **widening**. - `nint to byte` and `nuint to byte` use `conv.u1` while `nint to sbyte` and `nuint to sbyte` use `conv.i1`. For `byte`, `sbyte`, `short`, and `ushort` the "stack type" is `int32`. So `conv.i1` is effectively "downcast to a signed byte and then sign-extend up to int32" while `conv.u1` is effectively "downcast to an unsigned byte and then zero-extend up to int32". - `checked void* to nint` uses `conv.ovf.i.un` the same way that `checked void* to long` uses `conv.ovf.i8.un`. | Operand | Target | Conversion | IL | |:---:|:---:|:---:|:---:| | `object` | `nint` | Unboxing | `unbox` | | `void*` | `nint` | PointerToVoid | nop / `conv.ovf.i.un` | | `sbyte` | `nint` | ImplicitNumeric | `conv.i` | | `byte` | `nint` | ImplicitNumeric | `conv.u` | | `short` | `nint` | ImplicitNumeric | `conv.i` | | `ushort` | `nint` | ImplicitNumeric | `conv.u` | | `int` | `nint` | ImplicitNumeric | `conv.i` | | `uint` | `nint` | ExplicitNumeric | `conv.u` / `conv.ovf.i.un` | | `long` | `nint` | ExplicitNumeric | `conv.i` / `conv.ovf.i` | | `ulong` | `nint` | ExplicitNumeric | `conv.i` / `conv.ovf.i.un` | | `char` | `nint` | ImplicitNumeric | `conv.u` | | `float` | `nint` | ExplicitNumeric | `conv.i` / `conv.ovf.i` | | `double` | `nint` | ExplicitNumeric | `conv.i` / `conv.ovf.i` | | `decimal` | `nint` | ExplicitNumeric | `long decimal.op_Explicit(decimal) conv.i` / `... conv.ovf.i` | | `IntPtr` | `nint` | Identity | | | `UIntPtr` | `nint` | None | | | | | | | | `object` | `nuint` | Unboxing | `unbox` | | `void*` | `nuint` | PointerToVoid | nop | | `sbyte` | `nuint` | ExplicitNumeric | `conv.i` / `conv.ovf.u` | | `byte` | `nuint` | ImplicitNumeric | `conv.u` | | `short` | `nuint` | ExplicitNumeric | `conv.i` / `conv.ovf.u` | | `ushort` | `nuint` | ImplicitNumeric | `conv.u` | | `int` | `nuint` | ExplicitNumeric | `conv.i` / `conv.ovf.u` | | `uint` | `nuint` | ImplicitNumeric | `conv.u` | | `long` | `nuint` | ExplicitNumeric | `conv.u` / `conv.ovf.u` | | `ulong` | `nuint` | ExplicitNumeric | `conv.u` / `conv.ovf.u.un` | | `char` | `nuint` | ImplicitNumeric | `conv.u` | | `float` | `nuint` | ExplicitNumeric | `conv.u` / `conv.ovf.u` | | `double` | `nuint` | ExplicitNumeric | `conv.u` / `conv.ovf.u` | | `decimal` | `nuint` | ExplicitNumeric | `ulong decimal.op_Explicit(decimal) conv.u` / `... conv.ovf.u.un` | | `IntPtr` | `nuint` | None | | | `UIntPtr` | `nuint` | Identity | | |Enumeration|`nint`|ExplicitEnumeration|| |Enumeration|`nuint`|ExplicitEnumeration|| | Operand | Target | Conversion | IL | |:---:|:---:|:---:|:---:| | `nint` | `object` | Boxing | `box` | | `nint` | `void*` | PointerToVoid | nop / `conv.ovf.u` | | `nint` | `nuint` | ExplicitNumeric | `conv.u` (can be omitted) / `conv.ovf.u` | | `nint` | `sbyte` | ExplicitNumeric | `conv.i1` / `conv.ovf.i1` | | `nint` | `byte` | ExplicitNumeric | `conv.u1` / `conv.ovf.u1` | | `nint` | `short` | ExplicitNumeric | `conv.i2` / `conv.ovf.i2` | | `nint` | `ushort` | ExplicitNumeric | `conv.u2` / `conv.ovf.u2` | | `nint` | `int` | ExplicitNumeric | `conv.i4` / `conv.ovf.i4` | | `nint` | `uint` | ExplicitNumeric | `conv.u4` / `conv.ovf.u4` | | `nint` | `long` | ImplicitNumeric | `conv.i8` | | `nint` | `ulong` | ExplicitNumeric | `conv.i8` / `conv.ovf.u8` | | `nint` | `char` | ExplicitNumeric | `conv.u2` / `conv.ovf.u2` | | `nint` | `float` | ImplicitNumeric | `conv.r4` | | `nint` | `double` | ImplicitNumeric | `conv.r8` | | `nint` | `decimal` | ImplicitNumeric | `conv.i8 decimal decimal.op_Implicit(long)` | | `nint` | `IntPtr` | Identity | | | `nint` | `UIntPtr` | None | | | `nint` |Enumeration|ExplicitEnumeration|| | | | | | | `nuint` | `object` | Boxing | `box` | | `nuint` | `void*` | PointerToVoid | nop | | `nuint` | `nint` | ExplicitNumeric | `conv.i`(can be omitted) / `conv.ovf.i.un` | | `nuint` | `sbyte` | ExplicitNumeric | `conv.i1` / `conv.ovf.i1.un` | | `nuint` | `byte` | ExplicitNumeric | `conv.u1` / `conv.ovf.u1.un` | | `nuint` | `short` | ExplicitNumeric | `conv.i2` / `conv.ovf.i2.un` | | `nuint` | `ushort` | ExplicitNumeric | `conv.u2` / `conv.ovf.u2.un` | | `nuint` | `int` | ExplicitNumeric | `conv.i4` / `conv.ovf.i4.un` | | `nuint` | `uint` | ExplicitNumeric | `conv.u4` / `conv.ovf.u4.un` | | `nuint` | `long` | ExplicitNumeric | `conv.u8` / `conv.ovf.i8.un` | | `nuint` | `ulong` | ImplicitNumeric | `conv.u8` | | `nuint` | `char` | ExplicitNumeric | `conv.u2` / `conv.ovf.u2.un` | | `nuint` | `float` | ImplicitNumeric | `conv.r.un conv.r4` | | `nuint` | `double` | ImplicitNumeric | `conv.r.un conv.r8` | | `nuint` | `decimal` | ImplicitNumeric | `conv.u8 decimal decimal.op_Implicit(ulong)` | | `nuint` | `IntPtr` | None | | | `nuint` | `UIntPtr` | Identity | | | `nuint` |Enumeration|ExplicitEnumeration|| Conversion from `A` to `Nullable` is: - an implicit nullable conversion if there is an identity conversion or implicit conversion from `A` to `B`; - an explicit nullable conversion if there is an explicit conversion from `A` to `B`; - otherwise invalid. Conversion from `Nullable` to `B` is: - an explicit nullable conversion if there is an identity conversion or implicit or explicit numeric conversion from `A` to `B`; - otherwise invalid. Conversion from `Nullable` to `Nullable` is: - an identity conversion if there is an identity conversion from `A` to `B`; - an explicit nullable conversion if there is an implicit or explicit numeric conversion from `A` to `B`; - otherwise invalid. ### Operators The predefined operators are as follows. These operators are considered during overload resolution based on normal rules for implicit conversions _if at least one of the operands is of type `nint` or `nuint`_. (The IL for each operator includes the variants for `unchecked` and `checked` contexts if different.) | Unary | Operator Signature | IL | |:---:|:---:|:---:| | `+` | `nint operator +(nint value)` | `nop` | | `+` | `nuint operator +(nuint value)` | `nop` | | `-` | `nint operator -(nint value)` | `neg` | | `~` | `nint operator ~(nint value)` | `not` | | `~` | `nuint operator ~(nuint value)` | `not` | | Binary | Operator Signature | IL | |:---:|:---:|:---:| | `+` | `nint operator +(nint left, nint right)` | `add` / `add.ovf` | | `+` | `nuint operator +(nuint left, nuint right)` | `add` / `add.ovf.un` | | `-` | `nint operator -(nint left, nint right)` | `sub` / `sub.ovf` | | `-` | `nuint operator -(nuint left, nuint right)` | `sub` / `sub.ovf.un` | | `*` | `nint operator *(nint left, nint right)` | `mul` / `mul.ovf` | | `*` | `nuint operator *(nuint left, nuint right)` | `mul` / `mul.ovf.un` | | `/` | `nint operator /(nint left, nint right)` | `div` | | `/` | `nuint operator /(nuint left, nuint right)` | `div.un` | | `%` | `nint operator %(nint left, nint right)` | `rem` | | `%` | `nuint operator %(nuint left, nuint right)` | `rem.un` | | `==` | `bool operator ==(nint left, nint right)` | `beq` / `ceq` | | `==` | `bool operator ==(nuint left, nuint right)` | `beq` / `ceq` | | `!=` | `bool operator !=(nint left, nint right)` | `bne` | | `!=` | `bool operator !=(nuint left, nuint right)` | `bne` | | `<` | `bool operator <(nint left, nint right)` | `blt` / `clt` | | `<` | `bool operator <(nuint left, nuint right)` | `blt.un` / `clt.un` | | `<=` | `bool operator <=(nint left, nint right)` | `ble` | | `<=` | `bool operator <=(nuint left, nuint right)` | `ble.un` | | `>` | `bool operator >(nint left, nint right)` | `bgt` / `cgt` | | `>` | `bool operator >(nuint left, nuint right)` | `bgt.un` / `cgt.un` | | `>=` | `bool operator >=(nint left, nint right)` | `bge` | | `>=` | `bool operator >=(nuint left, nuint right)` | `bge.un` | | `&` | `nint operator &(nint left, nint right)` | `and` | | `&` | `nuint operator &(nuint left, nuint right)` | `and` | | | | nint operator |(nint left, nint right) | `or` | | | | nuint operator |(nuint left, nuint right) | `or` | | `^` | `nint operator ^(nint left, nint right)` | `xor` | | `^` | `nuint operator ^(nuint left, nuint right)` | `xor` | | `<<` | `nint operator <<(nint left, int right)` | `shl` | | `<<` | `nuint operator <<(nuint left, int right)` | `shl` | | `>>` | `nint operator >>(nint left, int right)` | `shr` | | `>>` | `nuint operator >>(nuint left, int right)` | `shr.un` | For some binary operators, the IL operators support additional operand types (see [ECMA-335](https://www.ecma-international.org/publications-and-standards/standards/ecma-335/) III.1.5 Operand type table). But the set of operand types supported by C# is limited for simplicity and for consistency with existing operators in the language. Lifted versions of the operators, where the arguments and return types are `nint?` and `nuint?`, are supported. Compound assignment operations `x op= y` where `x` or `y` are native ints follow the same rules as with other primitive types with pre-defined operators. Specifically the expression is bound as `x = (T)(x op y)` where `T` is the type of `x` and where `x` is only evaluated once. The shift operators should mask the number of bits to shift - to 5 bits if `sizeof(nint)` is 4, and to 6 bits if `sizeof(nint)` is 8. (see [§12.11](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1211-shift-operators)) in C# spec). The C#9 compiler will report errors binding to predefined native integer operators when compiling with an earlier language version, but will allow use of predefined conversions to and from native integers. `csc -langversion:9 -t:library A.cs` ```C# public class A { public static nint F; } ``` `csc -langversion:8 -r:A.dll B.cs` ```C# class B : A { static void Main() { F = F + 1; // error: nint operator+ not available with -langversion:8 F = (System.IntPtr)F + 1; // ok } } ``` ### Pointer arithmetic There are no predefined operators in C# for pointer addition or subtraction with native integer offsets. Instead, `nint` and `nuint` values are promoted to `long` and `ulong` and pointer arithmetic uses predefined operators for those types. ```C# static T* AddLeftS(nint x, T* y) => x + y; // T* operator +(long left, T* right) static T* AddLeftU(nuint x, T* y) => x + y; // T* operator +(ulong left, T* right) static T* AddRightS(T* x, nint y) => x + y; // T* operator +(T* left, long right) static T* AddRightU(T* x, nuint y) => x + y; // T* operator +(T* left, ulong right) static T* SubRightS(T* x, nint y) => x - y; // T* operator -(T* left, long right) static T* SubRightU(T* x, nuint y) => x - y; // T* operator -(T* left, ulong right) ``` ### Binary numeric promotions The _binary numeric promotions_ informative text (see [§12.4.7.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12473-binary-numeric-promotions)) in C# spec) is updated as follows: > - … > - Otherwise, if either operand is of type `ulong`, the other operand is converted to type `ulong`, or a binding-time error occurs if the other operand is of type `sbyte`, `short`, `int`, **`nint`**, or `long`. > - **Otherwise, if either operand is of type `nuint`, the other operand is converted to type `nuint`, or a binding-time error occurs if the other operand is of type `sbyte`, `short`, `int`, `nint`, or `long`.** > - Otherwise, if either operand is of type `long`, the other operand is converted to type `long`. > - Otherwise, if either operand is of type `uint` and the other operand is of type `sbyte`, `short`, **`nint`,** or `int`, both operands are converted to type `long`. > - Otherwise, if either operand is of type `uint`, the other operand is converted to type `uint`. > - **Otherwise, if either operand is of type `nint`, the other operand is converted to type `nint`.** > - Otherwise, both operands are converted to type `int`. ### Dynamic The conversions and operators are synthesized by the compiler and are not part of the underlying `IntPtr` and `UIntPtr` types. As a result those conversions and operators _are not available_ from the runtime binder for `dynamic`. ```C# nint x = 2; nint y = x + x; // ok dynamic d = x; nint z = d + x; // RuntimeBinderException: '+' cannot be applied 'System.IntPtr' and 'System.IntPtr' ``` ### Type members The only constructor for `nint` or `nuint` is the parameter-less constructor. The following members of `System.IntPtr` and `System.UIntPtr` _are explicitly excluded_ from `nint` or `nuint`: ```C# // constructors // arithmetic operators // implicit and explicit conversions public static readonly IntPtr Zero; // use 0 instead public static int Size { get; } // use sizeof() instead public static IntPtr Add(IntPtr pointer, int offset); public static IntPtr Subtract(IntPtr pointer, int offset); public int ToInt32(); public long ToInt64(); public void* ToPointer(); ``` The remaining members of `System.IntPtr` and `System.UIntPtr` _are implicitly included_ in `nint` and `nuint`. For .NET Framework 4.7.2: ```C# public override bool Equals(object obj); public override int GetHashCode(); public override string ToString(); public string ToString(string format); ``` Interfaces implemented by `System.IntPtr` and `System.UIntPtr` _are implicitly included_ in `nint` and `nuint`, with occurrences of the underlying types replaced by the corresponding native integer types. For instance if `IntPtr` implements `ISerializable, IEquatable, IComparable`, then `nint` implements `ISerializable, IEquatable, IComparable`. ### Overriding, hiding, and implementing `nint` and `System.IntPtr`, and `nuint` and `System.UIntPtr`, are considered equivalent for overriding, hiding, and implementing. Overloads cannot differ by `nint` and `System.IntPtr`, and `nuint` and `System.UIntPtr`, alone. Overrides and implementations may differ by `nint` and `System.IntPtr`, or `nuint` and `System.UIntPtr`, alone. Methods hide other methods that differ by `nint` and `System.IntPtr`, or `nuint` and `System.UIntPtr`, alone. ### Miscellaneous `nint` and `nuint` expressions used as array indices are emitted without conversion. ```C# static object GetItem(object[] array, nint index) { return array[index]; // ok } ``` `nint` and `nuint` cannot be used as an `enum` base type from C#. ```C# enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected { } ``` Reads and writes are atomic for `nint` and `nuint`. Fields may be marked `volatile` for types `nint` and `nuint`. [ECMA-334](https://www.ecma-international.org/publications-and-standards/standards/ecma-334/) 15.5.4 does not include `enum` with base type `System.IntPtr` or `System.UIntPtr` however. `default(nint)` and `new nint()` are equivalent to `(nint)0`; `default(nuint)` and `new nuint()` are equivalent to `(nuint)0`. `typeof(nint)` is `typeof(IntPtr)`; `typeof(nuint)` is `typeof(UIntPtr)`. `sizeof(nint)` and `sizeof(nuint)` are supported but require compiling in an unsafe context (as required for `sizeof(IntPtr)` and `sizeof(UIntPtr)`). The values are not compile-time constants. `sizeof(nint)` is implemented as `sizeof(IntPtr)` rather than `IntPtr.Size`; `sizeof(nuint)` is implemented as `sizeof(UIntPtr)` rather than `UIntPtr.Size`. Compiler diagnostics for type references involving `nint` or `nuint` report `nint` or `nuint` rather than `IntPtr` or `UIntPtr`. ### Metadata `nint` and `nuint` are represented in metadata as `System.IntPtr` and `System.UIntPtr`. Type references that include `nint` or `nuint` are emitted with a `System.Runtime.CompilerServices.NativeIntegerAttribute` to indicate which parts of the type reference are native ints. ```C# namespace System.Runtime.CompilerServices { [AttributeUsage( AttributeTargets.Class | AttributeTargets.Event | AttributeTargets.Field | AttributeTargets.GenericParameter | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = false)] public sealed class NativeIntegerAttribute : Attribute { public NativeIntegerAttribute() { TransformFlags = new[] { true }; } public NativeIntegerAttribute(bool[] flags) { TransformFlags = flags; } public readonly bool[] TransformFlags; } } ``` The encoding of type references with `NativeIntegerAttribute` is covered in [NativeIntegerAttribute.md](https://github.com/dotnet/roslyn/blob/master/docs/features/NativeIntegerAttribute.md). ## Alternatives [alternatives]: #alternatives An alternative to the "type erasure" approach above is to introduce new types: `System.NativeInt` and `System.NativeUInt`. ```C# public readonly struct NativeInt { public IntPtr Value; } ``` Distinct types would allow overloading distinct from `IntPtr` and would allow distinct parsing and `ToString()`. But there would be more work for the CLR to handle these types efficiently which defeats the primary purpose of the feature - efficiency. And interop with existing native int code that uses `IntPtr` would be more difficult. Another alternative is to add more native int support for `IntPtr` in the framework but without any specific compiler support. Any new conversions and arithmetic operations would be supported by the compiler automatically. But the language would not provide keywords, constants, or `checked` operations. ## Design meetings - https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-26.md - https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-06-13.md - https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-07-05.md#native-int-and-intptr-operators - https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-10-23.md - https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-03-25.md ================================================ FILE: proposals/csharp-9.0/nullable-constructor-analysis.md ================================================ # Nullable constructor analysis Champion issue: This proposal is intended to resolve a few of the outstanding problems with nullable constructor analysis. This proposal was accepted on [2020-07-27](https://github.com/dotnet/csharplang/blob/main/meetings/2020/LDM-2020-07-27.md#improved-nullable-analysis-in-constructors). ## Problems with the definite-assignment based approach Nullable analysis of constructors works by essentially running a definite assignment pass and reporting a warning if a constructor does not initialize a non-nullable reference type member (for example: a field, auto-property, or field-like event) in all code paths. The constructor is otherwise treated like an ordinary method for analysis purposes. This approach comes with a few problems. First is that the initial flow state of members is not accurate: ```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 = ""; } } ``` Another is that assertions/'throw' statements do not prevent field initialization warnings: ```cs public class C { public string Prop { get; set; } public C() // unexpected warning: 'Prop' is not initialized { Init(); if (Prop is null) { throw new Exception(); } } void Init() { Prop = "some default"; } } ``` ## Proposed approach We can address this by instead taking an approach similar to `[MemberNotNull]` analysis, where fields are marked maybe-null and a warning is given if we ever exit the method when a field is still in a maybe-null state. We can do this by introducing the following rules: **A constructor on a reference type with no initializer** *or* **A constructor on a reference type with a `: base(...)` initializer** has an initial nullable flow state determined by: - Initializing base type members to their declared state, since we expect the base constructor to initialize the base members. - Then initializing all *applicable members* in the type to the state given by assigning a `default` literal to the member. A member is applicable if: - It does not have oblivious nullability, *and* - It is instance and the constructor being analyzed is instance, or the member is static and the constructor being analyzed is static. - We expect the `default` literal to yield a `NotNull` state for non-nullable value types, a `MaybeNull` state for reference types or nullable value types, and a `MaybeDefault` state for unconstrained generics. - Then visiting the initializers for the applicable members, updating the flow state accordingly. - This allows some non-nullable reference members to be initialized using a field/property initializer, and others to be initialized within the constructor. - The expectation is that the compiler will flow-analyze and report diagnostics on the initializers once, then copy the resulting flow state as the initial state for each constructor which does not have a `: this(...)` initializer. **A constructor on a value type with a `: this()` initializer referencing the default value type constructor** has an initial flow state given by initializing all applicable members to the state given by assigning a `default` literal to the member. **A constructor on a reference type with a `: this(...)` initializer** *or* **A constructor on a value type with an initializer not referencing the default value type constructor** *or* **A constructor on a value type with no initializer** has the same initial nullable flow state as an ordinary method. Members have an initial state based on the declared annotations and nullability attributes. In the case of value types, we expect definite assignment analysis to provide the desired level of safety when there is no `: this(...)` initializer. This is the same as the existing behavior. **At each explicit or implicit 'return' in a constructor**, we give a warning for each *applicable member* whose flow state is incompatible with its annotations and nullability attributes. A reasonable proxy for this is: if assigning the member to itself at the return point would produce a nullability warning, then a nullability warning will be produced at the return point. It's possible this could result in a lot of warnings for the same members in some scenarios. As a "stretch goal" I think we should consider the following "optimizations": - If a member has an incompatible flow state at all return points in an applicable constructor, we warn on the constructor's name syntax instead of on each return point individually. - If a member has an incompatible flow state at all return points in all applicable constructors, we warn on the member declaration itself. ## Consequences of this approach ```cs public class C { public string Prop { get; set; } public C() { Prop = null; // Warning: cannot assign null to 'Prop' } // Warning: Prop may be null when exiting 'C.C()' // This is consistent with currently shipped behavior: [MemberNotNull(nameof(Prop))] void M() { Prop = null; // Warning: cannot assign null to 'Prop' } // Warning: Prop may be null when exiting 'C.M()' } ``` The above scenario produces multiple warnings corresponding to the same property. If there are more return points in the method, indefinitely many warnings could be produced depending on the number of return points in which a member has a bad flow state. However, this is consistent with the behavior we have shipped for `[MemberNotNull]` and `[NotNull]` attributes: we warn when a bad value goes in, and we warn again when you return where the variable could contain a bad value. --- ```cs public class C { public string Prop { get; set; } public C() { Prop.ToString(); // Warning: dereference of a possible null reference. } // No warning: Prop's flow state was 'promoted' to NotNull after dereference // This is also consistent with currently shipped behavior: [MemberNotNull(nameof(Prop))] void M() { Prop.ToString(); // Warning: dereference of a possible null reference. } // No warning: Prop's flow state was 'promoted' to NotNull after dereference } ``` In this scenario we never really initialize `Prop`, but we know that if we return normally from this constructor then Prop must have somehow gotten initialized. Thus this warning, while not ideal, does seem to be adequate for pointing the user toward where their problem lies. Similarly, this is consistent with the shipped behavior of `[MemberNotNull]` and `[NotNull]`. --- ```cs class C { string Prop1 { get; set; } string Prop2 { get; set; } public C(bool a) { if (a) { Prop1 = "hello"; return; // warning for Prop2 } else { return; // warning for Prop1 and for Prop2 } } } ``` This scenario demonstrates the independence of warnings at each return point, as well as the way warnings can multiply for a single member within a single constructor. It feels like there might be methods of reducing redundancy of the warnings, but this refinement could come later and improve `[MemberNotNull]`/`[NotNull]` analysis at the same time. For constructors with complex conditional logic, it does seem to be an improvement to say "at this return, you haven't initialized something yet" versus simply saying "somewhere in here, you didn't initialize something". ================================================ FILE: proposals/csharp-9.0/nullable-parameter-default-value-analysis.md ================================================ # Nullable Parameter Default Value Analysis Champion issue: ## Analysis of declarations In a method declaration it's desirable for the compiler to give warnings for parameter default values which are incompatible with the parameter's type. ```cs void M(string s = null) // warning CS8600: Converting null literal or possible null value to non-nullable type. { } ``` However, unconstrained generics present a problem where a bad value can go in but we don't warn about it for compat reasons. Therefore we adopted a strategy of simulating a assignment of the default value to the parameter in the method body, then joining in the resulting state, giving us the desired warnings in the method signature as well as the desired initial nullable state for the parameter. ```cs class C { void M0(T t) { } void M1(T t = default) // no warning here { M0(t); // warning CS8604: Possible null reference argument for parameter 't' in 'void C.M0(T t)'. } } ``` It's difficult to update the parameter initial state appropriately in all scenarios. Here are some scenarios where the approach falls over: ### [Overriding methods with optional parameters](https://github.com/dotnet/roslyn/issues/48848) ```cs Base obj = new Override(); obj.M(); // throws NRE at runtime public class Base { public virtual void M(T t = default) { } // no warning } public class Override : Base { public override void M(string s) { s.ToString(); // no warning today, but something in this sample ought to warn. :) } } ``` In the above sample we may call the method `Base.M()` and dispatch to `Override.M()`. We need to account for the possibility that the caller implicitly provided `null` as an argument for `s` via the base, but currently we do not do so. --- ### [Lambda conversion to delegates which have optional parameters](https://github.com/dotnet/roslyn/issues/48844) ```cs public delegate void Del(T t = default); public class C { public static void Main() { Del del = str => str.ToString(); // expected warning, but didn't get one del(); // throws NRE at runtime } } ``` In the above sample we expect that a lambda converted to the type `Del` will have a `MaybeNull` initial state for its parameter because of the default value. Currently we don't handle this case properly. --- ### [Abstract methods and delegate declarations which have optional parameters](https://github.com/dotnet/roslyn/issues/48847) ```cs public abstract class C { public abstract void M1(string s = null); // expected warning, but didn't get one } interface I { void M1(string s = null); // expected warning, but didn't get one } public delegate void Del1(string s = null); // expected warning, but didn't get one ``` In the above sample, we want warnings on these parameters which aren't directly associated with any method implementation. However, since these parameter lists don't have any methods with bodies that we want to flow analyze, we never hit the EnterParameters method in NullableWalker which simulates the assignments and produces the warnings. --- ### Indexers with get and set accessors ```cs public class C { public int this[int i, string s = null] // no warning here { get // entire accessor syntax has warning CS8600: Converting null literal or possible null value to non-nullable type. { return i; } set // entire accessor syntax has warning CS8600: Converting null literal or possible null value to non-nullable type. { } } } ``` This last sample is just an annoyance. Here we synthesize a distinct parameter symbol for each accessor, whose location is the entire accessor syntax. We simulate the default value assignment in each accessor and give a warning on the parameter, which ends up giving duplicate warnings that don't really show where the problem is. ## Suggested change to declaration analysis **We shouldn't update the parameter's initial state in flow analysis based on the default value.** It introduces strange complexity and missing warnings around overriding, delegate conversions, etc. that is not worth accounting for, and would cause user confusion if we did account for them. Revisiting the overriding sample from above: ```cs public class Base { public virtual void M(T t = default) { } // let's start warning here } public class Override : Base { public override void M(string s) { s.ToString(); // let's not warn here } } ``` As a user you'd probably find a warning on `s.ToString()` confusing and useless--the thing that's broken here is the incompatibility of the type and default value in `T t = default`, and that's where user's fix needs to go. **Instead, we should enforce that the default value is compatible with the parameter in all scenarios, including unconstrained generics.** I am certain that this is how we should do it in `/langversion:9` in VS 16.9. I also believe that we should do this in `/langversion:8` under the "bug fix" umbrella. `[AllowNull]` can be applied to unconstrained generic parameters to allow `default` as a default value, so C# 8 users are not blocked. I could be convinced otherwise about doing it in `/langversion:8` depending on the impact. As far as implementation strategy: we should just do this in SourceComplexParameterSymbol at the same time we bind the parameter's default value. We can ensure sufficient amount of consistency, as well as reasonable handling of suppression, perhaps by creating a NullableWalker and doing a "mini-analysis" of the assignment of the default value whose final state is discarded. ================================================ FILE: proposals/csharp-9.0/nullable-reference-types-specification.md ================================================ # Nullable Reference Types Specification [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] > ***The portions of this feature that were part of C# 8 have been incorporated into the standard. See:*** > > - [§6.5.9 Nullable directive](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/lexical-structure.md#659-nullable-directive) > - [§8.4.5 Satisfying constraints](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#845-satisfying-constraints) > - [§8.9 Reference Types and nullability](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#89-reference-types-and-nullability) > - [§10.2.6 Implicit nullable conversions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1026-implicit-nullable-conversions) > - [§10.3.4 Explicit nullable conversions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#1034-explicit-nullable-conversions) > - [§12.8.9 Null-forgiving expressions](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1289-null-forgiving-expressions) > - [§15.2.5 Type parameter constraints](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#1525-type-parameter-constraints) > - [§22.5.7 Code analysis attributes](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/attributes.md#2257-code-analysis-attributes) > > Those areas include the majority of this feature. The notable exception is the [`default` constraint](#default-constraint). This document contains much more detail on the implementation of nullable analysis provided by the roslyn compiler. This feature adds two new kinds of nullable types (nullable reference types and nullable generic types) to the existing nullable value types, and introduces a static flow analysis for purpose of null-safety. ## Syntax ### Nullable reference types and nullable type parameters Nullable reference types and nullable type parameters have the same syntax `T?` as the short form of nullable value types, but do not have a corresponding long form. For the purposes of the specification, the current `nullable_type` production is renamed to `nullable_value_type`, and `nullable_reference_type` and `nullable_type_parameter` productions are added: ```antlr type : value_type | reference_type | nullable_type_parameter | type_parameter | type_unsafe ; reference_type : ... | nullable_reference_type ; nullable_reference_type : non_nullable_reference_type '?' ; non_nullable_reference_type : reference_type ; nullable_type_parameter : non_nullable_non_value_type_parameter '?' ; non_nullable_non_value_type_parameter : type_parameter ; ``` The `non_nullable_reference_type` in a `nullable_reference_type` must be a nonnullable reference type (class, interface, delegate or array). The `non_nullable_non_value_type_parameter` in `nullable_type_parameter` must be a type parameter that isn't constrained to be a value type. Nullable reference types and nullable type parameters cannot occur in the following positions: - as a base class or interface - as the receiver of a `member_access` - as the `type` in an `object_creation_expression` - as the `delegate_type` in a `delegate_creation_expression` - as the `type` in an `is_expression`, a `catch_clause` or a `type_pattern` - as the `interface` in a fully qualified interface member name A warning is given on a `nullable_reference_type` and `nullable_type_parameter` in a *disabled* nullable annotation context. ### `class` and `class?` constraint The `class` constraint has a nullable counterpart `class?`: ```antlr primary_constraint : ... | 'class' '?' ; ``` A type parameter constrained with `class` (in an *enabled* annotation context) must be instantiated with a nonnullable reference type. A type parameter constrained with `class?` (or `class` in a *disabled* annotation context) may either be instantiated with a nullable or nonnullable reference type. A warning is given on a `class?` constraint in a *disabled* annotation context. The behavior of `?` annotations on type parameters that aren't constrained to be either a `struct` or `class` is covered in [Unconstrained type parameter annotations](unconstrained-type-parameter-annotations.md#-annotation). ### `notnull` constraint A type parameter constrained with `notnull` may not be a nullable type (nullable value type, nullable reference type or nullable type parameter). ```antlr primary_constraint : ... | 'notnull' ; ``` ### `default` constraint The `default` constraint can be used on a method override or explicit implementation to disambiguate `T?` meaning "nullable type parameter" from "nullable value type" (`Nullable`). Lacking the `default` constraint a `T?` syntax in an override or explicit implementation will be interpreted as `Nullable` See [Unconstrained type parameter annotations](unconstrained-type-parameter-annotations.md#default-constraint). ### The null-forgiving operator The post-fix `!` operator is called the null-forgiving operator. It can be applied on a *primary_expression* or within a *null_conditional_expression*: ```antlr primary_expression : ... | null_forgiving_expression ; null_forgiving_expression : primary_expression '!' ; null_conditional_expression : primary_expression null_conditional_operations_no_suppression suppression? ; null_conditional_operations_no_suppression : null_conditional_operations? '?' '.' identifier type_argument_list? | null_conditional_operations? '?' '[' argument_list ']' | null_conditional_operations '.' identifier type_argument_list? | null_conditional_operations '[' argument_list ']' | null_conditional_operations '(' argument_list? ')' ; null_conditional_operations : null_conditional_operations_no_suppression suppression? ; suppression : '!' ; ``` For example: ```csharp var v = expr!; expr!.M(); _ = a?.b!.c; ``` The postfix `!` operator has no runtime effect - it evaluates to the result of the underlying expression. Its only role is to change the null state of the expression to "not null", and to limit warnings given on its use. ### Nullable compiler directives `#nullable` directives control the nullable annotation and warning contexts. ```antlr pp_directive : ... | pp_nullable ; pp_nullable : whitespace? '#' whitespace? 'nullable' whitespace nullable_action (whitespace nullable_target)? pp_new_line ; nullable_action : 'disable' | 'enable' | 'restore' ; nullable_target : 'warnings' | 'annotations' ; ``` `#pragma warning` directives are expanded to allow changing the nullable warning context: ```antlr pragma_warning_body : ... | 'warning' whitespace warning_action whitespace 'nullable' ; ``` For example: ```csharp #pragma warning disable nullable ``` ## Nullable contexts Every line of source code has a *nullable annotation context* and a *nullable warning context*. These control whether nullable annotations have effect, and whether nullability warnings are given. The annotation context of a given line is either *disabled* or *enabled*. The warning context of a given line is either *disabled* or *enabled*. Both contexts can be specified at the project level (outside of C# source code), or anywhere within a source file via `#nullable` pre-processor directives. If no project level settings are provided the default is for both contexts to be *disabled*. The `#nullable` directive controls the annotation and warning contexts within the source text, and take precedence over the project-level settings. A directive sets the context(s) it controls for subsequent lines of code, until another directive overrides it, or until the end of the source file. The effect of the directives is as follows: - `#nullable disable`: Sets the nullable annotation and warning contexts to *disabled* - `#nullable enable`: Sets the nullable annotation and warning contexts to *enabled* - `#nullable restore`: Restores the nullable annotation and warning contexts to project settings - `#nullable disable annotations`: Sets the nullable annotation context to *disabled* - `#nullable enable annotations`: Sets the nullable annotation context to *enabled* - `#nullable restore annotations`: Restores the nullable annotation context to project settings - `#nullable disable warnings`: Sets the nullable warning context to *disabled* - `#nullable enable warnings`: Sets the nullable warning context to *enabled* - `#nullable restore warnings`: Restores the nullable warning context to project settings ## Nullability of types A given type can have one of three nullabilities: *oblivious*, *nonnullable*, and *nullable*. *Nonnullable* types may cause warnings if a potential `null` value is assigned to them. *Oblivious* and *nullable* types, however, are "*null-assignable*" and can have `null` values assigned to them without warnings. Values of *oblivious* and *nonnullable* types can be dereferenced or assigned without warnings. Values of *nullable* types, however, are "*null-yielding*" and may cause warnings when dereferenced or assigned without proper null checking. The *default null state* of a null-yielding type is "maybe null" or "maybe default". The default null state of a non-null-yielding type is "not null". The kind of type and the nullable annotation context it occurs in determine its nullability: - A nonnullable value type `S` is always *nonnullable* - A nullable value type `S?` is always *nullable* - An unannotated reference type `C` in a *disabled* annotation context is *oblivious* - An unannotated reference type `C` in an *enabled* annotation context is *nonnullable* - A nullable reference type `C?` is *nullable* (but a warning may be yielded in a *disabled* annotation context) Type parameters additionally take their constraints into account: - A type parameter `T` where all constraints (if any) are either nullable types or the `class?` constraint is *nullable* - A type parameter `T` where at least one constraint is either *oblivious* or *nonnullable* or one of the `struct` or `class` or `notnull` constraints is - *oblivious* in a *disabled* annotation context - *nonnullable* in an *enabled* annotation context - A nullable type parameter `T?` is *nullable*, but a warning is yielded in a *disabled* annotation context if `T` isn't a value type ### Oblivious vs nonnullable A `type` is deemed to occur in a given annotation context when the last token of the type is within that context. Whether a given reference type `C` in source code is interpreted as oblivious or nonnullable depends on the annotation context of that source code. But once established, it is considered part of that type, and "travels with it" e.g. during substitution of generic type arguments. It is as if there is an annotation like `?` on the type, but invisible. ## Constraints Nullable reference types can be used as generic constraints. `class?` is a new constraint denoting "possibly nullable reference type", whereas `class` in an *enabled* annotation context denotes "nonnullable reference type". `default` is a new constraint denoting a type parameter that isn't known to be a reference or value type. It can only be used on overridden and explicitly implemented methods. With this constraint, `T?` means a nullable type parameter, as opposed to being a shorthand for `Nullable`. `notnull` is a new constraint denoting a type parameter that is nonnullable. The nullability of a type argument or of a constraint does not impact whether the type satisfies the constraint, except where that is already the case today (nullable value types do not satisfy the `struct` constraint). However, if the type argument does not satisfy the nullability requirements of the constraint, a warning may be given. ## Null state and null tracking Every expression in a given source location has a *null state*, which indicated whether it is believed to potentially evaluate to null. The null state is either "not null", "maybe null", or "maybe default". The null state is used to determine whether a warning should be given about null-unsafe conversions and dereferences. The distinction between "maybe null" and "maybe default" is subtle and applies to type parameters. The distinction is that a type parameter `T` which has the state "maybe null" means the value is in the domain of legal values for `T` however that legal value may include `null`. Where as a "maybe default" means that the value may be outside the legal domain of values for `T`. Example: ```c# // The value `t` here has the state "maybe null". It's possible for `T` to be instantiated // with `string?` in which case `null` would be within the domain of legal values here. The // assumption though is the value provided here is within the legal values of `T`. Hence // if `T` is `string` then `null` will not be a value, just as we assume that `null` is not // provided for a normal `string` parameter void M(T t) { // There is no guarantee that default(T) is within the legal values for T hence the // state *must* be "maybe-default" and hence `local` must be `T?` T? local = default(T); } ``` ### Null tracking for variables For certain expressions denoting variables, fields or properties, the null state is tracked between occurrences, based on assignments to them, tests performed on them and the control flow between them. This is similar to how definite assignment is tracked for variables. The tracked expressions are the ones of the following form: ```antlr tracked_expression : simple_name | this | base | tracked_expression '.' identifier ; ``` Where the identifiers denote fields or properties. The null state for tracked variables is "not null" in unreachable code. This follows other decisions around unreachable code like considering all locals to be definitely assigned. ***Describe null state transitions similar to definite assignment*** ### Null state for expressions The null state of an expression is derived from its form and type, and from the null state of variables involved in it. ### Literals The null state of a `null` literal depends on the target type of the expression. If the target type is a type parameter constrained to a reference type then it's "maybe default". Otherwise it is "maybe null". The null state of a `default` literal depends on the target type of the `default` literal. A `default` literal with target type `T` has the same null state as the `default(T)` expression. The null state of any other literal is "not null". ### Simple names If a `simple_name` is not classified as a value, its null state is "not null". Otherwise it is a tracked expression, and its null state is its tracked null state at this source location. ### Member access If a `member_access` is not classified as a value, its null state is "not null". Otherwise, if it is a tracked expression, its null state is its tracked null state at this source location. Otherwise its null state is the default null state of its type. ```c# var person = new Person(); // The receiver is a tracked expression hence the member_access of the property // is tracked as well if (person.FirstName is not null) { Use(person.FirstName); } // The return of an invocation is not a tracked expression hence the member_access // of the return is also not tracked if (GetAnonymous().FirstName is not null) { // Warning: Cannot convert null literal to non-nullable reference type. Use(GetAnonymous().FirstName); } void Use(string s) { // ... } public class Person { public string? FirstName { get; set; } public string? LastName { get; set; } private static Person s_anonymous = new Person(); public static Person GetAnonymous() => s_anonymous; } ``` ### Invocation expressions If an `invocation_expression` invokes a member that is declared with one or more attributes for special null behavior, the null state is determined by those attributes. Otherwise the null state of the expression is the default null state of its type. The null state of an `invocation_expression` is not tracked by the compiler. ```c# // The result of an invocation_expression is not tracked if (GetText() is not null) { // Warning: Converting null literal or possible null value to non-nullable type. string s = GetText(); // Warning: Dereference of a possibly null reference. Use(s); } // Nullable friendly pattern if (GetText() is string s) { Use(s); } string? GetText() => ... Use(string s) { } ``` ### Element access If an `element_access` invokes an indexer that is declared with one or more attributes for special null behavior, the null state is determined by those attributes. Otherwise the null state of the expression is the default null state of its type. ```c# object?[] array = ...; if (array[0] != null) { // Warning: Converting null literal or possible null value to non-nullable type. object o = array[0]; // Warning: Dereference of a possibly null reference. Console.WriteLine(o.ToString()); } // Nullable friendly pattern if (array[0] is {} o) { Console.WriteLine(o.ToString()); } ``` ### Base access If `B` denotes the base type of the enclosing type, `base.I` has the same null state as `((B)this).I` and `base[E]` has the same null state as `((B)this)[E]`. ### Default expressions `default(T)` has the null state based on the properties of the type `T`: - If the type is a *nonnullable* type then it has the null state "not null" - Else if the type is a type parameter then it has the null state "maybe default" - Else it has the null state "maybe null" ### Null-conditional expressions ?. A `null_conditional_expression` has the null state based on the expression type. Note that this refers to the type of the `null_conditional_expression`, not the original type of the member being invoked: - If the type is a *nullable* value type then it has the null state "maybe null" - Else if the type is a *nullable* type parameter then it has the null state "maybe default" - Else it has the null state "maybe null" ### Cast expressions If a cast expression `(T)E` invokes a user-defined conversion, then the null state of the expression is the default null state for the type of the user-defined conversion. Otherwise: - If `T` is a *nonnullable* value type then `T` has the null state "not null" - Else if `T` is a *nullable* value type then `T` has the null state "maybe null" - Else if `T` is a *nullable* type in the form `U?` where `U` is a type parameter then `T` has the null state "maybe default" - Else if `T` is a *nullable* type, and `E` has null state "maybe null" or "maybe default", then `T` has the null state "maybe null" - Else if `T` is a type parameter, and `E` has null state "maybe null" or "maybe default", then `T` has the null state "maybe default" - Else `T` has the same null state as `E` ### Unary and binary operators If a unary or binary operator invokes an user-defined operator then the null state of the expression is the default null state for the type of the user-defined operator. Otherwise it is the null state of the expression. ***Something special to do for binary `+` over strings and delegates?*** ### Await expressions The null state of `await E` is the default null state of its type. ### The `as` operator The null state of an `E as T` expression depends first on properties of the type `T`. If the type of `T` is *nonnullable* then the null state is "not null". Otherwise the null state depends on the conversion from the type of `E` to type `T`: - If the conversion is an identity, boxing, implicit reference, or implicit nullable conversion, then the null state is the null state of `E` - Else if `T` is a type parameter then it has the null state "maybe default" - Else it has the null state "maybe null" ### The null-coalescing operator The null state of `E1 ?? E2` is the null state of `E2` ### The conditional operator The null state of `E1 ? E2 : E3` is based on the null state of `E2` and `E3`: - If both are "not null" then the null state is "not null" - Else if either is "maybe default" then the null state is "maybe default" - Else the null state is "not null" ### Query expressions The null state of a query expression is the default null state of its type. *Additional work needed here* ### Assignment operators `E1 = E2` and `E1 op= E2` have the same null state as `E2` after any implicit conversions have been applied. ### Expressions that propagate null state `(E)`, `checked(E)` and `unchecked(E)` all have the same null state as `E`. ### Expressions that are never null The null state of the following expression forms is always "not null": - `this` access - interpolated strings - `new` expressions (object, delegate, anonymous object and array creation expressions) - `typeof` expressions - `nameof` expressions - anonymous functions (anonymous methods and lambda expressions) - null-forgiving expressions - `is` expressions ### Nested functions Nested functions (lambdas and local functions) are treated like methods, except in regards to their captured variables. The initial state of a captured variable inside a lambda or local function is the intersection of the nullable state of the variable at all the "uses" of that nested function or lambda. A use of a local function is either a call to that function, or where it is converted to a delegate. A use of a lambda is the point at which it is defined in source. ## Type inference ### nullable implicitly typed local variables `var` infers an annotated type for reference types, and type parameters that aren't constrained to be a value type. For instance: - in `var s = "";` the `var` is inferred as `string?`. - in `var t = new T();` with an unconstrained `T` the `var` is inferred as `T?`. ### Generic type inference Generic type inference is enhanced to help decide whether inferred reference types should be nullable or not. This is a best effort. It may yield warnings regarding nullability constraints, and may lead to nullable warnings when the inferred types of the selected overload are applied to the arguments. ### The first phase Nullable reference types flow into the bounds from the initial expressions, as described below. In addition, two new kinds of bounds, namely `null` and `default` are introduced. Their purpose is to carry through occurrences of `null` or `default` in the input expressions, which may cause an inferred type to be nullable, even when it otherwise wouldn't. The determination of what bounds to add in the first phase are enhanced as follows: If an argument `Ei` has a reference type, the type `U` used for inference depends on the null state of `Ei` as well as its declared type: - If the declared type is a nonnullable reference type `U0` or a nullable reference type `U0?` then - if the null state of `Ei` is "not null" then `U` is `U0` - if the null state of `Ei` is "maybe null" then `U` is `U0?` - Otherwise if `Ei` has a declared type, `U` is that type - Otherwise if `Ei` is `null` then `U` is the special bound `null` - Otherwise if `Ei` is `default` then `U` is the special bound `default` - Otherwise no inference is made. ### Exact, upper-bound and lower-bound inferences In inferences *from* the type `U` *to* the type `V`, if `V` is a nullable reference type `V0?`, then `V0` is used instead of `V` in the following clauses. - If `V` is one of the unfixed type variables, `U` is added as an exact, upper or lower bound as before - Otherwise, if `U` is `null` or `default`, no inference is made - Otherwise, if `U` is a nullable reference type `U0?`, then `U0` is used instead of `U` in the subsequent clauses. The essence is that nullability that pertains directly to one of the unfixed type variables is preserved into its bounds. For the inferences that recurse further into the source and target types, on the other hand, nullability is ignored. It may or may not match, but if it doesn't, a warning will be issued later if the overload is chosen and applied. ### Fixing The spec currently does not do a good job of describing what happens when multiple bounds are identity convertible to each other, but are different. This may happen between `object` and `dynamic`, between tuple types that differ only in element names, between types constructed thereof and now also between `C` and `C?` for reference types. In addition we need to propagate "nullness" from the input expressions to the result type. To handle these we add more phases to fixing, which is now: 1. Gather all the types in all the bounds as candidates, removing `?` from all that are nullable reference types 2. Eliminate candidates based on requirements of exact, lower and upper bounds (keeping `null` and `default` bounds) 3. Eliminate candidates that do not have an implicit conversion to all the other candidates 4. If the remaining candidates do not all have identity conversions to one another, then type inference fails 5. *Merge* the remaining candidates as described below 6. If the resulting candidate is a reference type and *all* of the exact bounds or *any* of the lower bounds are nullable reference types, `null` or `default`, then `?` is added to the resulting candidate, making it a nullable reference type. *Merging* is described between two candidate types. It is transitive and commutative, so the candidates can be merged in any order with the same ultimate result. It is undefined if the two candidate types are not identity convertible to each other. The *Merge* function takes two candidate types and a direction (*+* or *-*): - *Merge*(`T`, `T`, *d*) = T - *Merge*(`S`, `T?`, *+*) = *Merge*(`S?`, `T`, *+*) = *Merge*(`S`, `T`, *+*)`?` - *Merge*(`S`, `T?`, *-*) = *Merge*(`S?`, `T`, *-*) = *Merge*(`S`, `T`, *-*) - *Merge*(`C`, `C`, *+*) = `C<`*Merge*(`S1`, `T1`, *d1*)`,...,`*Merge*(`Sn`, `Tn`, *dn*)`>`, *where* - `di` = *+* if the `i`'th type parameter of `C<...>` is covariant - `di` = *-* if the `i`'th type parameter of `C<...>` is contra- or invariant - *Merge*(`C`, `C`, *-*) = `C<`*Merge*(`S1`, `T1`, *d1*)`,...,`*Merge*(`Sn`, `Tn`, *dn*)`>`, *where* - `di` = *-* if the `i`'th type parameter of `C<...>` is covariant - `di` = *+* if the `i`'th type parameter of `C<...>` is contra- or invariant - *Merge*(`(S1 s1,..., Sn sn)`, `(T1 t1,..., Tn tn)`, *d*) = `(`*Merge*(`S1`, `T1`, *d*)`n1,...,`*Merge*(`Sn`, `Tn`, *d*) `nn)`, *where* - `ni` is absent if `si` and `ti` differ, or if both are absent - `ni` is `si` if `si` and `ti` are the same - *Merge*(`object`, `dynamic`) = *Merge*(`dynamic`, `object`) = `dynamic` ## Warnings ### Potential null assignment ### Potential null dereference ### Constraint nullability mismatch ### Nullable types in disabled annotation context ### Override and implementation nullability mismatch ## Attributes for special null behavior ================================================ FILE: proposals/csharp-9.0/patterns3.md ================================================ # Pattern-matching changes for C# 9.0 [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] We are considering a small handful of enhancements to pattern-matching for C# 9.0 that have natural synergy and work well to address a number of common programming problems: - https://github.com/dotnet/csharplang/issues/2925 Type patterns - https://github.com/dotnet/csharplang/issues/1350 Parenthesized patterns to enforce or emphasize precedence of the new combinators - https://github.com/dotnet/csharplang/issues/1350 Conjunctive `and` patterns that require both of two different patterns to match; - https://github.com/dotnet/csharplang/issues/1350 Disjunctive `or` patterns that require either of two different patterns to match; - https://github.com/dotnet/csharplang/issues/1350 Negated `not` patterns that require a given pattern *not* to match; and - https://github.com/dotnet/csharplang/issues/812 Relational patterns that require the input value to be less than, less than or equal to, etc a given constant. ## Parenthesized Patterns Parenthesized patterns permit the programmer to put parentheses around any pattern. This is not so useful with the existing patterns in C# 8.0, however the new pattern combinators introduce a precedence that the programmer may want to override. ```antlr primary_pattern : parenthesized_pattern | // all of the existing forms ; parenthesized_pattern : '(' pattern ')' ; ``` ## Type Patterns We permit a type as a pattern: ``` antlr primary_pattern : type-pattern | // all of the existing forms ; type_pattern : type ; ``` This retcons the existing *is-type-expression* to be an *is-pattern-expression* in which the pattern is a *type-pattern*, though we would not change the syntax tree produced by the compiler. One subtle implementation issue is that this grammar is ambiguous. A string such as `a.b` can be parsed either as a qualified name (in a type context) or a dotted expression (in an expression context). The compiler is already capable of treating a qualified name the same as a dotted expression in order to handle something like `e is Color.Red`. The compiler's semantic analysis would be further extended to be capable of binding a (syntactic) constant pattern (e.g. a dotted expression) as a type in order to treat it as a bound type pattern in order to support this construct. After this change, you would be able to write ```csharp void M(object o1, object o2) { var t = (o1, o2); if (t is (int, string)) {} // test if o1 is an int and o2 is a string switch (o1) { case int: break; // test if o1 is an int case System.String: break; // test if o1 is a string } } ``` ## Relational Patterns Relational patterns permit the programmer to express that an input value must satisfy a relational constraint when compared to a constant value: ``` C# public static LifeStage LifeStageAtAge(int age) => age switch { < 0 => LifeStage.Prenatal, < 2 => LifeStage.Infant, < 4 => LifeStage.Toddler, < 6 => LifeStage.EarlyChild, < 12 => LifeStage.MiddleChild, < 20 => LifeStage.Adolescent, < 40 => LifeStage.EarlyAdult, < 65 => LifeStage.MiddleAdult, _ => LifeStage.LateAdult, }; ``` Relational patterns support the relational operators `<`, `<=`, `>`, and `>=` on all of the built-in types that support such binary relational operators with two operands of the same type in an expression. Specifically, we support all of these relational patterns for `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, `nint`, and `nuint`. ```antlr primary_pattern : relational_pattern ; relational_pattern : '<' relational_expression | '<=' relational_expression | '>' relational_expression | '>=' relational_expression ; ``` The expression is required to evaluate to a constant value. It is an error if that constant value is `double.NaN` or `float.NaN`. It is an error if the expression is a null constant. When the input is a type for which a suitable built-in binary relational operator is defined that is applicable with the input as its left operand and the given constant as its right operand, the evaluation of that operator is taken as the meaning of the relational pattern. Otherwise we convert the input to the type of the expression using an explicit nullable or unboxing conversion. It is a compile-time error if no such conversion exists. The pattern is considered not to match if the conversion fails. If the conversion succeeds then the result of the pattern-matching operation is the result of evaluating the expression `e OP v` where `e` is the converted input, `OP` is the relational operator, and `v` is the constant expression. ## Pattern Combinators Pattern *combinators* permit matching both of two different patterns using `and` (this can be extended to any number of patterns by the repeated use of `and`), either of two different patterns using `or` (ditto), or the *negation* of a pattern using `not`. A common use of a combinator will be the idiom ``` c# if (e is not null) ... ``` More readable than the current idiom `e is object`, this pattern clearly expresses that one is checking for a non-null value. The `and` and `or` combinators will be useful for testing ranges of values ``` c# bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z'; ``` This example illustrates that `and` will have a higher parsing priority (i.e. will bind more closely) than `or`. The programmer can use the *parenthesized pattern* to make the precedence explicit: ``` c# bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z'); ``` Like all patterns, these combinators can be used in any context in which a pattern is expected, including nested patterns, the *is-pattern-expression*, the *switch-expression*, and the pattern of a switch statement's case label. ```antlr pattern : disjunctive_pattern ; disjunctive_pattern : disjunctive_pattern 'or' conjunctive_pattern | conjunctive_pattern ; conjunctive_pattern : conjunctive_pattern 'and' negated_pattern | negated_pattern ; negated_pattern : 'not' negated_pattern | primary_pattern ; primary_pattern : // all of the patterns forms previously defined ; ``` ## Change to 6.2.5 Grammar Ambiguities Due to the introduction of the *type pattern*, it is possible for a generic type to appear before the token `=>`. We therefore add `=>` to the set of tokens listed in [§6.2.5 Grammar ambiguities](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/lexical-structure.md#625-grammar-ambiguities) to permit disambiguation of the `<` that begins the type argument list. See also https://github.com/dotnet/roslyn/issues/47614. ## Open Issues with Proposed Changes ### Syntax for relational operators Are `and`, `or`, and `not` some kind of contextual keyword? If so, is there a breaking change (e.g. compared to their use as a designator in a *declaration-pattern*). ### Semantics (e.g. type) for relational operators We expect to support all of the primitive types that can be compared in an expression using a relational operator. The meaning in simple cases is clear ``` c# bool IsValidPercentage(int x) => x is >= 0 and <= 100; ``` But when the input is not such a primitive type, what type do we attempt to convert it to? ``` c# bool IsValidPercentage(object x) => x is >= 0 and <= 100; ``` We have proposed that when the input type is already a comparable primitive, that is the type of the comparison. However, when the input is not a comparable primitive, we treat the relational as including an implicit type test to the type of the constant on the right-hand-side of the relational. If the programmer intends to support more than one input type, that must be done explicitly: ``` c# bool IsValidPercentage(object x) => x is >= 0 and <= 100 or // integer tests >= 0F and <= 100F or // float tests >= 0D and <= 100D; // double tests ``` Result: The relational does include an implicit type test to the type of the constant on the right-hand-side of the relational. ### Flowing type information from the left to the right of `and` It has been suggested that when you write an `and` combinator, type information learned on the left about the top-level type could flow to the right. For example ```csharp bool isSmallByte(object o) => o is byte and < 100; ``` Here, the *input type* to the second pattern is narrowed by the *type narrowing* requirements of left of the `and`. We would define type narrowing semantics for all patterns as follows. The *narrowed type* of a pattern `P` is defined as follows: 1. If `P` is a type pattern, the *narrowed type* is the type of the type pattern's type. 2. If `P` is a declaration pattern, the *narrowed type* is the type of the declaration pattern's type. 3. If `P` is a recursive pattern that gives an explicit type, the *narrowed type* is that type. 4. If `P` is [matched via the rules for](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/patterns.md#positional-pattern) `ITuple`, the *narrowed type* is the type `System.Runtime.CompilerServices.ITuple`. 5. If `P` is a constant pattern where the constant is not the null constant and where the expression has no *constant expression conversion* to the *input type*, the *narrowed type* is the type of the constant. 6. If `P` is a relational pattern where the constant expression has no *constant expression conversion* to the *input type*, the *narrowed type* is the type of the constant. 7. If `P` is an `or` pattern, the *narrowed type* is the common type of the *narrowed type* of the subpatterns if such a common type exists. For this purpose, the common type algorithm considers only identity, boxing, and implicit reference conversions, and it considers all subpatterns of a sequence of `or` patterns (ignoring parenthesized patterns). 8. If `P` is an `and` pattern, the *narrowed type* is the *narrowed type* of the right pattern. Moreover, the *narrowed type* of the left pattern is the *input type* of the right pattern. 9. Otherwise the *narrowed type* of `P` is `P`'s input type. Result: The above narrowing semantics have been implemented. ### Variable definitions and definite assignment The addition of `or` and `not` patterns creates some interesting new problems around pattern variables and definite assignment. Since variables can normally be declared at most once, it would seem any pattern variable declared on one side of an `or` pattern would not be definitely assigned when the pattern matches. Similarly, a variable declared inside a `not` pattern would not be expected to be definitely assigned when the pattern matches. The simplest way to address this is to forbid declaring pattern variables in these contexts. However, this may be too restrictive. There are other approaches to consider. One scenario that is worth considering is this ``` csharp if (e is not int i) return; M(i); // is i definitely assigned here? ``` This does not work today because, for an *is-pattern-expression*, the pattern variables are considered *definitely assigned* only where the *is-pattern-expression* is true ("definitely assigned when true"). Supporting this would be simpler (from the programmer's perspective) than also adding support for a negated-condition `if` statement. Even if we add such support, programmers would wonder why the above snippet does not work. On the other hand, the same scenario in a `switch` makes less sense, as there is no corresponding point in the program where *definitely assigned when false* would be meaningful. Would we permit this in an *is-pattern-expression* but not in other contexts where patterns are permitted? That seems irregular. Related to this is the problem of definite assignment in a *disjunctive-pattern*. ```csharp if (e is 0 or int i) { M(i); // is i definitely assigned here? } ``` We would only expect `i` to be definitely assigned when the input is not zero. But since we don't know whether the input is zero or not inside the block, `i` is not definitely assigned. However, what if we permit `i` to be declared in different mutually exclusive patterns? ```csharp if ((e1, e2) is (0, int i) or (int i, 0)) { M(i); } ``` Here, the variable `i` is definitely assigned inside the block, and takes it value from the other element of the tuple when a zero element is found. It has also been suggested to permit variables to be (multiply) defined in every case of a case block: ```csharp case (0, int x): case (int x, 0): Console.WriteLine(x); ``` To make any of this work, we would have to carefully define where such multiple definitions are permitted and under what conditions such a variable is considered definitely assigned. Should we elect to defer such work until later (which I advise), we could say in C# 9 - beneath a `not` or `or`, pattern variables may not be declared. Then, we would have time to develop some experience that would provide insight into the possible value of relaxing that later. Result: Pattern variables can't be declared beneath a `not` or `or` pattern. ### Diagnostics, subsumption, and exhaustiveness These new pattern forms introduce many new opportunities for diagnosable programmer error. We will need to decide what kinds of errors we will diagnose, and how to do so. Here are some examples: ``` csharp case >= 0 and <= 100D: ``` This case can never match (because the input cannot be both an `int` and a `double`). We already have an error when we detect a case that can never match, but its wording ("The switch case has already been handled by a previous case" and "The pattern has already been handled by a previous arm of the switch expression") may be misleading in new scenarios. We may have to modify the wording to just say that the pattern will never match the input. ``` csharp case 1 and 2: ``` Similarly, this would be an error because a value cannot be both `1` and `2`. ``` csharp case 1 or 2 or 3 or 1: ``` This case is possible to match, but the `or 1` at the end adds no meaning to the pattern. I suggest we should aim to produce an error whenever some conjunct or disjunct of a compound pattern does not either define a pattern variable or affect the set of matched values. ``` csharp case < 2: break; case 0 or 1 or 2 or 3 or 4 or 5: break; ``` Here, `0 or 1 or` adds nothing to the second case, as those values would have been handled by the first case. This too deserves an error. ``` csharp byte b = ...; int x = b switch { <100 => 0, 100 => 1, 101 => 2, >101 => 3 }; ``` A switch expression such as this should be considered *exhaustive* (it handles all possible input values). In C# 8.0, a switch expression with an input of type `byte` is only considered exhaustive if it contains a final arm whose pattern matches everything (a *discard-pattern* or *var-pattern*). Even a switch expression that has an arm for every distinct `byte` value is not considered exhaustive in C# 8. In order to properly handle exhaustiveness of relational patterns, we will have to handle this case too. This will technically be a breaking change, but no user is likely to notice. ================================================ FILE: proposals/csharp-9.0/records.md ================================================ # Records [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: This proposal tracks the specification for the C# 9 records feature, as agreed to by the C# language design team. The syntax for a record is as follows: ```antlr record_declaration : attributes? class_modifier* 'partial'? 'record' identifier type_parameter_list? parameter_list? record_base? type_parameter_constraints_clause* record_body ; record_base : ':' class_type argument_list? | ':' interface_type_list | ':' class_type argument_list? ',' interface_type_list ; record_body : '{' class_member_declaration* '}' ';'? | ';' ; ``` Record types are reference types, similar to a class declaration. It is an error for a record to provide a `record_base` `argument_list` if the `record_declaration` does not contain a `parameter_list`. At most one partial type declaration of a partial record may provide a `parameter_list`. Record parameters cannot use `ref`, `out` or `this` modifiers (but `in` and `params` are allowed). ## Inheritance Records cannot inherit from classes, unless the class is `object`, and classes cannot inherit from records. Records can inherit from other records. ## Members of a record type In addition to the members declared in the record body, a record type has additional synthesized members. Members are synthesized unless a member with a "matching" signature is declared in the record body or an accessible concrete non-virtual member with a "matching" signature is inherited. A matching member prevents the compiler from generating that member, not any other synthesized members. Two members are considered matching if they have the same signature or would be considered "hiding" in an inheritance scenario. It is an error for a member of a record to be named "Clone". It is an error for an instance field of a record to have a top-level pointer type. A nested pointer type, such as an array of pointers, is allowed. The synthesized members are as follows: ### Equality members If the record is derived from `object`, the record type includes a synthesized readonly property equivalent to a property declared as follows: ```C# Type EqualityContract { get; } ``` The property is `private` if the record type is `sealed`. Otherwise, the property is `virtual` and `protected`. The property can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or if the explicit declaration doesn't allow overriding it in a derived type and the record type is not `sealed`. If the record type is derived from a base record type `Base`, the record type includes a synthesized readonly property equivalent to a property declared as follows: ```C# protected override Type EqualityContract { get; } ``` The property can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or if the explicit declaration doesn't allow overriding it in a derived type and the record type is not `sealed`. It is an error if either synthesized, or explicitly declared property doesn't override a property with this signature in the record type `Base` (for example, if the property is missing in the `Base`, or sealed, or not virtual, etc.). The synthesized property returns `typeof(R)` where `R` is the record type. The record type implements `System.IEquatable` and includes a synthesized strongly-typed overload of `Equals(R? other)` where `R` is the record type. The method is `public`, and the method is `virtual` unless the record type is `sealed`. The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or the explicit declaration doesn't allow overriding it in a derived type and the record type is not `sealed`. If `Equals(R? other)` is user-defined (not synthesized) but `GetHashCode` is not, a warning is produced. ```C# public virtual bool Equals(R? other); ``` The synthesized `Equals(R?)` returns `true` if and only if each of the following are `true`: - `other` is not `null`, and - For each instance field `fieldN` in the record type that is not inherited, the value of `System.Collections.Generic.EqualityComparer.Default.Equals(fieldN, other.fieldN)` where `TN` is the field type, and - If there is a base record type, the value of `base.Equals(other)` (a non-virtual call to `public virtual bool Equals(Base? other)`); otherwise the value of `EqualityContract == other.EqualityContract`. The record type includes synthesized `==` and `!=` operators equivalent to operators declared as follows: ```C# public static bool operator==(R? left, R? right) => (object)left == right || (left?.Equals(right) ?? false); public static bool operator!=(R? left, R? right) => !(left == right); ``` The `Equals` method called by the `==` operator is the `Equals(R? other)` method specified above. The `!=` operator delegates to the `==` operator. It is an error if the operators are declared explicitly. If the record type is derived from a base record type `Base`, the record type includes a synthesized override equivalent to a method declared as follows: ```C# public sealed override bool Equals(Base? other); ``` It is an error if the override is declared explicitly. It is an error if the method doesn't override a method with same signature in record type `Base` (for example, if the method is missing in the `Base`, or sealed, or not virtual, etc.). The synthesized override returns `Equals((object?)other)`. The record type includes a synthesized override equivalent to a method declared as follows: ```C# public override bool Equals(object? obj); ``` It is an error if the override is declared explicitly. It is an error if the method doesn't override `object.Equals(object? obj)` (for example, due to shadowing in intermediate base types, etc.). The synthesized override returns `Equals(other as R)` where `R` is the record type. The record type includes a synthesized override equivalent to a method declared as follows: ```C# public override int GetHashCode(); ``` The method can be declared explicitly. It is an error if the explicit declaration doesn't allow overriding it in a derived type and the record type is not `sealed`. It is an error if either synthesized, or explicitly declared method doesn't override `object.GetHashCode()` (for example, due to shadowing in intermediate base types, etc.). A warning is reported if one of `Equals(R?)` and `GetHashCode()` is explicitly declared but the other method is not explicit. The synthesized override of `GetHashCode()` returns an `int` result of combining the following values: - For each instance field `fieldN` in the record type that is not inherited, the value of `System.Collections.Generic.EqualityComparer.Default.GetHashCode(fieldN)` where `TN` is the field type, and - If there is a base record type, the value of `base.GetHashCode()`; otherwise the value of `System.Collections.Generic.EqualityComparer.Default.GetHashCode(EqualityContract)`. For example, consider the following record types: ```C# record R1(T1 P1); record R2(T1 P1, T2 P2) : R1(P1); record R3(T1 P1, T2 P2, T3 P3) : R2(P1, P2); ``` For those record types, the synthesized equality members would be something like: ```C# class R1 : IEquatable { public T1 P1 { get; init; } protected virtual Type EqualityContract => typeof(R1); public override bool Equals(object? obj) => Equals(obj as R1); public virtual bool Equals(R1? other) { return !(other is null) && EqualityContract == other.EqualityContract && EqualityComparer.Default.Equals(P1, other.P1); } public static bool operator==(R1? left, R1? right) => (object)left == right || (left?.Equals(right) ?? false); public static bool operator!=(R1? left, R1? right) => !(left == right); public override int GetHashCode() { return HashCode.Combine(EqualityComparer.Default.GetHashCode(EqualityContract), EqualityComparer.Default.GetHashCode(P1)); } } class R2 : R1, IEquatable { public T2 P2 { get; init; } protected override Type EqualityContract => typeof(R2); public override bool Equals(object? obj) => Equals(obj as R2); public sealed override bool Equals(R1? other) => Equals((object?)other); public virtual bool Equals(R2? other) { return base.Equals((R1?)other) && EqualityComparer.Default.Equals(P2, other.P2); } public static bool operator==(R2? left, R2? right) => (object)left == right || (left?.Equals(right) ?? false); public static bool operator!=(R2? left, R2? right) => !(left == right); public override int GetHashCode() { return HashCode.Combine(base.GetHashCode(), EqualityComparer.Default.GetHashCode(P2)); } } class R3 : R2, IEquatable { public T3 P3 { get; init; } protected override Type EqualityContract => typeof(R3); public override bool Equals(object? obj) => Equals(obj as R3); public sealed override bool Equals(R2? other) => Equals((object?)other); public virtual bool Equals(R3? other) { return base.Equals((R2?)other) && EqualityComparer.Default.Equals(P3, other.P3); } public static bool operator==(R3? left, R3? right) => (object)left == right || (left?.Equals(right) ?? false); public static bool operator!=(R3? left, R3? right) => !(left == right); public override int GetHashCode() { return HashCode.Combine(base.GetHashCode(), EqualityComparer.Default.GetHashCode(P3)); } } ``` ### Copy and Clone members A record type contains two copying members: * A constructor taking a single argument of the record type. It is referred to as a "copy constructor". * A synthesized public parameterless instance "clone" method with a compiler-reserved name The purpose of the copy constructor is to copy the state from the parameter to the new instance being created. This constructor doesn't run any instance field/property initializers present in the record declaration. If the constructor is not explicitly declared, a constructor will be synthesized by the compiler. If the record is sealed, the constructor will be private, otherwise it will be protected. An explicitly declared copy constructor must be either public or protected, unless the record is sealed. The first thing the constructor must do, is to call a copy constructor of the base, or a parameter-less object constructor if the record inherits from object. An error is reported if a user-defined copy constructor uses an implicit or explicit constructor initializer that doesn't fulfill this requirement. After a base copy constructor is invoked, a synthesized copy constructor copies values for all instance fields implicitly or explicitly declared within the record type. The sole presence of a copy constructor, whether explicit or implicit, doesn't prevent an automatic addition of a default instance constructor. If a virtual "clone" method is present in the base record, the synthesized "clone" method overrides it and the return type of the method is the current containing type. An error is produced if the base record clone method is sealed. If a virtual "clone" method is not present in the base record, the return type of the clone method is the containing type and the method is virtual, unless the record is sealed or abstract. If the containing record is abstract, the synthesized clone method is also abstract. If the "clone" method is not abstract, it returns the result of a call to a copy constructor. ### Printing members: PrintMembers and ToString methods If the record is derived from `object`, the record includes a synthesized method equivalent to a method declared as follows: ```C# bool PrintMembers(System.Text.StringBuilder builder); ``` The method is `private` if the record type is `sealed`. Otherwise, the method is `virtual` and `protected`. The method: 1. calls the method `System.Runtime.CompilerServices.RuntimeHelpers.EnsureSufficientExecutionStack()` if the method is present and the record has printable members. 2. for each of the record's printable members (non-static public field and readable property members), appends that member's name followed by " = " followed by the member's value separated with ", ", 3. return true if the record has printable members. For a member that has a value type, we will convert its value to a string representation using the most efficient method available to the target platform. At present that means calling `ToString` before passing to `StringBuilder.Append`. If the record type is derived from a base record `Base`, the record includes a synthesized override equivalent to a method declared as follows: ```C# protected override bool PrintMembers(StringBuilder builder); ``` If the record has no printable members, the method calls the base `PrintMembers` method with one argument (its `builder` parameter) and returns the result. Otherwise, the method: 1. calls the base `PrintMembers` method with one argument (its `builder` parameter), 2. if the `PrintMembers` method returned true, append ", " to the builder, 3. for each of the record's printable members, appends that member's name followed by " = " followed by the member's value: `this.member` (or `this.member.ToString()` for value types), separated with ", ", 4. return true. The `PrintMembers` method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or if the explicit declaration doesn't allow overriding it in a derived type and the record type is not `sealed`. The record includes a synthesized method equivalent to a method declared as follows: ```C# public override string ToString(); ``` The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or if the explicit declaration doesn't allow overriding it in a derived type and the record type is not `sealed`. It is an error if either synthesized, or explicitly declared method doesn't override `object.ToString()` (for example, due to shadowing in intermediate base types, etc.). The synthesized method: 1. creates a `StringBuilder` instance, 2. appends the record name to the builder, followed by " { ", 3. invokes the record's `PrintMembers` method giving it the builder, followed by " " if it returned true, 4. appends "}", 3. returns the builder's contents with `builder.ToString()`. For example, consider the following record types: ``` csharp record R1(T1 P1); record R2(T1 P1, T2 P2, T3 P3) : R1(P1); ``` For those record types, the synthesized printing members would be something like: ```C# class R1 : IEquatable { public T1 P1 { get; init; } protected virtual bool PrintMembers(StringBuilder builder) { builder.Append(nameof(P1)); builder.Append(" = "); builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if T1 is a value type return true; } public override string ToString() { var builder = new StringBuilder(); builder.Append(nameof(R1)); builder.Append(" { "); if (PrintMembers(builder)) builder.Append(" "); builder.Append("}"); return builder.ToString(); } } class R2 : R1, IEquatable { public T2 P2 { get; init; } public T3 P3 { get; init; } protected override bool PrintMembers(StringBuilder builder) { if (base.PrintMembers(builder)) builder.Append(", "); builder.Append(nameof(P2)); builder.Append(" = "); builder.Append(this.P2); // or builder.Append(this.P2); if T2 is a value type builder.Append(", "); builder.Append(nameof(P3)); builder.Append(" = "); builder.Append(this.P3); // or builder.Append(this.P3); if T3 is a value type return true; } public override string ToString() { var builder = new StringBuilder(); builder.Append(nameof(R2)); builder.Append(" { "); if (PrintMembers(builder)) builder.Append(" "); builder.Append("}"); return builder.ToString(); } } ``` ## Positional record members In addition to the above members, records with a parameter list ("positional records") synthesize additional members with the same conditions as the members above. ### Primary Constructor A record type has a public constructor whose signature corresponds to the value parameters of the type declaration. This is called the primary constructor for the type, and causes the implicitly declared default class constructor, if present, to be suppressed. It is an error to have a primary constructor and a constructor with the same signature already present in the class. At runtime the primary constructor 1. executes the instance initializers appearing in the class-body 1. invokes the base class constructor with the arguments provided in the `record_base` clause, if present If a record has a primary constructor, any user-defined constructor, except "copy constructor" must have an explicit `this` constructor initializer. Parameters of the primary constructor as well as members of the record are in scope within the `argument_list` of the `record_base` clause and within initializers of instance fields or properties. Instance members would be an error in these locations (similar to how instance members are in scope in regular constructor initializers today, but an error to use), but the parameters of the primary constructor would be in scope and useable and would shadow members. Static members would also be useable, similar to how base calls and initializers work in ordinary constructors today. A warning is produced if a parameter of the primary constructor is not read. Expression variables declared in the `argument_list` are in scope within the `argument_list`. The same shadowing rules as within an argument list of a regular constructor initializer apply. ### Properties For each record parameter of a record type declaration there is a corresponding public property member whose name and type are taken from the value parameter declaration. For a record: * A public `get` and `init` auto-property is created (see separate `init` accessor specification). An inherited `abstract` property with matching type is overridden. It is an error if the inherited property does not have `public` overridable `get` and `init` accessors. It is an error if the inherited property is hidden. The auto-property is initialized to the value of the corresponding primary constructor parameter. Attributes can be applied to the synthesized auto-property and its backing field by using `property:` or `field:` targets for attributes syntactically applied to the corresponding record parameter. ### Deconstruct A positional record with at least one parameter synthesizes a public void-returning instance method called Deconstruct with an out parameter declaration for each parameter of the primary constructor declaration. Each parameter of the `Deconstruct` method has the same type as the corresponding parameter of the primary constructor declaration. The body of the method assigns to each parameter of the `Deconstruct` method, the value of the instance property of the same name. The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or is static. The following example shows a positional record `R` with its compiler synthesized `Deconstruct` method, along with its usage: ```csharp public record R(int P1, string P2 = "xyz") { public void Deconstruct(out int P1, out string P2) { P1 = this.P1; P2 = this.P2; } } class Program { static void Main() { R r = new R(12); (int p1, string p2) = r; Console.WriteLine($"p1: {p1}, p2: {p2}"); } } ``` ## `with` expression A `with` expression is a new expression using the following syntax. ```antlr with_expression : switch_expression | switch_expression 'with' '{' member_initializer_list? '}' ; member_initializer_list : member_initializer (',' member_initializer)* ; member_initializer : identifier '=' expression ; ``` A `with` expression is not permitted as a statement. A `with` expression allows for "non-destructive mutation", designed to produce a copy of the receiver expression with modifications in assignments in the `member_initializer_list`. A valid `with` expression has a receiver with a non-void type. The receiver type must be a record. On the right hand side of the `with` expression is a `member_initializer_list` with a sequence of assignments to *identifier*, which must be an accessible instance field or property of the receiver's type. First, receiver's "clone" method (specified above) is invoked and its result is converted to the receiver's type. Then, each `member_initializer` is processed the same way as an assignment to a field or property access of the result of the conversion. Assignments are processed in lexical order. ================================================ FILE: proposals/csharp-9.0/skip-localsinit.md ================================================ # Suppress emitting of `localsinit` flag. [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Allow suppressing emit of `localsinit` flag via `SkipLocalsInitAttribute` attribute. ## Motivation [motivation]: #motivation ### Background Per CLR spec local variables that do not contain references are not initialized to a particular value by the VM/JIT. Reading from such variables without initialization is type-safe, but otherwise the behavior is undefined and implementation specific. Typically uninitialized locals contain whatever values were left in the memory that is now occupied by the stack frame. That could lead to nondeterministic behavior and hard to reproduce bugs. There are two ways to "assign" a local variable: - by storing a value or - by specifying `localsinit` flag which forces everything that is allocated from the local memory pool to be zero-initialized NOTE: this includes both local variables and `stackalloc` data. Use of uninitialized data is discouraged and is not allowed in verifiable code. While it might be possible to prove that by the means of flow analysis, it is permitted for the verification algorithm to be conservative and simply require that `localsinit` is set. Historically C# compiler emits `localsinit` flag on all methods that declare locals. While C# employs definite-assignment analysis which is more strict than what CLR spec would require (C# also needs to consider scoping of locals), it is not strictly guaranteed that the resulting code would be formally verifiable: - CLR and C# rules may not agree on whether passing a local as `out` argument is a `use`. - CLR and C# rules may not agree on treatment of conditional branches when conditions are known (constant propagation). - CLR could as well simply require `localinits`, since that is permitted. ### Problem In high-performance application the cost of forced zero-initialization could be noticeable. It is particularly noticeable when `stackalloc` is used. In some cases JIT can elide initial zero-initialization of individual locals when such initialization is "killed" by subsequent assignments. Not all JITs do this and such optimization has limits. It does not help with `stackalloc`. To illustrate that the problem is real - there is a known bug where a method not containing any `IL` locals would not have `localsinit` flag. The bug is already being exploited by users by putting `stackalloc` into such methods - intentionally to avoid initialization costs. That is despite the fact that absence of `IL` locals is an unstable metric and may vary depending on changes in codegen strategy. The bug should be fixed and users should get a more documented and reliable way of suppressing the flag. ## Detailed design Allow specifying `System.Runtime.CompilerServices.SkipLocalsInitAttribute` as a way to tell the compiler to not emit `localsinit` flag. The end result of this will be that the locals may not be zero-initialized by the JIT, which is in most cases unobservable in C#. In addition to that `stackalloc` data will not be zero-initialized. That is definitely observable, but also is the most motivating scenario. Permitted and recognized attribute targets are: `Method`, `Property`, `Module`, `Class`, `Struct`, `Interface`, `Constructor`. However compiler will not require that attribute is defined with the listed targets nor it will care in which assembly the attribute is defined. When attribute is specified on a container (`class`, `module`, containing method for a nested method, ...), the flag affects all methods contained within the container. Synthesized methods "inherit" the flag from the logical container/owner. The flag affects only codegen strategy for actual method bodies. I.E. the flag has no effect on abstract methods and is not propagated to overriding/implementing methods. This is explicitly a **_compiler feature_** and **_not a language feature_**. Similarly to compiler command line switches the feature controls implementation details of a particular codegen strategy and does not need to be required by the C# spec. ## Drawbacks [drawbacks]: #drawbacks - Old/other compilers may not honor the attribute. Ignoring the attribute is compatible behavior. Only may result in a slight perf hit. - The code without `localinits` flag may trigger verification failures. Users that ask for this feature are generally unconcerned with verifiability. - Applying the attribute at higher levels than an individual method has nonlocal effect, which is observable when `stackalloc` is used. Yet, this is the most requested scenario. ## Alternatives [alternatives]: #alternatives - omit `localinits` flag when method is declared in `unsafe` context. That could cause silent and dangerous behavior change from deterministic to nondeterministic in a case of `stackalloc`. - omit `localinits` flag always. Even worse than above. - omit `localinits` flag unless `stackalloc` is used in the method body. Does not address the most requested scenario and may turn code unverifiable with no option to revert that back. ## Unresolved questions [unresolved]: #unresolved-questions - Should the attribute be actually emitted to metadata? ## Design meetings None yet. ================================================ FILE: proposals/csharp-9.0/static-anonymous-functions.md ================================================ # Static anonymous functions [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Allow a 'static' modifier on lambdas and anonymous methods, which disallows capture of locals or instance state from containing scopes. ## Motivation Avoid unintentionally capturing state from the enclosing context, which can result in unexpected retention of captured objects or unexpected additional allocations. ## Detailed design A lambda or anonymous method may have a `static` modifier. The `static` modifier indicates that the lambda or anonymous method is a *static anonymous function*. A *static anonymous function* cannot capture state from the enclosing scope. As a result, locals, parameters, and `this` from the enclosing scope are not available within a *static anonymous function*. A *static anonymous function* cannot reference instance members from an implicit or explicit `this` or `base` reference. A *static anonymous function* may reference `static` members from the enclosing scope. A *static anonymous function* may reference `constant` definitions from the enclosing scope. `nameof()` in a *static anonymous function* may reference locals, parameters, or `this` or `base` from the enclosing scope. Accessibility rules for `private` members in the enclosing scope are the same for `static` and non-`static` anonymous functions. No guarantee is made as to whether a *static anonymous function* definition is emitted as a `static` method in metadata. This is left up to the compiler implementation to optimize. A non-`static` local function or anonymous function can capture state from an enclosing *static anonymous function* but cannot capture state outside the enclosing *static anonymous function*. Removing the `static` modifier from an anonymous function in a valid program does not change the meaning of the program. ================================================ FILE: proposals/csharp-9.0/target-typed-conditional-expression.md ================================================ # Target-Typed Conditional Expression [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Conditional Expression Conversion For a conditional expression `c ? e1 : e2`, when 1. there is no common type for `e1` and `e2`, or 2. for which a common type exists but one of the expressions `e1` or `e2` has no implicit conversion to that type we define a new implicit *conditional expression conversion* that permits an implicit conversion from the conditional expression to any type `T` for which there is a conversion-from-expression from `e1` to `T` and also from `e2` to `T`. It is an error if a conditional expression neither has a common type between `e1` and `e2` nor is subject to a *conditional expression conversion*. ## Better Conversion from Expression We change > #### Better conversion from expression > > Given an implicit conversion `C1` that converts from an expression `E` to a type `T1`, and an implicit conversion `C2` that converts from an expression `E` to a type `T2`, `C1` is a ***better conversion*** than `C2` if `E` does not exactly match `T2` and at least one of the following holds: > > * `E` exactly matches `T1` ([§12.6.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12645-better-conversion-from-expression)) > * `T1` is a better conversion target than `T2` ([§12.6.4.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1264-better-conversion-target)) to > #### Better conversion from expression > > Given an implicit conversion `C1` that converts from an expression `E` to a type `T1`, and an implicit conversion `C2` that converts from an expression `E` to a type `T2`, `C1` is a ***better conversion*** than `C2` if `E` does not exactly match `T2` and at least one of the following holds: > > * `E` exactly matches `T1` ([§12.6.4.5](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12645-better-conversion-from-expression)) > * **`C1` is not a *conditional expression conversion* and `C2` is a *conditional expression conversion***. > * `T1` is a better conversion target than `T2` ([§12.6.4.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12647-better-conversion-target)) **and either `C1` and `C2` are both *conditional expression conversions* or neither is a *conditional expression conversion***. ## Cast Expression The current C# language specification says > A *cast_expression* of the form `(T)E`, where `T` is a *type* and `E` is a *unary_expression*, performs an explicit conversion ([§10.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#103-explicit-conversions)) of the value of `E` to type `T`. In the presence of the *conditional expression conversion* there may be more than one possible conversion from `E` to `T`. With the addition of *conditional expression conversion*, we prefer any other conversion to a *conditional expression conversion*, and use the *conditional expression conversion* only as a last resort. ## Design Notes The reason for the change to *Better conversion from expression* is to handle a case such as this: ```csharp M(b ? 1 : 2); void M(short); void M(long); ``` This approach does have two small downsides. First, it is not quite the same as the switch expression: ```csharp M(b ? 1 : 2); // calls M(long) M(b switch { true => 1, false => 2 }); // calls M(short) ``` This is still a breaking change, but its scope is less likely to affect real programs: ```csharp M(b ? 1 : 2, 1); // calls M(long, long) without this feature; ambiguous with this feature. M(short, short); M(long, long); ``` This becomes ambiguous because the conversion to `long` is better for the first argument (because it does not use the *conditional expression conversion*), but the conversion to `short` is better for the second argument (because `short` is a *better conversion target* than `long`). This breaking change seems less serious because it does not silently change the behavior of an existing program. The reason for the notes on the cast expression is to handle a case such as this: ```csharp _ = (short)(b ? 1 : 2); ``` This program currently uses the explicit conversion from `int` to `short`, and we want to preserve the current language meaning of this program. The change would be unobservable at runtime, but with the following program the change would be observable: ```csharp _ = (A)(b ? c : d); ``` where `c` is of type `C`, `d` is of type `D`, and there is an implicit user-defined conversion from `C` to `D`, and an implicit user-defined conversion from `D` to `A`, and an implicit user-defined conversion from `C` to `A`. If this code is compiled before C# 9.0, when `b` is true we convert from `c` to `D` then to `A`. If we use the *conditional expression conversion*, then when `b` is true we convert from `c` to `A` directly, which executes a different sequence of user code. Therefore we treat the *conditional expression conversion* as a last resort in a cast, to preserve existing behavior. ================================================ FILE: proposals/csharp-9.0/target-typed-new.md ================================================ # Target-typed `new` expressions [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Do not require type specification for constructors when the type is known. ## Motivation [motivation]: #motivation Allow field initialization without duplicating the type. ```cs Dictionary> field = new() { { "item1", new() { 1, 2, 3 } } }; ``` Allow omitting the type when it can be inferred from usage. ```cs XmlReader.Create(reader, new() { IgnoreWhitespace = true }); ``` Instantiate an object without spelling out the type. ```cs private readonly static object s_syncObj = new(); ``` ## Specification [design]: #detailed-design A new syntactic form, *target_typed_new* of the *object_creation_expression* is accepted in which the *type* is optional. ```antlr object_creation_expression : 'new' type '(' argument_list? ')' object_or_collection_initializer? | 'new' type object_or_collection_initializer | target_typed_new ; target_typed_new : 'new' '(' argument_list? ')' object_or_collection_initializer? ; ``` A *target_typed_new* expression does not have a type. However, there is a new *object creation conversion* that is an implicit conversion from expression, that exists from a *target_typed_new* to every type. Given a target type `T`, the type `T0` is `T`'s underlying type if `T` is an instance of `System.Nullable`. Otherwise `T0` is `T`. The meaning of a *target_typed_new* expression that is converted to the type `T` is the same as the meaning of a corresponding *object_creation_expression* that specifies `T0` as the type. It is a compile-time error if a *target_typed_new* is used as an operand of a unary or binary operator, or if it is used where it is not subject to an *object creation conversion*. > **Open Issue:** should we allow delegates and tuples as the target-type? The above rules include delegates (a reference type) and tuples (a struct type). Although both types are constructible, if the type is inferable, an anonymous function or a tuple literal can already be used. ```cs (int a, int b) t = new(1, 2); // "new" is redundant Action a = new(() => {}); // "new" is redundant (int a, int b) t = new(); // OK; same as (0, 0) Action a = new(); // no constructor found ``` ### Miscellaneous The following are consequences of the specification: - `throw new()` is allowed (the target type is `System.Exception`) - Target-typed `new` is not allowed with binary operators. - It is disallowed when there is no type to target: unary operators, collection of a `foreach`, in a `using`, in a deconstruction, in an `await` expression, as an anonymous type property (`new { Prop = new() }`), in a `lock` statement, in a `sizeof`, in a `fixed` statement, in a member access (`new().field`), in a dynamically dispatched operation (`someDynamic.Method(new())`), in a LINQ query, as the operand of the `is` operator, as the left operand of the `??` operator, ... - It is also disallowed as a `ref`. - The following kinds of types are not permitted as targets of the conversion - **Enum types:** `new()` will work (as `new Enum()` works to give the default value), but `new(1)` will not work as enum types do not have a constructor. - **Interface types:** This would work the same as the corresponding creation expression for COM types. - **Array types:** arrays need a special syntax to provide the length. - **dynamic:** we don't allow `new dynamic()`, so we don't allow `new()` with `dynamic` as a target type. - **tuples:** These have the same meaning as an object creation using the underlying type. - All the other types that are not permitted in the *object_creation_expression* are excluded as well, for instance, pointer types. ## Drawbacks [drawbacks]: #drawbacks There were some concerns with target-typed `new` creating new categories of breaking changes, but we already have that with `null` and `default`, and that has not been a significant problem. ## Alternatives [alternatives]: #alternatives Most of complaints about types being too long to duplicate in field initialization is about *type arguments* not the type itself, we could infer only type arguments like `new Dictionary(...)` (or similar) and infer type arguments locally from arguments or the collection initializer. ## Questions [questions]: #questions - Should we forbid usages in expression trees? (no) - How the feature interacts with `dynamic` arguments? (no special treatment) - How IntelliSense should work with `new()`? (only when there is a single target-type) ## Design meetings - [LDM-2017-10-18](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-10-18.md#100) - [LDM-2018-05-21](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-05-21.md) - [LDM-2018-06-25](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-06-25.md) - [LDM-2018-08-22](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-08-22.md#target-typed-new) - [LDM-2018-10-17](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md) - [LDM-2020-03-25](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-03-25.md) ================================================ FILE: proposals/csharp-9.0/top-level-statements.md ================================================ # Top-level statements [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary [summary]: #summary Allow a sequence of *statements* to occur right before the *namespace_member_declaration*s of a *compilation_unit* (i.e. source file). The semantics are that if such a sequence of *statements* is present, the following type declaration, modulo the actual method name, would be emitted: ``` c# partial class Program { static async Task Main(string[] args) { // statements } } ``` See also https://github.com/dotnet/csharplang/issues/3117. ## Motivation [motivation]: #motivation There's a certain amount of boilerplate surrounding even the simplest of programs, because of the need for an explicit `Main` method. This seems to get in the way of language learning and program clarity. The primary goal of the feature therefore is to allow C# programs without unnecessary boilerplate around them, for the sake of learners and the clarity of code. ## Detailed design [design]: #detailed-design ### Syntax The only additional syntax is allowing a sequence of *statement*s in a compilation unit, just before the *namespace_member_declaration*s: ``` antlr compilation_unit : extern_alias_directive* using_directive* global_attributes? statement* namespace_member_declaration* ; ``` Only one *compilation_unit* is allowed to have *statement*s. Example: ``` c# if (args.Length == 0 || !int.TryParse(args[0], out int n) || n < 0) return; Console.WriteLine(Fib(n).curr); (int curr, int prev) Fib(int i) { if (i == 0) return (1, 0); var (curr, prev) = Fib(i - 1); return (curr + prev, curr); } ``` ### Semantics If any top-level statements are present in any compilation unit of the program, the meaning is as if they were combined in the block body of a `Main` method of a `Program` class in the global namespace, as follows: ``` c# partial class Program { static async Task Main(string[] args) { // statements } } ``` The type is named "Program", so can be referenced by name from source code. It is a partial type, so a type named "Program" in source code must also be declared as partial. But the method name "Main" is used only for illustration purposes, the actual name used by the compiler is implementation dependent and the method cannot be referenced by name from source code. The method is designated as the entry point of the program. Explicitly declared methods that by convention could be considered as an entry point candidates are ignored. A warning is reported when that happens. It is possible to specify a different entry point via the `-main:` compiler switch. The entry point method always has one formal parameter, `string[] args`. The execution environment creates and passes a `string[]` argument containing the command-line arguments that were specified when the application was started. The `string[]` argument is never null, but it may have a length of zero if no command-line arguments were specified. The ‘args’ parameter is in scope within top-level statements and is not in scope outside of them. Regular name conflict/shadowing rules apply. Async operations are allowed in top-level statements to the degree they are allowed in statements within a regular async entry point method. However, they are not required, if `await` expressions and other async operations are omitted, no warning is produced. The signature of the generated entry point method is determined based on operations used by the top level statements as follows: | **Async-operations\Return-with-expression** | **Present** | **Absent** | |----------------------------------------|-------------|-------------| | **Present** | `static Task Main(string[] args)`| `static Task Main(string[] args)` | | **Absent** | `static int Main(string[] args)` | `static void Main(string[] args)` | The example above would yield the following `$Main` method declaration: ``` c# partial class Program { static void $Main(string[] args) { if (args.Length == 0 || !int.TryParse(args[0], out int n) || n < 0) return; Console.WriteLine(Fib(n).curr); (int curr, int prev) Fib(int i) { if (i == 0) return (1, 0); var (curr, prev) = Fib(i - 1); return (curr + prev, curr); } } } ``` At the same time an example like this: ``` c# await System.Threading.Tasks.Task.Delay(1000); System.Console.WriteLine("Hi!"); ``` would yield: ``` c# partial class Program { static async Task $Main(string[] args) { await System.Threading.Tasks.Task.Delay(1000); System.Console.WriteLine("Hi!"); } } ``` An example like this: ``` c# await System.Threading.Tasks.Task.Delay(1000); System.Console.WriteLine("Hi!"); return 0; ``` would yield: ``` c# partial class Program { static async Task $Main(string[] args) { await System.Threading.Tasks.Task.Delay(1000); System.Console.WriteLine("Hi!"); return 0; } } ``` And an example like this: ``` c# System.Console.WriteLine("Hi!"); return 2; ``` would yield: ``` c# partial class Program { static int $Main(string[] args) { System.Console.WriteLine("Hi!"); return 2; } } ``` ### Scope of top-level local variables and local functions Even though top-level local variables and functions are "wrapped" into the generated entry point method, they should still be in scope throughout the program in every compilation unit. For the purpose of simple-name evaluation, once the global namespace is reached: - First, an attempt is made to evaluate the name within the generated entry point method and only if this attempt fails - The "regular" evaluation within the global namespace declaration is performed. This could lead to name shadowing of namespaces and types declared within the global namespace as well as to shadowing of imported names. If the simple name evaluation occurs outside of the top-level statements and the evaluation yields a top-level local variable or function, that should lead to an error. In this way we protect our future ability to better address "Top-level functions" (scenario 2 in https://github.com/dotnet/csharplang/issues/3117), and are able to give useful diagnostics to users who mistakenly believe them to be supported. ================================================ FILE: proposals/csharp-9.0/unconstrained-type-parameter-annotations.md ================================================ # Unconstrained type parameter annotations [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: ## Summary Allow nullable annotations for type parameters that are not constrained to value types or reference types: `T?`. ```C# static T? FirstOrDefault(this IEnumerable collection) { ... } ``` ## `?` annotation In C#8, `?` annotations could only be applied to type parameters that were explicitly constrained to value types or reference types. In C#9, `?` annotations can be applied to any type parameter, regardless of constraints. Unless a type parameter is explicitly constrained to value types, annotations can only be applied within a `#nullable enable` context. If a type parameter `T` is substituted with a reference type, then `T?` represents a nullable instance of that reference type. ```C# var s1 = new string[0].FirstOrDefault(); // string? s1 var s2 = new string?[0].FirstOrDefault(); // string? s2 ``` If `T` is substituted with a value type, then `T?` represents an instance of `T`. ```C# var i1 = new int[0].FirstOrDefault(); // int i1 var i2 = new int?[0].FirstOrDefault(); // int? i2 ``` If `T` is substituted with an annotated type `U?`, then `T?` represents the annotated type `U?` rather than `U??`. ```C# var u1 = new U[0].FirstOrDefault(); // U? u1 var u2 = new U?[0].FirstOrDefault(); // U? u2 ``` If `T` is substituted with a type `U`, then `T?` represents `U?`, even within a `#nullable disable` context. ```C# #nullable disable var u3 = new U[0].FirstOrDefault(); // U? u3 ``` For return values, `T?` is equivalent to `[MaybeNull]T`; for argument values, `T?` is equivalent to `[AllowNull]T`. The equivalence is important when overriding or implementing interfaces from an assembly compiled with C#8. ```C# public abstract class A { [return: MaybeNull] public abstract T F1(); public abstract void F2([AllowNull] T t); } public class B : A { public override T? F1() where T : default { ... } // matches A.F1() public override void F2(T? t) where T : default { ... } // matches A.F2() } ``` ## `default` constraint For compatibility with existing code where overridden and explicitly implemented generic methods could not include explicit constraint clauses, `T?` in an overridden or explicitly implemented method is treated as `Nullable` where `T` is a value type. To allow annotations for type parameters constrained to reference types, C#8 allowed explicit `where T : class` and `where T : struct` constraints on the overridden or explicitly implemented method. ```C# class A1 { public virtual void F1(T? t) where T : struct { } public virtual void F1(T? t) where T : class { } } class B1 : A1 { public override void F1(T? t) /*where T : struct*/ { } public override void F1(T? t) where T : class { } } ``` To allow annotations for type parameters that are not constrained to reference types or value types, C#9 allows a new `where T : default` constraint. ```C# class A2 { public virtual void F2(T? t) where T : struct { } public virtual void F2(T? t) { } } class B2 : A2 { public override void F2(T? t) /*where T : struct*/ { } public override void F2(T? t) where T : default { } } ``` It is an error to use a `default` constraint other than on a method override or explicit implementation. It is an error to use a `default` constraint when the corresponding type parameter in the overridden or interface method is constrained to a reference type or value type. ## Design meetings - https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-11-25.md - https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-17.md#t ================================================ FILE: proposals/csharp-9.0/variance-safety-for-static-interface-members.md ================================================ # Variance Safety for static interface members Champion issue: ## Summary Allow static, non-virtual members in interfaces to treat type parameters in their declarations as invariant, regardless of their declared variance. ## Motivation - https://github.com/dotnet/csharplang/issues/3275 - https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-24.md#interface-static-member-variance 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 { static Task F = Task.FromResult(default(T)); // No problem static Task P => Task.FromResult(default(T)); //CS1961 static Task M() => Task.FromResult(default(T)); //CS1961 static event EventHandler 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. ## Detailed Design Here is the proposed content for Vaiance Safety section of the language specification [§17.2.3.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#17232-variance-safety). The change is the addition of "*These restrictions do not apply to occurrences of types within declarations of static members.*" sentence at the beginning of the section. ### Variance safety The occurrence of variance annotations in the type parameter list of a type restricts the places where types can occur within the type declaration. *These restrictions do not apply to ocurrances of types within declarations of static members.* A type `T` is ***output-unsafe*** if one of the following holds: * `T` is a contravariant type parameter * `T` is an array type with an output-unsafe element type * `T` is an interface or delegate type `S` constructed from a generic type `S` where for at least one `Ai` one of the following holds: * `Xi` is covariant or invariant and `Ai` is output-unsafe. * `Xi` is contravariant or invariant and `Ai` is input-safe. A type `T` is ***input-unsafe*** if one of the following holds: * `T` is a covariant type parameter * `T` is an array type with an input-unsafe element type * `T` is an interface or delegate type `S` constructed from a generic type `S` where for at least one `Ai` one of the following holds: * `Xi` is covariant or invariant and `Ai` is input-unsafe. * `Xi` is contravariant or invariant and `Ai` is output-unsafe. Intuitively, an output-unsafe type is prohibited in an output position, and an input-unsafe type is prohibited in an input position. A type is ***output-safe*** if it is not output-unsafe, and ***input-safe*** if it is not input-unsafe. ## Other Considerations 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. ================================================ FILE: proposals/deconstruction-in-lambda-parameters.md ================================================ # Deconstruction in lambda parameters Champion issue: ## Summary Lambda parameters may be deconstructed within the parameter list of the lambda expression: ```cs Action<(int, int)> action = ((a, b)) => { }; Action<(int, int)> action = ((int a, int _)) => { }; Action<(int, SomeRecord)> action = ((a, (b, _))) => { }; ``` ## Motivation There has been steady interest in this feature since tuple deconstruction debuted in C# 7. When using LINQ methods, it is natural to bundle multiple variables into a tuple to be used in later stages. Here is one illustration provided by the community for the desired workflow: ```cs var item = Enumerable .Range(1, 10) .Select(i => (i + 1, i * i)) .Where(((a, b)) => 2 * a < b) .OrderBy(((a, b)) => b) .Last(); ``` Another use case is when passing multiple variables through generic arguments which are provided in order to avoid allocations. Examples are the `TLocal` parameter on [`Parallel.For`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.for) and [`Parallel.ForEach`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreach), as well as [`ThreadPool.QueueUserWorkItem`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool.queueuserworkitem?view=net-10.0#system-threading-threadpool-queueuserworkitem-1(system-action((-0))-0-system-boolean)). The usage pattern would look like: ```cs ThreadPool.QueueUserWorkItem( static ((varA, varB, varC)) => { ... }, (varA, varB, varC), preferLocal: false); ``` In the prior examples, the introduction of tuples was a user decision. However, some APIs (including LINQ's `Zip` method) may always introduce tuples, and sometimes in a nested fashion. The user then has two options. One option is to dot into deeply nested tuples as in `tuple.Left.Right` below, where the tuple names are chosen by the API rather than by the user and are often less than meaningful. The other option is to add a deconstruction statement to the lambda which may entail converting to a block body: ```cs var finalProvider = compilationAndGroupedNodesProvider.SelectMany((tuple, cancellationToken) => { // Then either: var ((syntaxTree, syntaxNodes), compilation) = tuple; // or: ProcessNodes(tuple.Left.Right); ``` While the deconstruction assignment works, it takes the user out of the realm of being able to simply use expression-bodied lambdas. Overall, this is a small feature aimed at expanding the goodness started in v7 with deconstructible types, and at reducing recurring papercuts when using tuples. ## Detailed design Any lambda parameter may be deconstructed if its type is deconstructible. A deconstructed lambda parameter consists entirely of tuple syntax with either implicitly-typed or explicitly-typed tuple elements, corresponding to whether the lambda is implicitly-typed or explicitly-typed. The tuple syntax must have at least two elements. Parameter modifiers and attributes are not permitted inside or outside the tuple. A deconstructing tuple element may be one of three operations: variable declaration, discard, or recursive deconstruction. Each of the three operations works the same as it does in a deconstructing assignment or `foreach` iteration variable deconstruction; however, the syntaxes for these operations have some differences in lambda parameter deconstruction. (Examples of deconstructible types are tuples, positional records, and types with user-defined `Deconstruct` instance or extension methods. This proposal does not change the set of types that the language considers deconstructible.) ### Semantics A compile-time error is produced in the same way as in a deconstructing assignment or deconstructing `foreach` if the type is not deconstructible with the given arity and any given explicit types. Code within the body of the lambda may use the declared variable names to access the deconstructed values. Deconstruction is performed before the lambda method body runs. It is performed the same way as in a deconstructing assignment or deconstructing `foreach`, to the extent needed to populate the declared variables, regardless of whether the variables are used in the body of the lambda. Code within the body of the lambda may assign to the declared variables and take writable references to them. As with by-value lambda parameters, changes may be written to these declared variables, and such changes are not observable outside the lambda. It is an error if a declared variable name is the same as a lambda parameter name or the same as another declared variable name. ### Implicit and explicit typing An implicitly-typed lambda does not specify parameter types (`(a, b) =>`), whereas an explicitly-typed lambda does (`(int a, int b) =>`). Variable declarations and discards in lambda parameters must follow suit, including nested deconstructions. In an implicitly-typed lambda, types are never specified for variable declarations and discards: ```cs Action action = (a, (b, (c, _))) => { }; ``` Whereas in an explicitly-typed lambda, types are always specified for variable declarations and discards: ```cs Action action = (int a, (int b, (int c, int _))) => { }; ``` `var` is not permitted as a variable declaration type or discard type within the parameter list of either implicitly-typed or explicitly-typed lambdas. ### Discards When exactly one lambda parameter is named `_`, it is considered a parameter name rather than a discard for backwards compatibility reasons. However, if there is any deconstruction in the lambda parameters, `_` becomes a discard at the top level. This avoids the confusion that would occur if `_` was able to be referenced in the lambda body, despite there being two discards in the lambda parameter list (one top-level, one nested): ```cs Action action = (_, (a, _)) => { /* _ is not an identifier here */ }; ``` This mirrors the effect of a second top-level discard as in the following example: ```cs Action action = (_, _) => { /* _ is not an identifier here */ };/ ``` We go even further and cause `_` at the top level to be a discard if there is any deconstruction, even without a second discard, because this is not a breaking change for the language and it avoids spreading the dichotomy of `_`-as-discard-or-parameter-name any further: ```cs Action action = (_, (a, b)) => { /* _ is not an identifier here */ }; ``` ### Parameter list parentheses A lambda's parameter list parentheses may not be omitted if any parameter in a lambda expression is deconstructed: ```cs Action<(int, int)> action = (a, b) => { }; // ❌ INVALID Action<(int, int)> action = ((a, b)) => { }; // Valid Action<(int, int)> action = (int a, int b) => { }; // ❌ INVALID Action<(int, int)> action = ((int a, int b)) => { }; // Valid ``` ### Method type inference A deconstructed lambda parameter can be used to infer a tuple type (see [inferred tuple type](#inferred-tuple-type)) for a method type parameter: ```cs M(((int a, string b)) => { }); // Success: T is (int a, string b) M(((int a, (string _, byte _))) => { }); // Success: T is (int a, (string, byte)) void M(Action action) { } ``` A deconstructed lambda parameter may also be used to infer individual element types for the tuple: ```cs M(((int a, string b)) => { }); // Success: T1 is int, T2 is string void M(Action<(T1, T2)> action) { } ``` ```cs M(((int a, (string b, byte c))) => { }); // Success: T1 is int, T2 is string, T3 is byte void M(Action<(T1, (T2, T3))> action) { } ``` Here is an example that combines inferring individual element types for a tuple from the method signature with inferring an additional type type for a method type parameter: ```cs M(((int a, (string b, byte c))) => { }); // Success: T1 is int, T2 is (string b, byte c) void M(Action<(T1, T2)> action) { } ``` A deconstructed lambda parameter may not be used to infer type parameters of types besides tuples. This would require mapping Deconstruct methods back to type parameters and is not expected to be an essential scenario. ```cs M(((int a, int b)) => { }); // ❌ INVALID void M(Action> action) { } record R(T1 Prop1, T2 Prop2); ``` ### Overload resolution Deconstructed lambda parameters can resolve ambiguities in overload resolution if there is exactly one candidate that allows the deconstructed lambda parameters to all map to tuple types: ```cs M(((int a, int b)) => { }); // Succeeds: calls M(Action<(int A, int B)>) void M(Action<(int A, int B)> action) { } void M(Action action) { } record R(int Prop1, int Prop2); ``` This may combine with [method type inference](#method-type-inference). If the deconstructed lambda parameter maps to a type parameter for which a tuple type may be inferred, and no other overload maps the deconstructed lambda parameter to a tuple type, overload resolution succeeds: ```cs M2(((int a, int b)) => { }); // Succeeds: calls M<(int a, int b)>(Action) void M2(Action action) { } void M2(Action action) { } record R(int Prop1, int Prop2); ``` However, no attempt is made to resolve ambiguities between non-tuple types. A deconstructed lambda parameter may become valid on any parameter type if that type declares a new Deconstruct instance method or if a Deconstruct extension method is imported. This takes a page from target-typed new: `new(...)` expressions also contribute no information to overload resolution for similar reasons. ```cs M(((int a, int b)) => { }); // ❌ INVALID: Ambiguous invocation void M(Action action) { } void M(Action action) { } record R(int Prop1, int Prop2); ``` ### Lambda natural types An explicitly typed lambda with a deconstructed lambda parameter has a natural type. The deconstructed lambda parameter contributes a tuple type for the corresponding parameter in the lambda natural type as described by [inferred tuple type](#inferred-tuple-type). Lambda natural types do not make use of lambda parameter names, so the lack of a specified parameter name in a deconstructed lambda parameter is of no consequence. ### Inferred tuple type A tuple type may be inferred from a deconstructed lambda parameter for [method type inference](#method-type-inference) or [lambda natural types](#lambda-natural-types). A tuple type is determined for a given deconstructed lambda parameter as follows: 1. Its arity is the arity of the deconstructing tuple syntax. 1. For each deconstructing tuple element: 1. If the element is a variable declaration, the corresponding tuple type element is of the same type as the variable declaration and has the same name as the variable declaration. 1. If the element is a discard, the corresponding tuple type element is of the same type as the discard and has no name. 1. If the element is a nested deconstruction, a nested tuple type is formed from the nested deconstruction using the same containing algorithm as for the top level. The corresponding tuple type element is of the resulting nested tuple type and has no name. The rationale for using the deconstructed variable name as the tuple element name is the same as the rationale for inferring the variable names as tuple element names in the following example: ```cs var a = 1; var b = 2; var x = (a, b); x.a++; x.b++; ``` ## Specification TODO ## Expansions Deconstruction could be allowed in LINQ clauses. Deconstruction in `from` and `let` is tracked by . ================================================ FILE: proposals/dictionary-expressions.md ================================================ # Dictionary Expressions Champion issue: ## Summary *Dictionary Expressions* are a continuation of the C# 12 *Collection Expressions* feature. They extend that system with a new terse syntax, `["mads": 21, "dustin": 22]`, for creating common dictionary values. Like with collection expressions, merging other dictionaries into these values is possible using the existing spread operator `..` like so: `[.. currentStudents, "mads": 21, "dustin": 22]` Several dictionary-like types can be created without external BCL support. These types are: 1. Concrete dictionary-like types, containing an read/write indexer `TValue this[TKey] { get; set; }`, like `Dictionary` and `ConcurrentDictionary`. 1. The well-known generic BCL dictionary interface types: `IDictionary` and `IReadOnlyDictionary`. Further support is present for dictionary-like types not covered above through the `CollectionBuilderAttribute` and a similar API pattern to the corresponding *create method* pattern introduced for collection expressions. Types like `ImmutableDictionary` and `FrozenDictionary` will be updated to support this pattern. ## Motivation While dictionaries are similar to standard sequential collections in that they can be interpreted as a sequence of key/value pairs, they differ in that they are often used for their more fundamental capability of efficient looking up of values based on a provided key. In an analysis of the BCL and the NuGet package ecosystem, sequential collection types and values make up the lion's share of collections used. However, dictionary types were still used a significant amount, with appearances in APIs occurring at between 5% and 10% the amount of sequential collections, and with dictionary values appearing universally in all programs. Currently, all C# programs must use many different and unfortunately verbose approaches to create instances of such values. Some approaches also have performance drawbacks. Here are some common examples: 1. Collection-initializer types, which require syntax like `new Dictionary { ... }` (lacking inference of possibly verbose TKey and TValue) prior to their values, and which can cause multiple reallocations of memory because they use `N` `.Add` invocations without supplying an initial capacity. 1. Immutable collections, which require syntax like `ImmutableDictionary.CreateRange(...)`, but which are also unpleasant due to the need to provide values as an `IEnumerable`. Builders are even more unwieldy. 1. Read-only dictionaries, which require first making a normal dictionary, then wrapping it. 1. Concurrent dictionaries, which lack an `.Add` method, and thus cannot easily be used with collection initializers. Looking at the surrounding ecosystem, we also find examples everywhere of dictionary creation being more convenient and pleasant to use. Swift, TypeScript, Dart, Ruby, Python, and more, opt for a succinct syntax for this purpose, with widespread usage, and to great effect. Cursory investigations have revealed no substantive problems arising in those ecosystems with having these built-in syntax forms. Unlike with *collection expressions*, C# does not have an existing pattern serving as the corresponding deconstruction form. Designs here should be made with a consideration for being complementary with future deconstruction work. An inclusive solution is needed for C#. It should meet the vast majority of case for customers in terms of the dictionary-like types and values they already have. It should also feel pleasant in the language, complement the work done with collection expressions, and naturally extend to pattern matching in the future. ## Detailed Design The following grammar productions are added: ```diff collection_element : expression_element | spread_element + | key_value_pair_element ; + key_value_pair_element + : expression ':' expression + ; ``` Alternative syntaxes are available for consideration, but should be considered later due to the bike-shedding cost involved. Picking the above syntax allows the compiler team to move quickly at implementing the semantic side of the feature, allowing earlier previews to be made available. These syntaxes include, but are not limited to: 1. Using braces instead of brackets. `{ k1: v1, k2: v2 }`. 2. Using brackets for keys: `[k1] = v1, [k2] = v2` 3. Using arrows for elements: `k1 => v1, k2 => v2`. Choices here would have implications regarding potential syntactic ambiguities, collisions with potential future language features, and concerns around corresponding pattern forms. However, all of those should not generally affect the semantics of the feature and can be considered at a later point dedicated to determining the most desirable syntax. ## Design Intuition There are three core aspects to the design of dictionary expressions. 1. Collection expressions containing `KeyValuePair<,>` (coming from `expression_element`, `spread_element`, or `key_value_pair_element` elements) can now instantiate a normal *collection type* *or* a *dictionary type*. So, if the target type for a collection expression is some *collection type* (that is *not* a *dictionary type*) with an element of `KeyValuePair<,>` then it can be instantiated like so: ```c# List> nameToAge = ["mads": 21]; ``` This is just a simple augmentation on top of the existing collection expression rules. In the above example, the code will be emitted as: ```c# __result.Add(new KeyValuePair("mads", 21)); ``` 2. Introduction of the *dictionary type*. *Dictionary types* are types that are similar to the existing *collection types*, with the additional requirements that they have an *element type* of some `KeyValuePair` *and* have an indexer `TValue this[TKey] { ... }`. The former requirement ensures that `List` is not considered a dictionary type, as its element type is `T` not `KeyValuePair<,>`. The latter requirement ensures that `List>` is also not considered a dictionary type, with its `int`-to-`KeyValuePair` indexer (instead of an `int`-to-`string` indexer). `Dictionary` passes both requirements. As such, if the target type for the collection expression *is* a *dictionary* type, then all `KeyValuePair<,>` produced by `expression_element` or `spread_element` elements will be changed to use the indexer\* to assign into the resultant dictionary. Any `key_value_pair_element` will use that indexer\* directly as well. For example: ```c# Dictionary nameToAge = ["mads": 21, existingDict.MaxPair(), .. otherDict]; // would be rewritten similar to: Dictionary __result = new(); __result["mads"] = 21; // Note: the below casts must be legal for the dictionary // expression to be legal var __t1 = existingDict.MaxPair(); __result[(string)__t1.Key] = (int)__t1.Value; foreach (var __t2 in otherDict) __result[(string)__t2.Key] = (int)__t2.Value; ``` \* The above holds for types with an available `set` accessor in their indexer. Similar semantics are provided for dictionary types without a writable indexer (like immutable dictionary types, or `IReadOnlyDictionary<,>`), and are explained later in the spec. 3. Alignment of the rules for assigning to *dictionary types* with the rules for assigning to *collection types*, just requiring aspects such as *element* and *iteration types* to be some `KeyValuePair<,>`. *However*, with the rules extended such that the `KeyValuePair<,>` type itself is relatively transparent, and instead the rule is updated to work on the underlying `TKey` and `TValue` types. This view allows for very natural processing of what would otherwise be thought of as disparate `KeyValuePair<,>` types. For example: ```c# Dictionary d1 = ...; // Assignment possible, even though KeyValuePair` is not itself assignable to KeyValuePair Dictionary d2 = [.. d1]; ``` Note: Many rules in this spec will refer to types needing to be the same `KeyValuePair<,>` type. This is an informal way of saying the types must have an identity conversion between them. As such, `KeyValuePair<(int X, int Y), object>` would be considered the same type a `KeyValuePair<(int, int), object?>` for the purpose of these rules. With a broad interpretation of these rules, all of the following would be legal: ```c# // Assigning to dictionary types: Dictionary nameToAge1 = ["mads": 21, existingKvp]; // as would Dictionary nameToAge2 = ["mads": 21, .. existingDict]; // as would Dictionary nameToAge3 = ["mads": 21, .. existingListOfKVPS]; // Assigning to collection types: List nameToAge1 = ["mads": 21, existingKvp]; // as would List nameToAge2 = ["mads": 21, .. existingDict]; // as would List nameToAge3 = ["mads": 21, .. existingListOfKVPS]; ``` ## Comparer support A dictionary expression can also provide a custom *comparer* to control its behavior just by including such a value as the first `expression_element` in the expression. For example: ```c# Dictionary caseInsensitiveMap = [StringComparer.CaseInsensitive, .. existingMap]; // Or even: Dictionary caseInsensitiveMap = [StringComparer.CaseInsensitive]; ``` While this approach does reuse `expression_element` both for specifying individual `KeyValuePair<,>` as well as a comparer for the dictionary, there is no ambiguity here as no type could satisfy both types. The motivation for this is due to the high number of cases of dictionaries found in real world code with custom comparers. Support for any further customization is not provided. This is in line with the lack of support for customization for normal collection expressions (like setting initial capacity). Other designs were explored which attempted to generalize this concept (for example, passing arbitrary arguments along). These designs never landed on a satisfactory syntax. And the concept of passing an arbitrary argument along doesn't supply a satisfactory answer on how that would control instantiating an `IDictionary<,>` or `IReadOnlyDictionary<,>`. ### Question: Comparers for *collection types* Should support for the key comparer be available for normal *collection types*, not just *dictionary types*. This would be useful for set-like types like `HashSet<>`. For example: ```c# HashSet values = [StringComparer.CaseInsensitive, .. names]; ``` ### Question: Specialized comparer syntax. Should there be more distinctive syntax for the comparer? Simply starting with a comparer could be difficult to tease out. Having a syntax like so could make things clearer: ```c# // `comparer: ...` to indicate the purpose of this value Dictionary caseInsensitiveMap = [comparer: StringComparer.CaseInsensitive, .. existingMap]; // Semicolon to more clearly delineate the comparer Dictionary caseInsensitiveMap = [StringComparer.CaseInsensitive; .. existingMap]; // Both? Dictionary caseInsensitiveMap = [comparer : StringComparer.CaseInsensitive; .. existingMap]; ``` ### Question: Types of comparers supported. `IEqualityComparer` is not the only comparer type used in collections. `SortedDictionary<,>` and `SortedSet<,>` both use an `IComparer` instead (as they have ordering, not hashing semantics). It seems unfortunate to leave out `SortedDictionary<,>` if we are supporting the rest. As such, perhaps the rules should just be that the special value in the collection be typed as some `IComparer` or some `IEqualityComparer`. ## Conversions [*Collection expression conversions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) are **updated** to include conversions to *dictionary types*. An implicit *collection expression conversion* exists from a collection expression to the following types: * A single dimensional *array type* `T[]`, in which case the *element type* is `T` * A *span type*: * `System.Span` * `System.ReadOnlySpan` In which case the *element type* is `T` * A *type* with an appropriate *[create method](#create-methods)*, in which case the *element type* is the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) determined from a `GetEnumerator` instance method or enumerable interface, not from an extension method * A *struct* or *class type* that implements `System.Collections.IEnumerable` where: * The *type* has an *[applicable](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#11642-applicable-function-member)* constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression. * **One of the following holds:** * **The [*iteration type*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement) of the *type* is `KeyValuePair`, and the *type* has an instance *indexer*, with `get` and `set` accessors where:** * **The indexer has a single parameter passed by value or with `in`.** * **There is an identity conversion from the parameter type to `K` and an identity conversion from the indexer type to `V`.** *Identity conversions rather than exact matches allow type differences that are ignored by the runtime: `object` vs. `dynamic`; tuple element names; nullable reference types; etc.* * **The `get` accessor returns by value.** * **The `get` and `set` accessors are declared `public`.** * **The indexer is not [*hidden*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/basic-concepts.md#7723-hiding-through-inheritance).** * If the collection expression has any elements, the *type* has an instance or extension method `Add` where: * The method can be invoked with a single value argument. * If the method is generic, the type arguments can be inferred from the collection and argument. * The method is accessible at the location of the collection expression. In which case the *element type* is the [*iteration type*](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#1395-the-foreach-statement) of the *type*. * An *interface type*: * `System.Collections.Generic.IEnumerable` * `System.Collections.Generic.IReadOnlyCollection` * `System.Collections.Generic.IReadOnlyList` * `System.Collections.Generic.ICollection` * `System.Collections.Generic.IList` In which case the *element type* is `T` * **An *interface type*:** * **`System.Collections.Generic.IDictionary`** * **`System.Collections.Generic.IReadOnlyDictionary`** **In which case the *element type* is `KeyValuePair`** *Collection expression conversions* require implicit conversions for each element. The element conversion rules are **updated** as follows. The implicit conversion exists if the type has an *element type* `T` where for each *element* `Eᵢ` in the collection expression: * If `Eᵢ` is an *expression element* then: * There is an implicit conversion from `Eᵢ` to `T`, **or** * **There is no implicit conversion from `Eᵢ` to `T`, and `T` is a type `KeyValuePair`, and `Eᵢ` has a type `KeyValuePair`, and there is an implicit conversion from `Kᵢ` to `K` and an implicit conversion from `Vᵢ` to `V`.** * If `Eᵢ` is a *spread element* `..Sᵢ` then: * There is an implicit conversion from the *iteration type* of `Sᵢ` to `T`, **or** * **There is no implicit conversion from the *iteration type* of `Sᵢ` to `T`, and `T` is a type `KeyValuePair`, and `Sᵢ` has an *iteration type* `KeyValuePair`, and there is an implicit conversion from `Kᵢ` to `K` and an implicit conversion from `Vᵢ` to `V`.** * **If `Eᵢ` is a *key-value pair element* `Kᵢ:Vᵢ`, then `T` is a type `KeyValuePair`, and there is an implicit conversion from `Kᵢ` to `K` and an implicit conversion from `Vᵢ` to `V`.** > Allowing implicit key and value conversions is useful for *expression elements* and *spread elements* where the key or value types do not match the collection element type exactly. > > ```csharp > Dictionary x = ...; > Dictionary y = [..x]; // key-value pair conversion from KVP to KVP > ``` Collection arguments are *not* considered when determining *collection expression* conversions. ## Create methods > A *create method* is indicated with a `[CollectionBuilder(...)]` attribute on the *collection type*. > The attribute specifies the *builder type* and *method name* of a method to be invoked to construct an instance of the collection type. > **A create method need not be called `Create`. Instead, it may commonly use the name `CreateRange` in the dictionary domain.** > > For the create method: > - The method must have a single parameter of type System.ReadOnlySpan, passed by value, and there is an identity conversion from E to the *iteration type* of the collection type. > > - **The method has two parameters, where the first is a [*comparer*](#Comparer-support) and the other follows the rules of the *single parameter* rule above. This method will be called if the collection expression's first element is an [*comparer*](#Comparer-support) that is convertible to that parameter type.** *Dictionary type* authors who use `CollectionBuilderAttribute` should have the method that is pointed to have `overwrite` not `throw` semantics when encountering the same `.Key` multiple times in the span of `KeyValuePair<,>` they are processing. The runtime has committed to supplying these new CollectionBuilder methods that take `ReadOnlySpan<>` for their immutable collections. ## Construction [*Collection construction*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#construction) is **updated** as follows. The elements of a collection expression are evaluated in order, left to right. Each element is evaluated exactly once, and any further references to the elements refer to the results of this initial evaluation. ... If the target type is a *struct* or *class type* that implements `System.Collections.IEnumerable`, and the target type does not have a *[create method](#create-methods)*, the construction of the collection instance is as follows: * The elements are evaluated in order. Some or all elements may be evaluated *during* the steps below rather than before. * The compiler *may* determine the *known length* of the collection expression by invoking [*countable*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#adding-index-and-range-support-to-existing-library-types) properties — or equivalent properties from well-known interfaces or types — on each *spread element expression*. * The constructor that is applicable with no arguments is invoked. * **If the *iteration type* is a type `KeyValuePair` and the [*collection expression conversion*](#conversions) involves an instance *indexer*, then:** * **For each element in order:** * **If the element is a *key value pair element* `Kᵢ:Vᵢ` then:** * **First `Kᵢ` is evaluated, then `Vᵢ` is evaluated.** * **The indexer is invoked on the collection instance with the converted values of `Kᵢ` and `Vᵢ`.** * **If the element is an *expression element* `Eᵢ`, then:** * **If `Eᵢ` is implicitly convertible to `KeyValuePair`, then:** * **`Eᵢ` is evaluated and converted to a `KeyValuePair`.** * **The indexer is invoked on the collection instance with `Key` and `Value` of the converted value.** * **Otherwise, `Eᵢ` has a type `KeyValuePair`, in which case:** * **`Eᵢ` is evaluated.** * **The indexer is invoked on the collection instance with `Key` and `Value` of the value, converted to `K` and `V`.** * **If the element is a *spread element* where the spread element *expression* has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tᵢ` then:** * **An applicable `GetEnumerator` instance or extension method is invoked on the spread element *expression***. * **For each item from the enumerator:** * **If `Tᵢ` is implicitly convertible to `KeyValuePair` then:** * **The item is converted to a `KeyValuePair`.** * **The indexer is invoked on the collection instance with `Key` and `Value` of the converted item.** * **Otherwise, `Tᵢ` is a type `KeyValuePair`, in which case:** * **The indexer is invoked on the collection instance with `Key` and `Value` of the item, converted to `K` and `V`.** * **If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions.** * **If the *iteration type* is a type `KeyValuePair` and the [*collection expression conversion*](#conversions) involves an applicable `Add` method, then:** * **For each element in order:** * **If the element is a *key value pair element* `Kᵢ:Vᵢ` then:** * **First `Kᵢ` is evaluated, then `Vᵢ` is evaluated.** * **A `KeyValuePair` instance is constructed from the values of `Kᵢ` and `Vᵢ` converted to `K` and `V`.** * **The applicable `Add` instance or extension method is invoked with the `KeyValuePair` instance as the argument.** * **If the element is an *expression element* `Eᵢ`, then:** * **If `Eᵢ` is implicitly convertible to `KeyValuePair`, then the applicable `Add` instance or extension method is invoked with `Eᵢ` as the argument.** * **Otherwise, `Eᵢ` has a type `KeyValuePair`, in which case:** * **`Eᵢ` is evaluated.** * **A `KeyValuePair` instance is constructed from the `Key` and `Value` of the value, converted to `K` and `V`.** * **The applicable `Add` instance or extension method is invoked with the `KeyValuePair` instance as the argument.** * **If the element is a *spread element* where the spread element *expression* has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tᵢ` then:** * **An applicable `GetEnumerator` instance or extension method is invoked on the spread element *expression***. * **For each item from the enumerator:** * **If `Tᵢ` is implicitly convertible to `KeyValuePair` then the applicable `Add` instance or extension method is invoked with item as the argument.** * **Otherwise, `Tᵢ` is a type `KeyValuePair`, in which case:** * **A `KeyValuePair` instance is constructed from the `Key` and `Value` of the item, converted to `K` and `V`.** * **The applicable `Add` instance or extension method is invoked with the `KeyValuePair` instance as the argument.** * **If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions.** * Otherwise, the *iteration type* is *not* a `KeyValuePair` type, in which case: * For each element in order: * If the element is an *expression element*, the applicable `Add` instance or extension method is invoked with the element *expression* as the argument. * If the element is a *spread element* then ...: * An applicable `GetEnumerator` instance or extension method is invoked on the *spread element expression*. * For each item from the enumerator: * The applicable `Add` instance or extension method is invoked on the *collection instance* with the item as the argument. * If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions. * ... If the target type is an *array*, a *span*, a type with a *[create method](#create-methods)*, or an *interface*, the construction of the collection instance is as follows: * The elements are evaluated in order. Some or all elements may be evaluated *during* the steps below rather than before. * The compiler *may* determine the *known length* of the collection expression by invoking [*countable*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#adding-index-and-range-support-to-existing-library-types) properties — or equivalent properties from well-known interfaces or types — on each *spread element expression*. * An *initialization instance* is created as follows: * If the target type is an *array* and the collection expression has a *known length*, an array is allocated with the expected length. * If the target type is a *span* or a type with a *create method*, and the collection has a *known length*, a span with the expected length is created referring to contiguous storage. * Otherwise intermediate storage is allocated. The intermediate storage has an indexer for element assignment. * **If the *iteration type* is a type `KeyValuePair`, then:** * **For each element in order:** * **If the element is a *key value pair element* `Kᵢ:Vᵢ` then:** * **First `Kᵢ` is evaluated, then `Vᵢ` is evaluated.** * **The initialization instance *indexer* is invoked on the collection instance with the converted values of `Kᵢ` and `Vᵢ`.** * **If the element is an *expression element* `Eᵢ`, then:** * **If `Eᵢ` is implicitly convertible to `KeyValuePair`, then:** * **`Eᵢ` is evaluated and converted to a `KeyValuePair`.** * **The initialization instance *indexer* is invoked on the collection instance with `Key` and `Value` of the converted value.** * **Otherwise, `Eᵢ` has a type `KeyValuePair`, in which case:** * **`Eᵢ` is evaluated.** * **The initialization instance *indexer* is invoked on the collection instance with `Key` and `Value` of the value, converted to `K` and `V`.** * **If the element is a *spread element* where the spread element *expression* has an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Tᵢ` then:** * **An applicable `GetEnumerator` instance or extension method is invoked on the spread element *expression***. * **For each item from the enumerator:** * **If `Tᵢ` is implicitly convertible to `KeyValuePair` then:** * **The item is converted to a `KeyValuePair`.** * **The initialization instance *indexer* is invoked on the collection instance with `Key` and `Value` of the converted item.** * **Otherwise, `Tᵢ` is a type `KeyValuePair`, in which case:** * **The initialization instance *indexer* is invoked on the collection instance with `Key` and `Value` of the item, converted to `K` and `V`.** * **If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions.** * Otherwise, the *iteration type* is *not* a `KeyValuePair` type, in which case: * For each element in order: * If the element is an *expression element*, the initialization instance *indexer* is invoked to assign the evaluated expression at the current index. * If the element is a *spread element* then ...: * An applicable `GetEnumerator` instance or extension method is invoked on the *spread element expression*. * For each item from the enumerator: * The initialization instance *indexer* is invoked to assign the item at the current index. * If the enumerator implements `IDisposable`, then `Dispose` will be called after enumeration, regardless of exceptions. * If intermediate storage was allocated for the collection, a collection instance is allocated with the actual collection length and the values from the initialization instance are copied to the collection instance, or if a span is required the compiler *may* use a span of the actual collection length from the intermediate storage. Otherwise the initialization instance is the collection instance. * If the target type has a *create method*, the create method is invoked with the span instance. ## Type inference `k:v` elements contribute input and output inferences respectively to those types. Normal expression elements and spread elements must have associated `KeyValuePair` types, where the `K_n` and `V_n` then contribute as well. For example: ```c# KeyValuePair kvp = ...; var a = AsDictionary(["mads": 21, "dustin": 22, kvp]); // AsDictionary(Dictionary arg) static Dictionary AsDictionary(Dictionary arg) => arg; ``` The [*type inference*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference) rules are updated as follows. > 11.6.3.2 The first phase > > For each of the method arguments `Eᵢ`: > > * An *input type inference* is made *from* `Eᵢ` *to* the corresponding *parameter type* `Tᵢ`. > > An *input type inference* is made *from* an expression `E` *to* a type `T` in the following way: > > * If `E` is a *collection expression* with elements `Eᵢ`: > * **If `T` has an *element type* `KeyValuePair`, or `T` is a *nullable value type* `T0?` and `T0` has an *element type* `KeyValuePair`, then for each `Eᵢ`**: > * **If `Eᵢ` is a *key value pair element* `Kᵢ:Vᵢ`, then an *input type inference* is made *from* `Kᵢ` *to* `Kₑ` and an *input type inference* is made *from* `Vᵢ` *to* `Vₑ`.** > * **If `Eᵢ` is an *expression element* with type `KeyValuePair`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Kᵢ` *to* `Kₑ` and a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Vᵢ` *to* `Vₑ`.** > * **If `Eᵢ` is a *spread element* with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `KeyValuePair`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Kᵢ` *to* `Kₑ` and a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Vᵢ` *to* `Vₑ`.** > * If `T` has an *element type* `Tₑ`, or `T` is a *nullable value type* `T0?` and `T0` has an *element type* `Tₑ`, then for each `Eᵢ`: > * If `Eᵢ` is an *expression element*, then an *input type inference* is made *from* `Eᵢ` *to* `Tₑ`. > * If `Eᵢ` is a *spread element* with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `Sᵢ`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Sᵢ` *to* `Tₑ`. > * *[existing rules from first phase]* ... > 11.6.3.7 Output type inferences > > An *output type inference* is made *from* an expression `E` *to* a type `T` in the following way: > > * If `E` is a *collection expression* with elements `Eᵢ`: > * **If `T` has an *element type* `KeyValuePair`, or `T` is a *nullable value type* `T0?` and `T0` has an *element type* `KeyValuePair`, then for each `Eᵢ`**: > * **If `Eᵢ` is a *key value pair element* `Kᵢ:Vᵢ`, then an *output type inference* is made *from* `Kᵢ` *to* `Kₑ` and an *output type inference* is made *from* `Vᵢ` *to* `Vₑ`.** > * **If `Eᵢ` is an *expression element*, no inference is made from `Eᵢ`.** > * **If `Eᵢ` is a *spread element*, no inference is made from `Eᵢ`.** > * If `T` has an *element type* `Tₑ` or `T` is a *nullable value type* `T0?` and `T0` has an *element type* `Tₑ`, then for each `Eᵢ`: > * If `Eᵢ` is an *expression element*, then an *output type inference* is made *from* `Eᵢ` *to* `Tₑ`. > * If `Eᵢ` is a *spread element*, no inference is made from `Eᵢ`. > * *[existing rules from output type inferences]* ... The *input type inference* change is necessary to infer `T` in `InputType()` in the following; the *output type inference* change is necessary to infer `T` in `OutputType()`. ```csharp static void InputType(Dictionary d); static void OutputType(Dictionary> d); InputType(["a":1]); OutputType(["b":() => 2)]); ``` ## Extension methods No changes here. Like with collection expressions, dictionary expressions do not have a natural type, so the existing conversions from type are not applicable. As a result, a dictionary expression cannot be used directly as the first parameter for an extension method invocation. ## Overload resolution For example, given: ```c# void X(IDictionary dict); void X(Dictionary dict); ``` In this case, standard betterness would pick the latter method. Similarly for: ```c# void X(IEnumerable> dict); void X(Dictionary dict); ``` Similar to *collection expressions*, there is no betterness between disparate concrete dictionary types. For example: ```c# void X(Dictionary dict); void X(ImmutableDictionary dict); X([a, b]); // ambiguous ``` [*Better collection conversion from expression*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md) is updated as follows. > If there is an identity conversion from `E₁` to `E₂`, then the element conversions are as good as each other. Otherwise, the element conversions to `E₁` are ***better than the element conversions*** to `E₂` if: > - For every `ELᵢ`, `CE₁ᵢ` is at least as good as `CE₂ᵢ`, and > - There is at least one i where `CE₁ᵢ` is better than `CE₂ᵢ` > Otherwise, neither set of element conversions is better than the other, and they are also not as good as each other. > > Conversion comparisons are made as follows: > - **If the target is a type with an *element type* `KeyValuePair`:** > - **If `ELᵢ` is a *key value pair element* `Kᵢ:Vᵢ`, conversion comparison uses better conversion from expression from `Kᵢ` to `Kₑ` and better conversion from expression from `Vᵢ` to `Vₑ`.** > - **If `ELᵢ` is an *expression element* with *element type* `KeyValuePair`, conversion comparison uses better conversion from type `Kᵢ` to `Kₑ` and better conversion from type `Vᵢ` to `Vₑ`.** > - **If `ELᵢ` is an *spread element* with an expression with *element type* `KeyValuePair`, conversion comparison uses better conversion from type `Kᵢ` to `Kₑ` and better conversion from type `Vᵢ` to `Vₑ`.** > - **If the target is a type with an *element type* other than `KeyValuePair<,>`:** > - **If `ELᵢ` is a *key value pair element*, there is no conversion to the *element type*.** > - If `ELᵢ` is an *expression element*, conversion comparison uses better conversion from expression. > - If `ELᵢ` is a *spread element*, conversion conversion uses better conversion from the spread collection *element type*. > > `C₁` is a ***better collection conversion from expression*** than `C₂` if: > - Both `T₁` and `T₂` are not *span types*, and `T₁` is implicitly convertible to `T₂`, and `T₂` is not implicitly convertible to `T₁`, or > - **Both or neither of `T₁` and `T₂` have *element type* `KeyValuePair<,>`, and** `E₁` does not have an identity conversion to `E₂`, and both and the element conversions to `E₁` are ***better than the element conversions*** to `E₂`, or > - `E₁` has an identity conversion to `E₂`, and one of the following holds: > - `T₁` is `System.ReadOnlySpan`, and `T₂` is `System.Span`, or > - `T₁` is `System.ReadOnlySpan` or `System.Span`, and `T₂` is an *array_or_array_interface* with *element type* `E₂` > > Otherwise, neither collection type is better, and the result is ambiguous. ## Interface translation ### Non-mutable interface translation Given a target type `IReadOnlyDictionary`, a compliant implementation is required to produce a value that implements that interface. If a type is synthesized, it is recommended the synthesized type implements `IDictionary` as well. This ensures maximal compatibility with existing libraries, including those that introspect the interfaces implemented by a value in order to light up performance optimizations. In addition, the value must implement the nongeneric `IDictionary` interface. This enables collection expressions to support dynamic introspection in scenarios such as data binding. A compliant implementation is free to: 1. Use an existing type that implements the required interfaces. 2. Synthesize a type that implements the required interfaces. In either case, the type used is allowed to implement a larger set of interfaces than those strictly required. Synthesized types are free to employ any strategy they want to implement the required interfaces properly. For example, returning a cached singleton for empty collections, or a synthesized type which inlines the keys/values directly within itself, avoiding the need for additional internal collection allocations. 1. The value must return `true` when queried for `ICollection.IsReadOnly`. This ensures consumers can appropriately tell that the collection is non-mutable, despite implementing the mutable views. 1. The value must throw on any call to a mutation method (like `IDictionary.Add`). This ensures safety, preventing a non-mutable collection from being accidentally mutated. This follows the originating intuition around the `IEnumerable / IReadOnlyCollection / IReadOnlyList` interfaces and the allowed flexibility the compiler has in using an existing type or synthesized type when creating an instance of those in [*collection expressions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#non-mutable-interface-translation). ### Mutable interface translation Given the target type `IDictionary`: 1. The value must be an instance of `Dictionary` Translation mechanics will happen using the already defined rules that encompass the `Dictionary` type (including handling of an initially provided [*comparer*](#Comparer-support)). This follows the originating intuition around the `IList / ICollection` interfaces and the concrete `List` destination type in [*collection expressions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#mutable-interface-translation). ## Answered Questions ### Answered question 1 Can a dictionary type value be created without using a key_value_pair_element? For example, are the following legal? ```c# Dictionary d1 = [existingKvp]; Dictionary d2 = [.. otherDict]; ``` Note: the element `KeyValuePair` types need not be identical to the `KeyValuePair` type of the destination dictionary type. They simply must be convertible to the `V1 this[K1 key] { ... }` indexer provided by the dictionary. Yes. These are legal: [LDM-2024-03-11](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-03-11.md#conclusions) ### Answered question 2 Can you spread a *non dictionary type* when producing a dictionary type'd value. For example: ```c# Dictionary nameToAge = ["mads": 21, .. existingListOfKVPS]; ``` **Resolution:** *Spread elements* of key-value pair collections will be supported in dictionary expressions. [LDM-2024-03-11](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-03-11.md#conclusions) ### Answered question 3 How far do we want to take this KeyValuePair representation of things? Do we allow *key value pair elements* when producing normal collections? For example, should the following be allowed: ```c# List> = ["mads": 21]; ``` **Resolution:** *Key value pair elements* will be supported in collection expressions for collection types that have a key-value pair element type. [LDM-2024-03-11](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-03-11.md#conclusions) ### Answered question 4 Dictionaries provide two ways of initializing their contents. A restrictive `.Add`-oriented form that throws when a key is already present in the dictionary, and a permissive indexer-oriented form which does not. The restrictive form is useful for catching mistakes ("oops, I didn't intend to add the same thing twice!"), but is limiting *especially* in the spread case. For example: ```c# Dictionary optionMap = [opt1Name: opt1Default, opt2Name: opt2Default, .. userProvidedOptions]; ``` Or, conversely: ```c# Dictionary optionMap = [.. Defaults.CoreOptions, feature1Name: feature1Override]; ``` Which approach should we go with for dictionary expressions? Options include: 1. Purely restrictive. All elements use `.Add` to be added to the list. Note: types like `ConcurrentDictionary` would then not work, not without adding support with something like the `CollectionBuilderAttribute`. 2. Purely permissive. All elements are added using the indexer. Perhaps with compiler warnings if the exact same key is given the same constant value twice. 3. Perhaps a hybrid model. `.Add` if only using `k:v` and switching to indexers if using spread elements. There is deep potential for confusion here. **Resolution:** Use *indexer* as the lowering form. [LDM-2024-03-11](https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-03-11.md#conclusions) ### Answered question 5 What types and translation should be used when targeting dictionary interfaces (`IDictionary` or `IReadOnlyDictionary`)? **Resolution:** Use the same rules used for mutable and non-mutable interfaces for normal [*collection expressions*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#interface-translation) analogously translated to dictionaries. Full details can be found in [interface-translation](#interface-translation). [LDM-2025-04-09](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-09.md#conclusion) ### Conversion from expression element for `KeyValuePair` collections Confirm the **allowed conversions** from an *expression element* when the target type is a `KeyValuePair` collection. ```csharp List> list; list = [default]; // ok list = [new()]; // ok list = [new StringIntPair()]; // error: UDC not supported ``` > * If `Eᵢ` is an *expression element* then one of the following holds: > * **There is an implicit conversion from `Eᵢ` to `KeyValuePair` where the conversion is one of:** > * ***default literal conversion*** > * ***target-typed new conversion*** > * **`Eᵢ` has type `KeyValuePair` and there is an implicit conversion from `Kᵢ` to `K` and an implicit conversion from `Vᵢ` to `V`.** **Resolution:** Existing conversions should continue to apply for *expression elements* and *spread elements* before considering co-variant conversions of `Key` and `Value` for distinct `KeyValuePair<,>` types. [LDM-2025-03-17](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion-2) ### Binding to indexer For concrete dictionary types that do not use `CollectionBuilderAttribute`, where the compiler constructs the resulting instance using a constructor and repeated calls to an indexer, how should the compiler resolve the appropriate indexer for each element? ```csharp MyDictionary d = [ (object)"one":1, // this[object] { set; } "two":2 // this[string] { set; } ]; class MyDictionary : IEnumerable> { // ... public V this[K k] { ... } public object this[object o] { ... } } ``` Options include: 1. For each element individually, use normal lookup rules and overload resolution to determine the resulting indexer based on the element expression (for an expression element) or type (for a spread or key-value pair element). *This corresponds to the binding behavior for `Add()` methods for non-dictionary collection expressions.* 2. Use the target type implementation of `IDictionary.this[K] { get; set; }`. 3. Use the accessible indexer that matches the signature `V this[K] { get; set; }`. **Resolution:** Option 3: Use the indexer that qualifies the type as a dictionary type. [LDM-2025-03-05](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-05.md#conclusion) ### Type inference for `KeyValuePair` collections Confirm the [*type inference*](#type-inference) rules for elements when the target type is a `KeyValuePair` collection. ```csharp string x; int y; KeyValuePair e; Dictionary d; ... Print([x:y]); // Print Print([e]); // Print Print([..d]); // Print Print([x:y, e, ..d]); // Print void Print(List> pairs) { ... } ``` > * **If `T` has an *element type* `KeyValuePair`, or `T` is a *nullable value type* `T0?` and `T0` has an *element type* `KeyValuePair`, then for each `Eᵢ`**: > * **If `Eᵢ` is a *key value pair element* `Kᵢ:Vᵢ`, then an *input type inference* is made *from* `Kᵢ` *to* `Kₑ` and an *input type inference* is made *from* `Vᵢ` *to* `Vₑ`.** > * **If `Eᵢ` is an *expression element* with type `KeyValuePair`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Kᵢ` *to* `Kₑ` and a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Vᵢ` *to* `Vₑ`.** > * **If `Eᵢ` is a *spread element* with an [*iteration type*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/statements.md#1295-the-foreach-statement) `KeyValuePair`, then a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Kᵢ` *to* `Kₑ` and a [*lower-bound inference*](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116310-lower-bound-inferences) is made *from* `Vᵢ` *to* `Vₑ`.** **Resolution:** Rules accepted as written. [LDM-2025-03-24](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-24.md#conclusion-1) ### Overload resolution for `KeyValuePair` collections Confirm the *better conversion* rules for [*overload resolution*](#overload-resolution) when the target types are `KeyValuePair` collections. ```csharp KeyValuePair e; Dictionary d; ... Print([1:2]); // Print([e]) // ambiguous Print([..d]) // ambiguous void Print(List> pairs) { ... } void Print(List> pairs) { ... } ``` > Conversion comparisons are made as follows: > - **If the target is a type with an *element type* `KeyValuePair`:** > - **If `ELᵢ` is a *key value pair element* `Kᵢ:Vᵢ`, conversion comparison uses better conversion from expression from `Kᵢ` to `Kₑ` and better conversion from expression from `Vᵢ` to `Vₑ`.** > - **If `ELᵢ` is an *expression element* with *element type* `KeyValuePair`, conversion comparison uses better conversion from type `Kᵢ` to `Kₑ` and better conversion from type `Vᵢ` to `Vₑ`.** > - **If `ELᵢ` is an *spread element* with an expression with *element type* `KeyValuePair`, conversion comparison uses better conversion from type `Kᵢ` to `Kₑ` and better conversion from type `Vᵢ` to `Vₑ`.** **Resolution:** Rules accepted as written. [LDM-2025-03-24](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-24.md#conclusion-2) ### Support dictionary types as `params` type Should types with element type `KeyValuePair`, that are not otherwise collection types, be supported as `params` parameter types? ```csharp KeyValuePair x, y; ToDictionary(x, y); ToReadOnlyDictionary(x, y); static Dictionary ToDictionary( params Dictionary elements) => elements; // C#14: ok? static IReadOnlyDictionary ToReadOnlyDictionary( params IReadOnlyDictionary elements) => elements; // C#14: ok? ``` Note that regardless of whether we support dictionary types for `params`, or simply continue to support C#12 collection types with `KeyValuePair` element type, it won't be possible to use `k:v` syntax when calling a `params` method with *expanded form*. ```csharp ToList("one":1); // error: syntax error ':' ToList(["two":2]); // C#14: ok static List> ToList(params List> elements) => elements; ``` **Resolution:** Allow `params` on dictionary-like types that can be targeted with a collection expression, and constructing those types will prefer using indexers when available. [LDM-2025-03-24](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-24.md#conclusion) ### Question: Types that support both collection and dictionary initialization C# 12 supports collection types where the element type is some `KeyValuePair<,>`, where the type has an applicable `Add()` method that takes a single argument. Which approach should we use for initialization if the type also includes an indexer? For example, consider a type like so: ```c# public class Hybrid : IEnumerable> { public void Add(KeyValuePair pair); public TValue this[TKey key] { ... } } // This would compile in C# 12: // Translating to calls to .Add. Hybrid nameToAge = [someKvp]; ``` Options include: 1. Use applicable instance indexer if available; otherwise use C#12 initialization. 2. Use applicable instance indexer if available; otherwise report an error during construction (or conversion?). 3. Use C#12 initialization always. **Resolution:** If the target type is a struct or class type that implements `IEnumerable` and has an iteration type of `KeyValuePair`, and the type has the expected instance indexer (see [*Conversions*](#conversions)), then the indexer is used for initialization rather than any `Add` methods. [LDM-2025-03-05](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-05.md#conclusion) ### Question: Parsing ambiguity Parsing ambiguity around: `[a ? [b] : c]` **Resolution:** Parse as `[a ? ([b]) : (c)]`. [LDM-2025-04-14](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-14.md#conclusion) ### Question: Implement non-generic `IDictionary` when targeting `IReadOnlyDictionary<,>` Collection expressions specified explicitly: > Given a target type which does not contain mutating members, namely `IEnumerable`, `IReadOnlyCollection`, and `IReadOnlyList`, a compliant implementation is required to produce a value that implements that interface. ... > > In addition, the value must implement the nongeneric `ICollection` and `IList` interfaces. This enables collection expressions to support dynamic introspection in scenarios such as data binding. Do we want a similar correspondance when the target type is `IReadOnlyDictionary<,>`? Specifically, should the value be required to implement the non-generic `IDictionary` interface? **Resolution:** The type used to implement `IReadOnlyDictionary` should implement `System.Collections.IDictionary`. [LDM-2025-04-14](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-14.md#conclusion-1) ## Retracted Designs/Questions ### Question: Should `k:v` elements force dictionary semantics? Is there a concern around the following interface destinations: ```c# // NOTE: These are not overloads. void AAA(IEnumerable> pairs) ... void BBB(IDictionary pairs) ... AAA(["mads": 21, .. ldm]); BBB(["mads": 21, .. ldm]); ``` When the destination is an `IEnumerable`, we tend to think we're producing a sequence (so "mads" could show up twice). However, the use of the `k:v` syntax more strongly indicates production of a dictionary-value. What should we do here when targeting `IEnumerable<...>` *and* using `k:v` elements? Produce an ordered sequence, with possibly duplicated values? Or produce an unordered dictionary, with unique keys? Resolution: `IEnumerable` is not a dictionary type (as it lacks an indexer). As such, it has sequential value semantics (and can include duplicates). This would happen today anyways if someone did `[.. ldm]` and we do not think the presence of a `k:v` element changes how the semantics should work. This is also similar to how passing to an `IEnumerable` would differ from passing to some *set* type with normal collection expressions. The target type *intentionally* affects semantics, and there is no expectation that across very different target types that one would receive the same resultant values with the same behaviors. We do not view *dictionary types* or *key value pair elements* as changing the calculus here. ### Question: Allow deconstructible types? Should we take a very restrictive view of `KeyValuePair<,>`? Specifically, should we allow only that exact type? Or should we allow any types with an implicit conversion to that type? For example: ```c# struct Pair { public static implicit operator KeyValuePair(Pair pair) => ...; } Dictionary map1 = [pair1, pair2]; // ? List> pairs = ...; Dictionary map2 = [.. pairs]; // ? ``` Similarly, instead of `KeyValuePair<,>` we could allow *any* type deconstructible to two values? For example: ```c# record struct Pair(X x, Y y); Dictionary map1 = [pair1, pair2]; // ? ``` Resolution: While cute, these capabilities are not needed for core scenarios to work. They also raise concerns about where to draw the line wrt to what is the dictionary space and what is not. As such, we will only allow `KeyValuePair<,>` for now. And we will not do anything with tuples and/or other deconstructible types. This is also something that could be relaxed in the future if there is sufficient feedback and motivation to warrant it. This design space is withdrawn from dictionary expressions. ### Question: Semantics when a type implements the *dictionary type* shape in multiple ways. What are the rules when types have multiple indexers and multiple implementations of `IEnumerable>`? This concern already exists with *collection types*. For those types, the rule is that we must have an *element type* as per the existing language rules. This follows for *dictionary types*, along with the rule that there must be a corresponding indexer for this *element type*. If those hold, the type can be used as a *dictionary type*. If these don't hold, it cannot be. ### Question: Special case 'comparer' support for dictionaries (and regular collections)? [Collection expression arguments](https://github.com/dotnet/csharplang/blob/main/proposals/collection-expression-arguments.md) proposes a generalized system for providing arguments for constructible (`new(...)`) collection types, collection builder types, and for a subset of interface types. This solves the problem of how can a comparer be passed to a dictionary-like type, as well as for other collections that can benefit from customization (like hash sets and the like). However, in the absence of an approved language change to support a generalized argument passing system, do we want to be able to have special support for passing *only* comparers along? For example, a hypothetical syntax could be something like: ```c# Dictionary nameToOptions = [ comparer: StringComparer.OrginalIgnoreCase, .. GetDefaultOptions(), .. GetHostSpecificOptions(), .. GetPlatformSpecificOptions(), ]; ``` Pros: A specialized syntax can be clearer and more focused to the exact problem at hand, side stepping lots of complexity related to arbitrary argument passing (for example, out/ref arguments, named arguments, optional arguments, and the like). Cons: Some user will still want to pass arbitrary arguments along (for cases like 'capacity: ...', and for collections that capture/wrap some other collection). They will still be left out if we do not have a general system. And, if we add a general system later, there would be multiple ways to support passing a comparer along.
Possible syntactic options here are: 1. `[comparer: StringComparer.OrginalIgnoreCase]`. Simple and clear. But a long contextual keyword for 'comparer'. 2. `[comp: StringComparer.OrginalIgnoreCase]`. A bit less clear, but generally readable in context. Similar to `init` where we truncate a word to something reasonable in context. 3. `[...] with StringComparer.OrdinalIgnoreCase`. Not desirable. Collections may be quite large, and having to get to the end to understand core behavior/semantics of how the collection operates is not great. This especially clashes with all existing forms to make collections today, where the comparer will be at the start. 4. `[ == StringComparer.OrdinalIgnoreCase]`. Cutesy syntax. `==` represents 'equality', and thus this is a special element saying "equality is provided by this comparer" 5. `[ == : StringComparer.OrdinalIgnoreCase]`. Similarly cutesy, just using a colon to indicate "provided by". 6. `[ <=> StringComparer.OrdinalIgnoreCase]`. Cutesy, and in line with C++ (and potential future language changes) where the "spaceship operator" represents the way things compare against each other. Note: Any solution should support both `IComparer<>` (for `SortedSet<>`, `SortedDictionary<,>`, and their immutable variants) and `IEqualityComparer<>` (for all the hashing based collections). As such, a mild word like `comparer/comp` seems to fit the bill best. If we do special case comparers, the rules would say something intuitively akin to the following: > If a comparer element is provided, then: > 1. If generating a `new()` type, the type must have a constructor callable with the single comparer argument. > 2. If generating a collection builder type, there must be a factory method referenced that can take the comparer as the first argument, and the elements as the second. > 3. If generating an interface, the only supported interfaces are `IDictionary<,>` and `IReadOnlyDictionary<,>`. For the former, the comparer will be passed to the `new(IEqualityComparer<>)` constructor on `Dictionary<>`. For the latter, the dictionary created by the compiler will be guaranteed to use the specified equality comparer to perform hashing and equality checks of the provided keys. Note: real rules would be tbd. The above is just a light sketch to motivate discussion.
Resolution: We do not believe specialized syntax is worth it. We prefer this space be fully subsumed by the [Collection Expression Arguments](https://github.com/dotnet/csharplang/blob/main/proposals/collection-expression-arguments.md) feature. [LDM-2025-04-23](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md#conclusion-2) ## Open Questions ### Support `KeyValuePair<,>` variance with `params`? Should key and value variance be supported for expanded calls for a `params` collection of `KeyValuePair`? ```csharp KeyValuePair kvp = new("one", 1); PrintOne(kvp); // error: cannot convert from KeyValuePair to KeyValuePair PrintMany([kvp]); // ok! PrintMany(kvp); // ok? static void PrintOne(KeyValuePair arg) { } static void PrintMany(params KeyValuePair[] args) { } ``` If so, [*params collections*](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/params-collections.md#parameter-collections) will need to be **updated** to allow implicit conversions for key and value types. > A parameter collection permits arguments to be specified in one of two ways in a method invocation: > > - The argument given for a parameter collection can be a single expression that is implicitly convertible to the parameter collection type. > In this case, the parameter collection acts precisely like a value parameter. > - Alternatively, the invocation can specify zero or more arguments for the parameter collection, where each argument is an expression > that is implicitly convertible to the parameter collection's *element type*, **or** > **the argument is an expression of type `KeyValuePair` and the collection *element type* is `KeyValuePair` and there is a implicit conversion from `Kᵢ` to `Kₑ` and an implicit conversion from `Vᵢ` to `Vₑ`.** > In this case, the invocation creates an instance of the parameter collection type according to the rules specified in > [Collection expressions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md) > as though the arguments were used as expression elements in a collection expression in the same order, > and uses the newly created collection instance as the actual argument. > When constructing the collection instance, the original *unconverted* arguments are used. ================================================ FILE: proposals/enhanced-switch-statements.md ================================================ # Enhanced switch statements Champion issue: ## Summary [summary]: #summary This proposal is an enhancement to the existing switch statement syntax to permit switch-expression-style arms instead of using case statements. This feature is orthogonal to https://github.com/dotnet/csharplang/issues/3037 and can be done independently, but was conceived of as taking https://github.com/dotnet/csharplang/issues/3037's switch expressions and applying them to switch statements. ## Motivation [motivation]: #motivation This is an attempt to bring more recent C# design around switch expressions into a statement form, that can be used when there is nothing to return. The existing switch statement construct, while very familiar to C/C++ programmers, has several design choices that can feel dated by current C# standards. These include requiring `break;` statements in each case, even though there is no implicit fall-through in C#, and variable scopes that extend across all cases of the switch statement for variables declared _in_ the body, but not for variables declared in patterns in the case labels. We attempt to modernize this by providing an alternate syntax based on the switch expression syntax added in C# 8.0 and improved with the first part of this proposal. ## Detailed design [design]: #detailed-design We enhance the grammar of switch statements, creating an alternate form based on the grammar of switch expressions. This alternate form is not compatible with the existing form of switch statements: you must use either the new form or the old form, but not both. ```cs var o = ...; switch (o) { 1 => Console.WriteLine("o is 1"), string s => Console.WriteLine($"o is string {s}"), List l => { Console.WriteLine("o is a list of strings:"); foreach (var s in l) { Console.WriteLine($"\t{s}"); } } } ``` We make the following changes to the grammar: ```antlr switch_block : '{' switch_section* '}' | '{' switch_statement_arms ','? '}' ; switch_statement_arms : switch_statement_arm | switch_statement_arms ',' switch_statement_arm ; switch_statement_arm : pattern case_guard? '=>' statement_expression | pattern case_guard? '=>' block ; ``` Unlike a switch expression, an enhanced switch statement does not require the switch_statement_arms to have a best common type or to be target-typed. Whether the arm conditions have to be exhaustive like a switch expression is currently an open design question. Block-bodied statement arms are not required to have the end of the block be unreachable, and while a `break;` in the body will exit the switch arm, it is not required at the end of the block like in traditional switch statements. ## Drawbacks [drawbacks]: #drawbacks As with any proposals, we will be complicating the language further by doing these proposals. In particular, we will be adding another syntactic form to switch statements that has some very different semantics to existing forms. ## Alternatives [alternatives]: #alternatives https://github.com/dotnet/csharplang/issues/2632: The original issue proposed that we allow C# 8.0 switch expressions as `expression_statement`s. We had a few initial problems with this proposal: * We're uncomfortable making these a top-level statement without the ability to put more than 1 statement in an arm. * There's some concern that making an infix expression a top-level statement is not very CSharpy. * Requiring a semicolon at the end of a switch expression in an expression-statement context feels bad like a mistake, but we also don't want to convolute the grammar in such a way as to fix this issue. ## Unresolved questions [unresolved]: #unresolved-questions * Should enhanced switch statement arms be an all-or-nothing choice? ie, should you be able to use an old-style `case` label and a new-style arrow arm in the same statement? This proposal takes the opinion that this should be an exclusive choice: you use one or the other. This enables a debate about the second unresolved question for enhanced switch, whether they should be exhaustive. If we decide that enhanced switch should not be exhaustive, then this debate becomes largely a syntactic question. * An important note about modern switch expression arms is that you cannot have multiple `when` clauses with fallthrough, like you can with traditional switch statements today. If this is an all-or-nothing choice, this means that the moment you need multiple when clauses you fall off the rails and must convert the whole thing back to a traditional switch statement. * Should enhanced switch be exhaustive? Switch expressions, where enhanced switch statements are inspired from, are exhaustive. However, while the exhaustivity makes sense in an expression context where something must be returned in all cases, this makes less sense in a statement context. Until we get discriminated unions in the language, the only times we can be truly exhaustive in C# is when operating on a closed type heirarchy that includes a catch-all case, or we are operating on a value type. And if the user is required to add a catch-all do nothing case, then purpose of exhaustivity has been largely obviated. ================================================ FILE: proposals/expand-ref.md ================================================ Expanding ref support === ## Summary This proposal expands the capabilities of `ref` and `scoped` in the language. The goal being to leverage the existing types of rules in the model to allow `ref struct` usage in more locations and provide more lifetime expressiveness for APIs. ## Motivation There are still a number of scenarios around `ref` which cannot be safely expressed in the language. These are generally when using multiple mutable `ref struct` parameters where many are passed by `ref` or when trying to use `ref struct` in `ref` fields. To _fully_ satisfy all of these scenarios would require us to introduce explicit lifetime parameters and relationships into the language. That is a _huge_ investment that is not yet motivated by need. Instead this proposal takes our existing lifetime annotation, `scoped`, and sees how much further `ref` safety can be taken without introducing any other annotations or keywords. This doesn't solve all scenarios but does remove several known friction points in the language. It also serves to show us exactly where the limits are without introducing explicit lifetime parameters. ## Detailed Design The rules for `ref struct` safety are defined in the following documents: - [ref safety proposal](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md). - [ref fields proposal](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md) This proposal will be building on top of those previous ones. The more detailed rules will rely on the [annotation syntax](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#annotations) to describe the detailed rules. This is the most direct way to discuss how syntax behaves in the greater model. Readers interested in the very low level details should familiarize themselves with that syntax before digesting this proposal. ### ref scoped parameters The language will allow for parameters to be declared as `ref scoped`. This will serve to constrain the _safe-to-escape_ of the value such that it cannot be returned from the current method. ```csharp Span M(Span p1, ref scoped Span p2) { // Error: cannot return scoped value return p2; // Error: the safe-to-escape of p1 is not convertible to p2. p2 = p1; // Okay: heap can always be assigned p2 = default; // Okay p2[0] = 42; } ``` This capability will help cases where multiple `ref struct` values with different lifetimes are passed by `ref`. Having `ref scoped` allows developers to note which values do not escape and that allows for more call site flexibility. ```csharp ref struct Data { ... } void Copy1(ref Data source, ref Data dest) { ... } void Copy2(ref Data source, ref scoped Data dest) { ... } void Use(ref Data data) { // STE: current method var local = new Data(stackalloc int[42]); // Error: compiler has to assume local copied to data Copy1(ref data, ref local); // Okay: compiler knows lifetime only flows data -> local Copy2(ref data, ref local); } ``` This is accomplished by giving every `ref scoped` parameter a new escape scope named _current parameter N_ where _N_ is the numeric order of the parameter. For example the first parameter has a _safe-to-escape_ of _current parameter 1_. An escape scope of _current parameter N_ can be converted to _current method_ but has no other defined relationship. That serves to restrict their usage to the current method. It's important to note each parameter has a different _current parameter N_ scope. That means they cannot be assigned to each other. This is necessary to prevent `ref scoped` parameters from returning each others data. ```csharp void Swap(ref scoped Span p1, ref scoped Span p2) { // Error: can't assign current parameter 2 to current parameter 1 p2 = p1; // Error: can't assign current parameter 1 to current parameter 2 p1 = p2; // Okay: as current parameter 1 and 2 can be converted to current method scoped Span local1 = p1; scoped Span local2 = p2; // Okay: however the safe-to-escape here is current parameter N, not // current method so this could cause a bit of confusion later on Span local3 = p1; Span local4 = p2; // Okay: the safe-to-escape of the value is inferred in this case as it is // done for ref locals today. ref Span refLocal1 = ref p1; ref Span refLocal2 = ref p2; } ``` A `ref scoped` parameter is also implicitly `scoped ref`. That means neither the value nor its `ref` can be returned from the method. Both `ref` and `in` parameters can have their values modified with `scoped`. An `out` parameter cannot have its value modified with `scoped` as such a declaration is non-sensical. ```csharp void M( ref scoped Span p1, // Okay in scoped Span p2, // Okay out scoped Span p2, // Error ) ``` The [method arguments must match](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#rules-method-arguments-must-match) rules will be updated to take `ref scoped` into account. Values passed to such parameters do not need to be considered when calculating the return scopes. Detailed notes: - A `ref scoped` parameter is implicitly `scoped ref` - An `out scoped` parameter declaration is an error ### ref field to ref struct The language will allow for `ref struct` to appear as `ref scoped` fields. This `scoped` will serve to ensure the values cannot be escaped outside the containing instance but can be read and manipulated within it. ```csharp ref struct Deserializer { ref scoped Utf8JsonReader reader; ReadOnlySpan M1() { // okay: implicitly scoped to current method var span = reader.ValueSpan; // okay reader.Skip(); // Error: can't escape the ref data the ref scoped field refers to return reader.ValueSpan; } } ``` This is accomplished by giving every `ref scoped` field two new escape scopes named _current field N_ and _current ref field N_ where _N_ is the numeric order of the field. For example, the first field has a _safe-to-escape_ of _current field 1_ and a _ref-safe-to-escape_ of _current ref field N_. Both escape scopes can be converted to _current method_, and _current field N_ can be converted to _current ref field N_, but no other defined relationships exist. That serves to restrict their usage to the current method where the containing value is used. This escape scope applies to both. Below are a few examples of these rules in action ```csharp ref struct NestedRefStruct { } ref struct RefStruct { public NestedRefStruct NestedField; } ref struct S { ref scoped RefStruct field; RefStruct M1(RefStruct s) { // Okay field = new(); // Error: calling-method is not convertible to current-field-1 as they have // no relationship field = s; // Error: safe-to-escape is current-field-1 which isn't returnable return field; } NestedRefStruct M2() { // Error: safe-to-escape is current-field-1 which isn't returnable return field.NestedField; } ref RefStruct M3() { // Error: safe-to-escape is current-ref-field-1 which isn't returnable return ref field; } } ``` The [method arguments must match](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#rules-method-arguments-must-match) rules do not need to be updated here as they already account for `ref` parameters being captured as `ref` field. Even though a `ref` to `ref struct` was not directly returnable before, it could be returned indirectly by a `ref` to a `struct` field of the value. The language will also allow for `ref` fields to be declared as `scoped ref`. There are less use cases for this but `ref scoped` implies `scoped ref` hence the rules must be adjusted to account for this. As such the syntax will be exposed because while the use cases are small the infrastructure already exists. The _ref-safe-to-escape_ of such fields follows the logic above for `ref scoped` fields. Detailed notes: - A `ref` field where the type is a `ref struct` must be `ref scoped` - A `ref` field may be marked `scoped ref` ### Sunset restricted types The ability for any type to be a `ref` field allows us to fully sunset the notion of restricted types. The compiler has a concept of a set of _restricted types_ which is largely undocumented. These types were given a special status because in C# 1.0 there was no general purpose way to express their behavior. Most notably the fact that the types can contain references to the execution stack. Instead the compiler had special knowledge of them and restricted their use to ways that would always be safe: disallowed returns, cannot use as array elements, cannot use in generics, etc ... Once `ref` fields are available and extended to support `ref struct` these types can be fully rationalized within those rules. As such the compiler will no longer have the notion of restricted types when using a language version that supports `ref` fields of `ref struct`. To support this our `ref` safety rules will be updated as follows: - `__makeref(e)` will be logically treated as a method with the signature `static TypedReference __makeref(ref T value)` were `T` is the type of `e`. - `__refvalue(e, T)` - When `T` is a `ref struct`: will be treated as accessing a field declared as `ref scoped T` inside `e`. - Will be treated as accessing a field declared as `ref T` inside `e` - `__arglist` as a parameter will be implicitly `scoped` - `__arglist(...)` as an expression will have a *ref-safe-to-escape* and *safe-to-escape* of *current method*. Conforming runtimes will ensure that `TypedReference`, `RuntimeArgumentHandle` and `ArgIterator` are defined as `ref struct`. Further `TypedReference` must be viewed as having a `ref` field to a `ref struct` for any possible type (it can store any value). That combined with the above rules will ensure references to the stack do not escape beyond their lifetime. Note: strictly speaking this is a compiler implementation detail vs. part of the language. But given the relationship with `ref` fields it is being included in the language proposal for simplicity. ### Annotation Definition At an annotation level every parameter marked `ref scoped` will have a new lifetime parameter defined. The name will be `$paramN` where _N_ is the numerical order of the parameter. That lifetime will only have the relationship `where $paramN : $local`. ```csharp ref struct S { } void M(ref scoped S s) // maps to void M<$param1>(ref<$local> S<$param1> s) where $param1 : $local ``` This definition prevents the value from escaping from the method as the lifetime is not returnable. It also prevents local data from escaping from the current method through the parameter as the lifetime is wider than `$local` but not equivalent. ```csharp void M<$param1>(ref<$local> S<$param1> p) where $param1 : $local { S<$local> s = new S<$local>(stackalloc int[42]); // error: cannot convert S<$local> to S<$param1> p = s; } ``` At an annotation level every field marked `scoped ref` (explicitly or implicitly via `ref scoped`) will have a new lifetime parameter defined. The name will be `$refFieldN` where _N_ is the numerical order of the field. That lifetime will have the relationship `where $refFieldN : $local` in all methods that use the type. ```csharp ref struct S { scoped ref int i; } S M(S p) { } // maps to ref struct S { ref<$refField1> int i; } S<$cm> M<$cm, $l1>(S<$cm, $l1> p) where $l1 : $local { } ``` Every field marked as `ref scoped` will have a new lifetime parameter defined. The name will be `$fieldN` where _N_ is the numerical order of the field. That lifetime will have the relationship `where $fieldN : $refFieldN` defined on the type. It will also have the relationship `where $fieldN : $local` in all method that use the type. ```csharp ref struct S1 { } ref struct S2 { ref scoped S1 field; } S2 M(S2 p) { } // maps to ref struct S1 { } ref struct S2 where $field1 : $refField1 { ref<$refField1> S1<$field1> field; } S1<$cm, $l1, $l2> M<$cm, $l1>M(S<$cm, $l1, $l2> p) where $l2 : $l1 where $l1 : $local { } ``` These definitions prevent the values (`ref` or value) from escaping as their lifetimes are never returnable. It does allow for them to be manipulated and adjusted though. Non `ref` data, or data known to have `$heap` lifetime, can be assigned into such fields. ## Open Issues ### Ability to mark this as ref scoped The proposal does not provide any way to mark `this` as `ref scoped` for a given method. At this time the author can see no significant benefits to this. If such scenarios do come along then an attribute such as `[RefScoped]` could be introduced similar to how `[UnscopedRef]` works. ### Requiring ref fields to ref struct to be scoped Certain readers are likely to be disappointed that `ref` field to `ref struct` must be `ref scoped`. That limits the number of scenarios which can assign `ref` data into such fields. This is unfortunately necessary given the constraints of the design. Having a plain `ref` effectively requires that explicit lifetime annotations exist in the language. There is no other way to safely express the relationship between the value and the container. ================================================ FILE: proposals/extension-indexers.md ================================================ # Extension indexers [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Champion issue: https://github.com/dotnet/csharplang/issues/9856 ## Declaration ### Grammar Extension indexers are added to the set of permitted members inside an extension declaration by extending the grammar as follows (relative to [proposals/csharp-14.0/extensions.md](proposals/csharp-14.0/extensions.md#declaration)): ```antlr extension_member_declaration : method_declaration | property_declaration | indexer_declaration // new | operator_declaration ; ``` Like ordinary indexers, extension indexers have no identifier and are identified by their parameter list. Extension indexers may use the full set of features that ordinary indexers support today (accessor bodies, expression-bodied members, ref-returning accessors, `scoped` parameters, attributes, etc.). Because indexers are always instance members, an extension block that declares an indexer must provide a named receiver parameter. The existing restrictions on extension members continue to apply: indexers inside an extension declaration cannot specify `abstract`, `virtual`, `override`, `new`, `sealed`, `partial`, `protected` (or any of the related accessibility modifiers), or `init` accessors. ```csharp public static class BitExtensions { extension(int i) { public bool this[int index] { get => ...; } } } ``` All rules from the C# standard that apply to ordinary indexers apply to extension indexers, but extension members do not have an implicit or explicit `this`. The existing extension member inferrability rule still applies: For each non-method extension member, all the type parameters of its extension block must be used in the combined set of parameters from the extension and the member. ### `IndexerName` attribute `IndexerNameAttribute` may be applied to an extension indexer. The attribute is not emitted in metadata, but its value affects conflicts between members, it determines the name of the property and accessors in metadata, and is used when emitting `[DefaultMemberAttribute]` (see [Metadata](#metadata)). ## Consumption ### Indexer access The rules in [Indexer access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128124-indexer-access) are updated: if the normal processing of the indexer access finds no applicable indexer, an attempt is made to process the construct as an extension indexer access. 1. Attempt to bind using only the instance indexers declared (or inherited) on the receiver type. If an applicable candidate is found, overload resolution selects among those instance members as today and stops. 2. If the set of applicable indexers is empty, an attempt is made to process the **element_access** as an implicit `System.Index`/`System.Range` indexer access (which relies on `Length`/`Count` plus `this[int]`/`Slice(int, int)`). 3. If both steps fail to identify any applicable indexers, an attempt is made to process the **element_access** as an extension indexer access. Note: the element access section handles the case where an argument has type `dynamic`, so it never gets processed as an indexer access. #### Extension indexer access Extension members, including extension indexers, are never considered when the receiver is a **base_access** expression. Note: we only process an **element_access** as an indexer access if the receiver is a variable or value, so extension indexers are never considered when the receiver is a type. Given an **element_access** `E[A]`, the objective is to identify an extension indexer. A candidate extension indexer is ***applicable*** with respect to receiver `E` and argument list `A` if an expanded signature, comprised of the type parameters of the extension block and a parameter list combining the extension parameter with the indexer's parameters, is applicable with respect to an argument list combining the receiver `E` with the argument list `A`. We reuse the extension method scope-walk: we traverse the same scopes consulted for extension method invocation, including the current and enclosing lexical scopes and `using` namespace or `using static` imports. Considering each scope in turn: - Extension blocks in non-generic static class declarations in the current scope are considered. - The indexers in those extension blocks comprise the candidate set. - Candidates that are not accessible are removed from the set. - Candidates that are not applicable (as defined above) are removed from the set. - If the resulting set is not empty, overload resolution is applied to the candidate set. - If a single best indexer can be identified, then we have successfully processed the indexer access. - Otherwise, the extension indexer access is ambiguous and a compile-time error occurs. - Otherwise, an attempt is made to process the **element_access** as an implicit `System.Index`/`System.Range` indexer access (which relies on `Length`/`Count` plus `this[int]`/`Slice(int, int)`) using extension members in the current scope. - If there is no applicable candidate for one or both parts, then we proceed to the next scope. - If an applicable candidate is found for both parts (the `Length`/`Count` part and the `this[int]`/`Slice(int, int)` part), then we consider there was an applicable extension implicit indexer and this is the last scope we will consider. - If a single best member can be identified for each part, then we have successfully processed the implicit indexer access. - Otherwise, a compile-time error occurs. Using this single best indexer identified at the previous step, the indexer access is then processed as a static method invocation. Depending on the context in which it is used, an indexer access causes invocation of either the *get_accessor* or the *set_accessor* of the indexer. If the indexer access is the target of an assignment, the *set_accessor* static implementation method is invoked to assign a new value. In all other cases, the *get_accessor* static implementation method is invoked to obtain the current value. Either way, the invocation will use generic arguments inferred during the applicability check and the receiver as the first argument. ### Other element-access forms Any construct that defers to element-access binding (null-conditional element access or assignments, index assignments in object initializers, or list and spread patterns) automatically participates in the extension indexer resolution described above. - So a type with a suitable `Length` or `Count` extension properties is considered *countable* for the purpose of those patterns. - The implicit `System.Index`/`System.Range` fallback indexers can also be extensions, but the two parts (`Length`/`Count` and `this[int]`/`Slice(int, int)`) must be found in the same scope. Note: Since a list-pattern with a spread-pattern needs to bind a `Length`/`Count`, a real or implicit `this[Index]` and a real or implicit `this[Range]`, it may use the `Length` from one scope, the `this[int]` and corresponding `Length` from another scope and the `Slice(int, int)` and `Length` from yet another scope. The compiler assumes that the `Length`/`Count` properties are well-behaved and give the same result. ### Expression trees Extension indexers cannot be captured in expression trees. ### XML docs CREF syntax allows referring to an extension indexer and its accessors, as well as its implementation methods. Example: ```csharp /// /// /// /// /// /// /// /// /// public static class E { extension(int i) { /// public int this[string s] { get => throw null; set => throw null; } } } ``` ## Metadata Extension indexers follow the same lowering model as extension properties. For each CLR-level extension grouping type that contains at least one indexer, the compiler emits: - An extension property named `Item` (or the value supplied by `IndexerNameAttribute`) with accessor bodies that `throw NotImplementedException()` and an `[ExtensionMarkerName]` attribute referencing the appropriate extension marker type. - Implementation methods named `get_Item`/`set_Item` in the enclosing static class. These methods prepend the receiver parameter to the parameter list and contain the user-defined bodies. They are `static` and participate in overload resolution in the same way as implementation methods for extension properties. To mirror the behavior of ordinary indexers, the compiler also emits `[DefaultMemberAttribute]` on any extension grouping type that contains one or more extension indexers. The attribute’s `MemberName` equals the metadata name of the indexer (`Item` by default, or the value from `IndexerNameAttribute`). ### Example Source code: ```csharp static class BitExtensions { extension(T t) { public bool this[int index] { get => ...; set => ...; } } } ``` Emitted metadata (simplified to C#-like syntax): ```csharp [Extension] static class BitExtensions { [Extension, SpecialName, DefaultMember("Item")] public sealed class $T0 // grouping type { [SpecialName] public static class $T_t // marker type { [SpecialName] public static void $(T t) { } // marker method } [ExtensionMarkerName("$T_t")] public bool this[int index] // extension indexer { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } } // accessor implementation methods public static bool get_Item(T t, int index) => ...; public static void set_Item(T t, int index, bool value) => ...; } ``` ## Open issues ### ~~Dealing with `params`~~ If you have an extension indexer with `params`, such as `int this[int i, params string[] s] { get; set; }`, there are three ways you could use it: - extension indexing: `receiver[i: 0, "Alice", "Bob"]` - getter implementation invocation: `E.get_Item(receiver, i: 0, "Alice", "Bob")` - setter implementation invocation: `E.set_Item(...)` But what is the signature of the setter implementation method? It only makes sense for the last parameter of a user-invocable method signature to have `params`, so it serves no purpose in `E.set_Item(... extension parameter ..., this i, params string[] s, int value)`. Some options: 1. disallow `params` for extension indexers that have a setter 2. omit the `[ParamArray]` attribute on the setter implementation method 3. do nothing special (emit the `[ParamArray]`) I would propose option 2, as it maximizes `params` usefulness. The cost is only a small difference between extension indexing and disambiguation syntax. Decision (LDM 2026-02-02): emit the `[ParamArray]` and verify no negative impact on tooling ### ~~Impact of assigned value to type inference~~ ```csharp int i = 0; i[42, null] = new object(); // fails inference E.set_Item(i, 42, null, new object()); // infer `E.set_Item` public static class E { extension(int i) { public T this[int j, T t] { set { } } } } ``` ```csharp #nullable enable int i = 0; i[new object()] = null; // infer `E.extension` and warn on conversion of null literal to `object!` E.set_Item(i, new object(), null); // infer `E.set_Item` public static class E { extension(int i) { public T this[T t] { set { } } } } ``` Decision (LDM 2026-02-02): the indexer is inferred only given the receiver and arguments in the argument list (ie. the assigned value doesn't contribute). ### ~~Should extension `Length`/`Count` properties make a type countable?~~ As a reminder, extensions do not come into play when binding [implicit Index or Range indexers](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#adding-index-and-range-support-to-existing-library-types): ```csharp C c = new C(); _ = c[..]; // Cannot apply indexing with [] to an expression of type 'C' class C { public int Length => 0; } static class E { public static C Slice(this C c, int i, int j) => null!; } ``` So our position so far has been that extensions properties should not count as "countable properties" in list-patterns, collection expressions and implicit indexers. If we expose `this[Index]` or `this[Range]` extension indexers in element access scenarios, it is natural to expect the target type to work in list patterns. List patterns, however, require a `Length` or `Count` property. Should extension properties satisfy that requirement? (that would seem natural) ```csharp C c = new C(); var x1 = c[^1]; var x2 = c[1..]; if (c is [.., var y1]) { } if (c is [_, .. var y2]) { } class C { } static class E { extension(C c) { object this[System.Index] => ...; C this[System.Range] => ...; int Length => ...; } } ``` But then, should those properties also contribute to the implicit indexer fallback (`Length`/`Count` + `Slice`) that is used when an explicit `Index`/`Range` indexer is missing? ```csharp C c = new C(); if (c is [var y1, .. var y2]) { } class C { C Slice(int i, int j) => ...; } static class E { extension(C c) { object this[System.Index] => ...; int Length => ...; } } ``` Decision (LDM 2026-02-02): extensions should contribute everywhere, including countable properties and implicit indexer fallback. ### ~~Confirm whether extension indexer access comes before or after implicit indexers~~ ```csharp C c = new C(); _ = c[^1]; class C { public int Length => ...; public int this[int i] => ...; } static class E { extension(C c) { public int this[System.Index i] => ...; } } ``` I've spec'ed and implemented extension indexer access as having priority over implicit indexers, but now think they should come after to avoid unnecessary compat breaks. Update (LDM 2026-02-02): this needs further investigation. Yes, extensions should come after non-extension members, but beyond that we need some concrete proposals in light of above decision to allow extensions to contribute to implicit indexer fallback. ### Count/Length: Is the name prioritized first, or non-extension vs extension? We also have an existing fallback: `Length` is prioritized over `Count` property. Should an extension `Length` come before or after a non-extension `Count` property? Answer: the proposal is to look up scope by scope. Instance scope comes before extension scopes. Within each scope, we prefer `Length` over `Count`. ### ~~Confirm proposed design for implicit indexers~~ We look scope-by-scope, starting from instance scope and then proceeding to extension scopes. Within a scope: 1. we look for a real indexer, 2. otherwise, if we have a single argument of the right type, then we look for an implicit indexer. Decision (LDM 2026-03-09): no, once we move into extension scopes, we're going to use all-the-way-through extension resolution. ### ~~Confirm proposed design for list-patterns~~ For a list-pattern with a spread, we will look for: 1. a `Length`/`Count` 2. a real or implicit `this[Index]` 3. a real or implicit `this[Range]` We will look for each independently. But when we look for an implicit `this[Index]` or `this[Range]`, the two parts must come from the same scope: 1. `Length`/`Count` 2. `this[int]`/`Slice(int, int)` Note: the non-negative handling for `Length` patterns kicks in when a type can be used in a list-pattern (ie. it is countable and indexable). Decision (LDM 2026-03-09): no, we'll follow this lookup order instead: 1. List patterns are resolved as if we look for Length/Count, Index indexer and Range indexer individually 2. For Index and Range indexers, proceed as follows: a. With instance lookup only, find the "real" index if possible b. With instance lookup only, find the parts of the implicit indexer if possible c. With full lookup (instance+extension), find the "real" index if possible d. With full lookup (instance+extension), find the parts of the implicit indexer if possible (each in individual lookups) ### ~~Should extension `Slice` method also contribute?~~ ```cs _ = c[1..^1]; static class E { extension(C c) { public int Length => 3; } public static C Slice(this C c, int i, int j) => ...; } ``` Decision (LDM 2026-03-09): yes, we're treating classic and new extension methods exactly the same. ### ~~Should extension `Length` contribute to spread optimization?~~ ```cs C c = new C(); int[] i = [0, .. c]; // Uses Length, if available, to allocate the right size ``` Decision (LDM 2026-03-09): no, it's unlikely that an extension would be able to implement this in a performant way, so it would not help for optimization. ================================================ FILE: proposals/fieldof.md ================================================ # Allow direct use of backing field during construction Champion issue: Champion issue for `field` keyword: https://github.com/dotnet/csharplang/issues/8635 Related discussion: https://github.com/dotnet/csharplang/discussions/8704 ## Summary [summary]: #summary Allow direct assignment and use of a property's backing field during construction, without having to invoke the setter, via a new `fieldof(Prop)` expression. ```cs class C { public C(DataStore store) { this.store = store; fieldof(this.Prop) = store.ReadPropFromDisk(); M(ref fieldof(this.Prop)); } void Method() { // error: 'fieldof' can only be used during initialization (see also Alternatives) fieldof(this.Prop) = "a"; } private DataStore store; public string Prop { get => field; set { if (value != field) { field = value; store.WritePropToDisk(value); } } } } ``` ## Motivation [motivation]: #motivation We are seeing prominent source generators such as [MVVM Toolkit](https://github.com/CommunityToolkit/dotnet) and [ComputeSharp](https://github.com/Sergio0694/ComputeSharp) making heavy use of *partial field-backed properties*. Using a backing field for a partial property implementation comes with a number of experience improvements, including: 1. avoiding the need for either generator or user to introduce an additional member with a distinct name (and in practice, often the generator needs to introduce it, which is bad for discoverability). 2. avoiding the need for the generator to "wire up" the relationship between the field and property, in order to make it clear to user and compiler (e.g. for nullable constructor analysis). 3. allowing user to put `[field: Attr]` on the definition part of the property, and have the attributes just go where they're supposed to, without any hacky workarounds from the generator itself. 4. allowing user to put a property initializer on the definition part, letting the user initialize the field during construction without invoking the setter logic. Users are hitting limitations related to (4). Specifically, by *only* allowing the property initializer itself to assign the backing field, we are imposing an inconvenient limitation on what values are allowed to "bypass the setter" during construction: ```cs class C1 { // "stuff available in a static context" can be used: public partial string Prop { get; set; } = ValueFactory.GetValue(); } class C2(string prop) { // primary constructor parameters can be used: public partial string Prop { get; set; } = prop; } public class C3 { // but non-primary constructors must go through setter logic. internal C3() { // even though users may have reasons that a primary constructor isn't suitable. // e.g. in this case, even if we figure out how to make things work with a primary constructor, // we may not want that constructor to have the same accessibility as the containing type. var (first, second) = GetValues(); Prop1 = first; Prop2 = second; } public partial string Prop1 { get; set; } public partial string Prop2 { get; set; } } ``` See also [field-keyword.md#property-initializers](https://github.com/dotnet/csharplang/blob/main/proposals/field-keyword.md#property-initializers). It's fairly easy to imagine the `bool IsActive` example from that proposal, which motivated the property initializer behavior we have today, where the initial value doesn't simply come from a constant or a static, but needs to be passed in through a constructor. ```cs class SomeViewModel { public SomeViewModel(bool isActive) { // without a way to assign the field directly, // 'HasPendingChanges' is set to true, only when 'isActive' is true. // But all we're trying to do is rehydrate state from a previous session/user setting/etc.. IsActive = isActive; } public bool HasPendingChanges { get; private set; } public bool IsActive { get; set => Set(ref field, value); } private bool Set(ref T location, T value) { if (EqualityComparer.Default.Equals(location, value)) return false; location = value; HasPendingChanges = true; return true; } } ``` ### Why not just declare the field explicitly? We believe that solutions involving explicitly declaring the backing field will significantly degrade the end user experience in source generator scenarios. Essentially, hand-rolled substitutes for the benefits outlined in [Motivation](#motivation) are unlikely to be uniform and fully correct across various generators. Users would have to get oriented with different solutions across different generators for associating the field and property, locating the related declarations in user code and generated code, applying attributes independently to the field and property, and applying field initializers. See also [Alternate generator patterns](#alternate-generator-patterns). ### What about encapsulation? One purported benefit of the `field` keyword feature is that it is *only* usable from within the property accessors. It may seem questionable that this proposal is to seemingly change that, and allow the `field` to *also* be used in constructors. However, this encapsulation has never been as complete as the above statement implies. Today, a type's constructors need to be concerned with which properties are *field-backed*, because it is directly related to nullable constructor analysis--forgetting to assign or check a field-backed property can result in a warning, while doing the same on a non-field-backed property will not. ```cs class C { public string Prop1 { get => ValueStore.Get(); set => ValueStore.Set(value); } public string Prop2 { get => field; set => field = value; } // warning for Prop2, but not for Prop1 public C() { } } ``` The fact that a property initializer (and by extension, a primary constructor) is permitted to "bypass" the setter logic is necessary and useful. We think that allowing such "bypass" to occur in ordinary constructors of the same type is useful for the same reasons. Because the capability remains limited to construction-time, we believe it preserves and reinforces the benefits of using the `field` keyword. ## Detailed design [design]: #detailed-design The grammar is updated as follows: ```diff primary_no_array_creation_expression : literal | interpolated_string_expression | simple_name | parenthesized_expression (...) | nameof_expression + | fieldof_expression (...) ; +fieldof_expression + : 'fieldof' '(' expression ')' + ; ``` A `fieldof_expression` of the form `fieldof(P)` is evaluated and classified as follows: - If the containing member of the expression is not a constructor or `init` accessor, a compile-time error occurs. - If `P` is not classified as a property access of a [field-backed property](https://github.com/dotnet/csharplang/blob/main/proposals/field-keyword.md#glossary), a compile-time error occurs. - If `P` is classified as a property access of a field-backed property, then `fieldof(P)` is classified as a variable, specifically the backing field of `P`. - If `P` is static and the containing constructor is not static, or vice-versa, a compile-time error occurs. - If `P` is not declared in the containing type, a compile-time error occurs. (`fieldof()` does not work with a property declared on a base type.) - Otherwise, `fieldof(P)` denotes the backing field of `P`. A `fieldof_expression` is subject to limitations on the receiver of its property access, similar to an assignment to a `readonly` field. Specifically, the receiver must be the instance being initialized by the containing constructor, i.e. explicit or implicit `this`. Otherwise, a compile-time error occurs. ### Ref safety The *ref-safe-context* ([§9.7.2.4](https://github.com/dotnet/csharpstandard/blob/81d9d57826f289fbf772e10dfec776227fab1006/standard/variables.md#9724-field-ref-safe-context)) for an expression of the form `fieldof(e.P)` is determined as follows: - If `e` is of a value type, then the *ref-safe-context* of `fieldof(e.P)` is the same as the *ref-safe-context* of `e`. - Otherwise, its *ref-safe-context* is *caller-context*. The *safe-context* ([§16.4.12.4](https://github.com/dotnet/csharpstandard/blob/81d9d57826f289fbf772e10dfec776227fab1006/standard/structs.md#164124-field-safe-context)) for an expression of the form `fieldof(e.P)` is determined as follows: - If `e.P` is of ref struct type, then the *safe-context* of `fieldof(e.P)` is the same as the *safe-context* of `e`. - Otherwise, its *safe-context* is *caller-context*. The above ref safety rules are strongly analogous to the existing, linked rules which apply to ordinary field accesses. ### Compat This design makes no concession to preserving existing `fieldof(P)` behavior when a symbol `fieldof` is already in scope. This is a divergence from existing `nameof` behavior, but aligns with the existing breaking change design of the `field` keyword itself. This proposal also reserves `fieldof(P)` in expression contexts generally, rather than reserving it only in constructors and `init` accessors. Existing code containing calls like `fieldof(P)` would need to be changed to `@fieldof(P)` in order to avoid breaks. We could consider instead limiting the break to only apply within constructors and `init` accessors. Depending on feedback, we could also adjust the design so that `fieldof` works more like `nameof`, and simply becomes unavailable when a symbol `fieldof` is in scope. To give a sense of relative risk of the break with `field`, versus `fieldof`, there are about ~86k results for `field` in C# source and comments on GitHub, and about 5 of the same for `fieldof`, at time of writing. ## Drawbacks [drawbacks]: #drawbacks The motivating scenarios may not rise to the level of justifying a new contextual keyword or specialized syntax form in the language. ## Alternatives [alternatives]: #alternatives ### Permit anywhere in the same type Except for encapsulation, there isn't a specific reason we *need* to limit use of `fieldof()` to initialization. We could instead allow it anywhere in the same type if we wanted, which would effectively make `field` itself just a shorthand for `fieldof()` for the current property. ```cs class C { string P { get => fieldof(P); set => field = value; } void M0() { M1(ref fieldof(P)); } void M1(ref string s) { } } ``` The "encapsulation" behavior, as it currently exists in absence of `fieldof()`, seems appealing, as prevents misuse of the field outside the policy of the associated property. However, since `fieldof()` is always a more nested expression than a property access, it seems like users will tend to use the property *anyway* unless they have a specific reason for needing to use the field. If we think that there are justified construction-specific cases for using the backing field directly, then perhaps there are also valid post-construction cases as well, that we may not know about yet, and it's not justified to put in a *cliff*, saying: sorry, only during initialization or in the accessors. Instead, we could simply see what the user is trying to do, and get out of their way. At the same time, `field` was thought to be a stepping stone toward a more general "property scoped fields" feature--where it seems much harder to justify accessing the fields outside of the property. The *just get out of the user's way* line of reasoning also seems to lead to letting people specify any accessibility for the backing field, which somehow seems a little too far. Essentially, the `field` feature is providing both *encapsulation* and *association* benefits. The question is whether to allow users to drop the *encapsulation* part, if they wish, and keep the *association* part. ### Alternate syntaxes We could consider alternative syntax for doing the same thing, such as an [init prefix](https://github.com/dotnet/csharplang/discussions/8704#discussioncomment-11450489): ```cs public C(string prop) { // 'init' prefix appearing before an assignment means assign a backing field. init Prop = prop; } ``` Arguably, `fieldof(Prop)` is more clear than `init Prop`. The latter is more of a "knowledge check" that a property initializer assigns the field without calling the setter, and that we are using `init` to "simulate" such an initializer outside of the property declaration. While `fieldof(Prop)` more directly states "we are using the field here". Also, due to reusing an accessor keyword, it may be confusing to unfamiliar users: ```cs class C { public string Prop { get; init { SideEffect(); field = value; } } public C(string prop) { // wait.. putting 'init' here means "don't use the init accessor"? init Prop = prop; } } ``` ### Alternate generator patterns Generator authors could come up with a pattern where the field is explicitly declared by either generator or user, and associated to the property in a way that generator and compiler can understand (e.g., nullability attributes, naming conventions, and/or generator-specific attributes to associate members by name). Then, user can simply refer to the explicit field in a constructor. We think this is a bad solution, because it requires generator authors to solve all the bullet points mentioned in [Motivation](#motivation), and necessarily results in a compromised end user experience. ### `initialized` flag pattern We could advise users in this situation to introduce a flag which is set at the end of construction: ```cs class C { private readonly bool _initialized; public C(string prop) { Prop = prop; _initialized = true; } public string Prop { get => field; set { if (!_initialized) { field = value; return; } if (value != field) { field = value; OnPropertyChanged(); } } } } ``` We think this is not a palatable solution compared to simply being able to set the field, due to increasing memory usage and complicating setter and constructor logic. ## Open questions [open]: #open-questions See [Compat](#compat). ================================================ FILE: proposals/final-initializers.md ================================================ # Final initializers * [x] Proposed * [ ] Prototype: Not Started * [ ] Implementation: Not Started * [ ] Specification: Not Started ## Summary [summary]: #summary Final initializers are a proposed new kind of member declaration that runs at the end of an object's initialization - after constructors, object initializers and collection initializers. *Early notes refer to these as "validators".* ## Motivation [motivation]: #motivation With object and collection initializers in the language, there is not a place in a type declaration to write code that runs *after* the object is otherwise fully initialized - e.g. to do final validation, trim or clean up input, compute additional private state, register the object somewhere, etc. Final initializers provide a new kind of member declaration specifically for that purpose. As an example, consider a type which has properties for the year, month and the day. The month should be a value in the range 1-12 and the day checked against the `DateTime.DaysInMonth` method which uses the year and month. While this check can easily be done in a constructor, there is currently no way to do the check when object initializers are allowed. These checks could be done in the final initializer which would run after all constructors and initialization, including initialization done in code instantiating a member of the class. ## Detailed design [design]: #detailed-design Running example: ``` c# public class Person { public required string FirstName { get; init; } public string? MiddleName { get; init; } public required string LastName { get; init; } private readonly string fullName; init { // Fix up provided state FirstName = FirstName.Trim(); MiddleName = MiddleName?.Trim(); LastName = LastName.Trim(); // Validate state if (FirstName is "") throw new ArgumentException("Empty names not allowed", nameof(FirstName)); if (LastName is "") throw new ArgumentException("Empty names not allowed", nameof(LastName)); // Compute additional state fullName = (MiddleName is null) ? $"{FirstName} {LastName}" : $"{FirstName} {MiddleName} {LastName}"; } public override string ToString() => fullName; } ``` ### Syntax This production is added to `class_member_declaration`, etc.: ``` antlr final_initializer_declaration : attributes? `init` method_body ; ``` No modifiers can be specified. In some ways the declaration is a counterpart to a finalizer [14.13](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/classes.md#1413-finalizers), in that it has a very specific format with few points of decision for the programmer. ### Semantics Unless one is specified, all types are considered to have an implicit, empty final initializer. A final initializer is considered a public virtual instance member. A final initializer declaration is considered an override, and will implicitly perform a call to the final initializer of the base class before executing the specified body, all the way up to the (empty) final initializer of the `object` class. Just like within constructor bodies and `init` accessor bodies, `readonly` fields and init-only properties can be assigned to within a final initializer body. In the above example, all the assignments in the final initializer body are to readonly fields and init-only properties. At the end of the execution of an object creation expression ([11.7.15.2](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#117152-object-creation-expressions)) or a `with` expression, the resulting object's final initializer is executed as a function member invocation [11.6.6](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/expressions.md#1166-function-member-invocation) on the resulting object, which means that it will be a virtual call based on the runtime type of that object. ### Nullability When it comes to nullability analysis, the body of the final initializer would be assumed to benefit from member initializers and would consider `required` member annotations when it comes to *reading* non-nullable reference fields. It would in turn contribute to preventing nullability warnings by *writing* to non-nullable reference fields. In the above example `FirstName.Trim()` does not yield a nullability warning, because the property is required. At the same time, the declaration of the non-nullable `fullName` does not yield a nullability error, because it is assigned to in the final initializer. ### Inheritance hierarchies Final initializers would always call the base class final initializer before performing the code of the initializer. ### Implementation strategies Final initializers would probably be implemented as a public virtual method with an unspeakable name (same across all final initializers), no parameters and a `void` return type. This proposal is for `object` itself to have such a virtual method, and for all final initializer declarations to be turned into overrides of this method with a `base` call prepended to the body. This would lead to every single object having such a method on it, which may or may not be an issue. The name of this method would be consistent across all final initializers and unspeakable, assuming use cases for users to directly call it are not discovered. Using this strategy, the final initializer in the above example would generate a method override like the following: ``` c# public override void __final_initializer() { base.__final_initializer(); ... } ``` An object creation expression ``` c# new Person { FirstName = "Marie", LastName = "Curie" } ``` would generate code to create the object and initialize the properties, as today, followed by a call to `__final_initializer()``: ``` c# var __tmp = new Person(); __tmp.FirstName = "Marie"; __tmp.LastName = "Curie"; __.tmp.__final_initializer(); ``` ## Drawbacks [drawbacks]: #drawbacks This is yet another mechanism related to object initialization. While it provides a means for behaviors and guarantees around object state that weren't available before, it needs to be weighed against the complexity it adds. ## Alternatives [alternatives]: #alternatives ### Implementation [implementation]: #implementation Other alternatives include introducing method declarations only when necessitated by final initializer declarations. Under such strategies, it is important that the presence of final initializer methods is dynamically discoverable (either through reflection, interface implementation or otherwise), so that even when the runtime type is not statically known, the final initializer can be found and called. This can be the case for `with` expressions. The latter set of approaches can be vulnerable to binary breaks - if a base class introduces a final initializer, a derived class that is not recompiled may have the wrong semantics and not call the base. ### Syntax/naming [syntax-naming]: #syntax-naming - Is the `init` syntax the best choice? Should it rather mirror finalizers in some way, with a glyph and an empty parameter list `()`? - Is unconditionally calling the base final initializer before the body too inflexible? Should there be an explicit base call syntax instead? ### Inheritance [inheritance]: #inheritance Alternatively, the user be expected to call the base final initializer explicitly at some point in the execution of their final initializer, rather than emitting the code to call base automatically. ## Unresolved questions [unresolved]: #unresolved-questions - Which implementation strategy best balances performance with binary compatibility? - Should `extern` be supported, as it is for finalizers? ## Design meetings https://github.com/dotnet/csharplang/blob/main/meetings/2020/LDM-2020-04-27.md#primary-constructor-bodies-and-validators ================================================ FILE: proposals/immediately-enumerated-collection-expressions.md ================================================ # Immediately Enumerated Collection Expressions Champion issue: https://github.com/dotnet/csharplang/issues/9754 ## Summary [summary]: #summary Permit collection expressions in "immediately enumerated" contexts, without requiring a target type. ```cs foreach (bool b in [true, false]) { doMyLogicWith(b); } string[] items1 = ["a", "b", .. ["c"]]; ``` See also https://github.com/dotnet/csharplang/blob/main/meetings/working-groups/collection-literals/collection-expressions-inferred-type.md. ## Motivation [motivation]: #motivation Choosing a single "natural type" for collection expressions in the general case, e.g. `var coll = [1, 2, 3];`, has proven to be a difficult question and represents a major commitment. Should `coll` be a `List`, `ReadOnlySpan`, or something else? It's not obvious, and some of the possible answers represent a major engineering cost, and in any case a major statement about the defaults/preferences of the ecosystem. However, we do see a significant amount of demand for the ability to use collection expressions in contexts where the collection is immediately enumerated, and the collection value itself is not directly observable in user code. In this case, we should be able to define a solution which is convenient, optimal, and relatively low risk. ## Detailed design [design]: #detailed-design ### foreach Given a foreach statement of the form: ```cs foreach (iteration_type iteration_variable in collection) embedded_stmt ``` - When `collection` lacks a natural type, we determine if a *collection expression iteration conversion* exists, from `collection` to type `IEnumerable`, and apply the conversion if it exists. - `TElem` is determined in the following way: - If `iteration_type` is explicitly typed (i.e. not `var`), then `TElem` is `iteration_type`. - Otherwise, `TElem` is the *best common element type* of `collection`. - If the type of `TElem` can be determined, then the *collection expression iteration conversion* exists. Otherwise, the conversion does not exist. - As in [collection-expressions.md](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md), `IEnumerable` refers to `System.Collections.Generic.IEnumerable` throughout this specification. ### spreads Given a *spread element* of the form: ```cs .. collection ``` - When `collection` lacks a natural type, we determine if a *collection expression iteration conversion* exists, from `collection` to type `IEnumerable`, and apply the conversion if it exists. - `TElem` is determined in the following way: - If the collection-expression containing the spread element `.. collection` is subject to a *collection expression conversion* to a type with an *element type*, then `TElem` is that *element type*. - Otherwise, `TElem` is the *best common element type* of `collection`. - If the type of `TElem` can be determined, then the *collection expression iteration conversion* exists. Otherwise, the conversion does not exist. #### Remarks We intend for the following cases, which push element type information down from a target type to just work: - `foreach (string? x in [null]) { }` - `string?[] items = [.. [null]];` - `List items = [.. [null]];` When no target element type is available, such as when `foreach (var x ...` form is being used, or when the element type of the containing collection-expression of a spread is not known, then, the *best common element type* mechanism is used to propagate the nested element type information outward. ### Best common element type See also [collection-expressions.md#type-inference](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#type-inference). The *best common element type* of an expression `E` is determined similarly to the *best common type of a set of expressions* ([§12.6.3.16](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#126316-finding-the-best-common-type-of-a-set-of-expressions)): - A new *unfixed* type variable `X` is introduced. - An *output type inference* ([§12.6.3.8](expressions.md#12638-output-type-inferences)) is performed from `E` to `IEnumerable`. - `X` is *fixed* ([§12.6.3.13](expressions.md#126313-fixing)), if possible, and the resulting type is the best common element type. - Otherwise inference fails. For example, in the following statement, the element type of the collection being iterated, is same as the *best common type* of expressions `a, b, c`: ```cs foreach (var item in [a, b, c]) { } ``` Note that this feature is intentionally specified in such a way that an element type is determined similarly for the `foreach` collection above, as it is for a generic method call with an `IEnumerable` parameter: ```cs M([a, b, c]); void M(IEnumerable items) { } ``` ### Implementation flexibility Similar to [collection-expressions-in-foreach.md](https://github.com/dotnet/csharplang/blob/9d618b5eacaca9721550fb9a153a291087c10dae/proposals/collection-expressions-in-foreach.md), the implementation is encouraged to optimize based on the fact that user code can't observe the array which is created for a foreach-collection or spread-value under these new rules. So, it is free to use different strategies to allocate space for the collection elements such as an InlineArray on the stack, or not creating a collection instance at all and instead "inlining" the enumeration of elements. ### Future considerations This proposal doesn't get us 100% of the way there to "conditional element inclusion" scenarios like the following: ```cs // The spread value is erroneous even after this proposal string[] items2 = ["a", "b", .. includeRest ? ["c", "d"] : []]; ``` We are interested in pursuing type inference improvements which we expect to improve things across the board—for calls, foreach, and spreads—all in a similar way. ```cs // Make all of the following work using a future type inference improvement: M1(cond ? [1] : [2]); M2(cond ? [1] : [2]); foreach (var item in cond ? [1] : [2]) { } string[] array = ["a", "b", .. includeRest ? ["c", "d"] : []]; void M1(T[] items) { } void M2(IEnumerable items) { } ``` Permitting conditional element inclusion will also grow the optimization space, e.g.: ```cs List items = [a, b, .. includeMoreItems ? [c, d] : []]; // someday could emit as: List items = new List(capacity: 2 + 2); items.Add(a); items.Add(b); if (includeMoreItems) { items.Add(c); items.Add(d); } ``` ## Drawbacks [drawbacks]: #drawbacks Ensuring high-quality code generation in a wide variety of usage scenarios may be a significant amount of work. ## Alternatives [alternatives]: #alternatives Take the [collection-expressions-in-foreach.md](https://github.com/dotnet/csharplang/blob/98d6837c32e8d0ab25a29001267be5be206a0f19/proposals/collection-expressions-in-foreach.md) proposal instead, which provides support for the "base case" `foreach (var item in [1, 2, 3])` only, and doesn't provide support in spreads. ## Open questions [open]: #open-questions ### Output type inference for spreads The type inference rules (see [Method type argument inference](#method-type-argument-inference)) state the following regarding *output type inference*: > If `Eᵢ` is a *spread element*, no inference is made from `Eᵢ`. It looks like this was added back in https://github.com/dotnet/csharplang/pull/7604 andthe significance of this decision isn't 100% clear. It's possibly because we were only making inferences from types at that time. In this proposal, we adjust this so that an output type inference can be made from expression, in the case of `[.. [() => expr1, () => expr2]]`, for example. **Resolution:** Break out type inference changes into its own proposal, and pare this proposal down to only providing a target type for collections and spread values which lack natural type. ### Use of "special language-level collection type" for immediately enumerated collections Instead of `T[]`, we could choose to define the feature in terms of a new type kind defined at the language level. We would call it something like an *iteration type `T` with element type `Tₑ`. This could potentially make it easier to define things in such a way that `foreach (Span span in [span1, span2, span3]) { ... }` could work. However, we don't expect to have support for ref struct element type in the short term. Since ref structs don't work with so many things, that specific support requires careful design and possibly evolution of features like *ref fields* in order to permit types such as `ReadOnlySpan>`. **Resolution:** Use `IEnumerable` as target type for the scenarios in this proposal. We think that using a special new type kind would add additional spec/implementation cost without adding significant value. Use of `IEnumerable` permits ref structs as elements but not pointers. We'd like to investigate viability of actually generating code for the "collection of Spans" case, with caution, and understanding that the scenario may need to be blocked until separate, further language improvements are made. ### Optimization of immediately enumerated `new[] { }` expressions Since we are already discussing optimization of `foreach (var i in [1, 2, 3])`, to avoid realizing the `int[]`, we may wish to also permit the compiler to optimize `foreach (var i in new[] { 1, 2, 3 })`. **Resolution:** We don't think the value is worth the risk, because array literals have been around such a long time. If people want the nice new codegen, they should move to the new syntax form. ================================================ FILE: proposals/inactive/README.md ================================================ ================================================ FILE: proposals/inactive/list-patterns-enumerables.md ================================================ # List patterns on enumerables Champion issue: ## Summary Lets you to match an enumerable with a sequence of patterns e.g. `enumerable is { 1, 2, 3 }` will match a sequence of the length three with 1, 2, 3 as its elements. ## Detailed design The pattern syntax is unchanged: ```antlr primary_pattern : list_pattern | length_pattern | slice_pattern | // all of the pattern forms previously defined ; ``` ### Pattern compatibility A *length_pattern* is now also compatible with any type that is not *countable* but is *enumerable* — it can be used in `foreach`. A *list_pattern* is now also compatible with any type that is not *indexable* but is *enumerable*. A *slice_pattern* without a subpattern is compatible with any type that is compatible with a *list_pattern*. ``` enumerable is { 1, 2, .. } // okay enumerable is { 1, 2, ..var x } // error ``` ### Semantics If the input type is *enumerable* but not *countable*, then the *length_pattern* is checked on the number of elements obtained from enumerating the collection. If the input type is *enumerable* but not *indexable*, then the *list_pattern* enumerates elements from the collection and checks them against the listed patterns: Patterns at the start of the *list_pattern* — that are before the `..` *slice_pattern* if one is present, or all otherwise — are matched against the elements produced at the start of the enumeration. If the collection does not produce enough elements to get a value corresponding to a starting pattern, the match fails. So the *constant_pattern* `3` in `{ 1, 2, 3, .. }` doesn't match when the collection has fewer than 3 elements. Patterns at the end of the *list_pattern* (that are following the `..` *slice_pattern* if one is present) are matched against the elements produced at the end of the enumeration. If the collection does not produce enough elements to get values corresponding to the ending patterns, the *slice_pattern* does not match. So the *slice_pattern* in `{ 1, .., 3 }` doesn't match when the collection has fewer than 2 elements. A *list_pattern* without a *slice_pattern* only matches if the number of elements produced by complete enumeration and the number of patterns are equals. So `{ _, _, _ }` only matches when the collection produces exactly 3 elements. Note that those implicit checks for number of elements in the collection are unaffected by the collection type being *countable*. So `{ _, _, _ }` will not make use of `Length` or `Count` even if one is available. When multiple *list_patterns* are applied to one input value the collection will be enumerated once at most: ``` _ = collection switch { { 1 } => ..., { 2 } => ..., { .., 3 } => ..., }; _ = collectionContainer switch { { Collection: { 1 } } => ..., { Collection: { 2 } } => ..., { Collection: { .., 3 } } => ..., }; ``` It is possible that the collection will not be completely enumerated. For example, if one of the patterns in the *list_pattern* doesn't match or when there are no ending patterns in a *list_pattern* (e.g. `collection is { 1, 2, .. }`). If an enumerator is produced when a *list_pattern* is applied to an enumerable type and that enumerator is disposable it will be disposed when a top-level pattern containing the *list_pattern* successfully matches, or when none of the patterns match (in the case of a `switch` statement or expression). It is possible for an enumerator to be disposed more than once and the enumerator must ignore all calls to `Dispose` after the first one. ``` // any enumerator used to evaluate this switch statement is disposed at the indicated locations _ = collection switch { { 1 } => /* here */ ..., _ => /* here */ ..., }; /* here too, with a spilled try/finally around the switch expression */ ``` ### Lowering on enumerable type > **Open question**: Need to investigate how to reduce allocation for the end circular buffer. `stackalloc` is bad in loops. Maybe we'll just have to fall back to locals and a `switch`. (see [`params` feature discussion](https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params) also) Although a helper type is not necessary, it helps simplify and illustrate the logic. ``` class ListPatternHelper { // Notes: // We could inline this logic to avoid creating a new type and to handle the pattern-based enumeration scenarios. // We may only need one element in start buffer, or maybe none at all, if we can control the order of checks in the patterns DAG. // We could emit a count check for a non-terminal `..` and economize on count checks a bit. private EnumeratorType enumerator; private int count; private ElementType[] startBuffer; private ElementType[] endCircularBuffer; public ListPatternHelper(EnumerableType enumerable, int startPatternsCount, int endPatternsCount) { count = 0; enumerator = enumerable.GetEnumerator(); startBuffer = startPatternsCount == 0 ? null : new ElementType[startPatternsCount]; endCircularBuffer = endPatternsCount == 0 ? null : new ElementType[endPatternsCount]; } // targetIndex = -1 means we want to enumerate completely private int MoveNextIfNeeded(int targetIndex) { int startSize = startBuffer?.Length ?? 0; int endSize = endCircularBuffer?.Length ?? 0; Debug.Assert(targetIndex == -1 || (targetIndex >= 0 && targetIndex < startSize)); while ((targetIndex == -1 || count <= targetIndex) && enumerator.MoveNext()) { if (count < startSize) startBuffer[count] = enumerator.Current; if (endSize > 0) endCircularBuffer[count % endSize] = enumerator.Current; count++; } return count; } public bool Last() { return !enumerator.MoveNext(); } public int Count() { return MoveNextIfNeeded(-1); } // fulfills the role of `[index]` for start elements when enough elements are available public bool TryGetStartElement(int index, out ElementType value) { Debug.Assert(startBuffer is not null && index >= 0 && index < startBuffer.Length); MoveNextIfNeeded(index); if (count > index) { value = startBuffer[index]; return true; } value = default; return false; } // fulfills the role of `[^hatIndex]` for end elements when enough elements are available public ElementType GetEndElement(int hatIndex) { Debug.Assert(endCircularBuffer is not null && hatIndex > 0 && hatIndex <= endCircularBuffer.Length); int endSize = endCircularBuffer.Length; Debug.Assert(endSize > 0); return endCircularBuffer[(count - hatIndex) % endSize]; } } ``` `collection is [3]` is lowered to ``` @{ var helper = new ListPatternHelper(collection, 0, 0); helper.Count() == 3 } ``` `collection is { 0, 1 }` is lowered to ``` @{ var helper = new ListPatternHelper(collection, 2, 0); helper.TryGetStartElement(index: 0, out var element0) && element0 is 0 && helper.TryGetStartElement(1, out var element1) && element1 is 1 && helper.Last() } ``` `collection is { 0, 1, .. }` is lowered to ``` @{ var helper = new ListPatternHelper(collection, 2, 0); helper.TryGetStartElement(index: 0, out var element0) && element0 is 0 && helper.TryGetStartElement(1, out var element1) && element1 is 1 } ``` `collection is { .., 3, 4 }` is lowered to ``` @{ var helper = new ListPatternHelper(collection, 0, 2); helper.Count() >= 2 && // `..` with 2 ending patterns helper.GetEndElement(hatIndex: 2) is 3 && // [^2] is 3 helper.GetEndElement(1) is 4 // [^1] is 4 } ``` `collection is { 1, 2, .., 3, 4 }` is lowered to ``` @{ var helper = new ListPatternHelper(collection, 2, 2); helper.TryGetStartElement(index: 0, out var element0) && element0 is 1 && helper.TryGetStartElement(1, out var element1) && element1 is 2 && helper.Count() >= 4 && // `..` with 2 starting patterns and 2 ending patterns helper.GetEndElement(hatIndex: 2) is 3 && helper.GetEndElement(1) is 4 } ``` The same way that a `Type { name: pattern }` *property_pattern* checks that the input has the expected type and isn't null before using that as receiver for the property checks, so can we have the `{ ..., ... }` *list_pattern* initialize a helper and use that as the pseudo-receiver for element accesses. This should allow merging branches of the patterns DAG, thus avoiding creating multiple enumerators. Note: async enumerables are out-of-scope for C# 10. (Confirmed in LDM 4/12/2021) Note: sub-patterns are disallowed in slice-patterns on enumerables for now despite some desirable uses: `e is { 1, 2, ..[var count] }` (LDM 4/12/2021) ## Unresolved questions 1. Should we limit the list-pattern to `IEnumerable` types? Then we could allow `{ 1, 2, ..var x }` (`x` would be an `IEnumerable` we would cook up) (answer [LDM 4/12/2021]: no, we'll disallow sub-pattern in slice pattern on enumerable for now) 2. Should we try and optimize list-patterns like `{ 1, _, _ }` on a countable enumerable type? We could just check the first enumerated element then check `Length`/`Count`. Can we assume that `Count` agrees with enumerated count? 3. Should we try to cut the enumeration short for length-patterns on enumerables in some cases? (computing min/max acceptable count and checking partial count against that) What if the enumerable type has some sort of `TryGetNonEnumeratedCount` API? 4. Can we detect at runtime that the input type is sliceable, so as to avoid enumeration? .NET 6 may be adding some LINQ methods/extensions that would help. ================================================ FILE: proposals/inactive/pointer-null-coalescing.md ================================================ # Extend null-coalescing (??) and null coalescing assignment (??=) operators to pointers * [x] Proposed * [ ] Prototype: Not Started * [ ] Implementation: Not Started * [ ] Specification: In Progress ## Summary [summary]: #summary This proposal extends support of a commonly used feature in C# (?? and ??=) to unsafe code and pointer types specifcally. ## Motivation [motivation]: #motivation There is no reason for pointer types to be excluded from this feature as they semantically fit the feature's use case. Supporting this extends the feature's scope to what one would expect it to logically support. This concept is already supported in ternary expressions EG: `T *foo = bar != null ? bar : baz;` So the same syntactic options that are offered to non-pointer types should be extended to pointer types. ## Detailed design [design]: #detailed-design For this addition to the language, no grammar changes are required. We are merely adding supported types to an existing operator. The current conversion rules will then determine the resulting type of the ?? expression. Other rules will remain the same with the exception of the relaxed type rules which will need to be modified. The C# spec will need to be updated to reflect this addition with the change of the line. > A null coalescing expression of the form `a` ?? `b` requires `a` to be of a nullable type or reference type. to > A null coalescing expression of the form `a` ?? `b` requires `a` to be of a nullable type, reference type or pointer type. as well as > If `A` exists and is not a nullable type or a reference type, a compile-time error occurs. to > If `A` exists and is not a nullable type, reference type or pointer type, a compile-time error occurs. Currently the rules for `??=` do not prohibit pointer types; however, the operator was not implemented like that. ## Drawbacks [drawbacks]: #drawbacks Unsafe code can be considered confusing and a vector for bugs that would not happen otherwise. Increasing the available syntactic options for pointer types could possibly lead to bugs where the developer was unaware of the semantic meaning of what was written. ## Alternatives [alternatives]: #alternatives No other designs have been considered as this is not a new feature, but it is rather an extension of an already implemented feature. ## Unresolved questions [unresolved]: #unresolved-questions A possible question is if support should be extended to implicit dereferencing in the same vein as implicit conversions for `Nullable` are supported. EG: int* foo = null; int bar = foo ?? 3; ## Design meetings https://github.com/dotnet/csharplang/issues/418 ================================================ FILE: proposals/inactive/repeated-attributes.md ================================================ # Repeated Attributes in Partial Members Champion issue: ## Summary Allow each declaration of a partial member to independently apply an attribute not marked with `[AttributeUsage(AllowMultiple = true)]`, as long as the attribute arguments are identical in all applications. ## Motivation When considering what attributes are present on a 'partial' method, the language unions together all the attributes in all corresponding positions on both declarations. For example, the method `M` below has attributes `A` and `B`. ```cs [A] partial void M(); [B] partial void M() { } ``` This means that attributes which are not marked `[AttributeUsage(AllowMultiple = true)]` cannot be present across both parts: ```cs [A] partial void M(); [A] // error: duplicate attribute! partial void M() { } ``` This presents a usability/readability issue, because some attributes are designed to inform the user and/or maintainer of the method of what pre/postconditions or invariants the method requires. For example: ```cs public partial bool TryGetValue([NotNullWhen(true)] out object? value); public partial bool TryGetValue(out object? value) { ... } ``` A partial member typically facilitates the relationship between a code generator and an end user--each party provides one of the declarations of the partial member in order for a code generator to provide functionality to the user, or for the user to access an extension point in generated code. In the situation where only one declaration is allowed to have these single-application attributes, the generator and the user can't effectively communicate their requirements to each other. If a generator produces a defining declaration with a `NotNullWhen` attribute, for instance, the user cannot write an implementing declaration with that same attribute, even though the postcondition is applicable to the implementation, and checked by the compiler. This creates confusion for users when tracking down the root causes of warnings or when trying to understand the behaviors of a method. ## Solution Allow a non-AllowMultiple attribute to be used once on each symbol (member, return, parameter, etc.) in each partial declaration, as long as the attribute arguments are identical. Since attribute arguments are all constants, the compiler can verify this. When attributes are unioned across declarations, each non-AllowMultiple attribute will be de-duplicated and only one instance of the attribute will be emitted. ```cs public partial bool TryGetValue([NotNullWhen(true)] out object? value); public partial bool TryGetValue([NotNullWhen(true)] out object? value) { ... } // ok // equivalent to: public bool TryGetValue([NotNullWhen(true)] out object value) { ... } // error when attribute arguments do not match public partial bool TryGetValue([NotNullWhen(true)] out object? value); public partial bool TryGetValue([NotNullWhen(false)] out object? value) { ... } // error ``` ### Open questions 1. Should such repetition of attributes be permitted on 'partial' type declarations or only on non-type members (e.g. methods)? 2. Should attributes which *do* allow multiple usages on a symbol be permitted to "opt in" to de-duplication of equivalent usages of an attribute? ### Design meetings #### [6th July 2020](../meetings/2020/LDM-2020-07-06.md#repeated-attributes-on-partial-members) The proposal is accepted. - Repetition of non-AllowMultiple attributes will be permitted across partial type declarations (open question 1). - Repeated application of AllowMultiple attributes will not change in behavior, and an "opt in" mechanism for de-duplication may be considered in a future proposal (open question 2). ================================================ FILE: proposals/inference-for-constructor-calls.md ================================================ # Target-typed inference for constructor calls *This proposal builds on the [target-typed generic type inference](https://github.com/dotnet/csharplang/blob/main/proposals/target-typed-generic-type-inference.md) proposal. ## Summary Generic type inference is extended to 'new' expressions, which may infer type arguments for the newly created class or struct, including [from a target type](target-typed-generic-type-inference) if present. For instance, given: ```csharp public class MyCollection : IEnumerable { public MyCollection() { ... } ... } ``` We would allow the constructor to be called without a type argument when it can be inferred from arguments or (in this case) a target type: ```csharp IEnumerable c = new MyCollection(); // 'T' = 'string' inferred from target type ``` ## Motivation Even without [target-typed generic type inference](https://github.com/dotnet/csharplang/blob/main/proposals/target-typed-generic-type-inference.md) constructors are at a disadvantage to factory methods, because type arguments cannot be inferred on invocation. This frequently leads to trivial factory methods being declared simply for the benefit of type inference. If target-typed inference is added, this convenience gap is only going to grow. For constructor calls we already have "target type new" for when a target type is exactly the type to be created, but even when that's not the case, arguments or the target type often have sufficient information between them that the type arguments for the constructed type could be inferred. This is frequently the case for [closed hierarchies](https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md) and [unions](https://github.com/dotnet/csharplang/blob/main/proposals/nominal-type-unions.md). Most commonly, a target type will be the closed class or union type itself, whereas the constructed type will be one of the case types: ```csharp Option option = new Some("Hello"); ``` With this proposal, it could be written simply as ```csharp Option option = new Some("Hello"); // Infer 'string' from argument and target type ``` ## Detailed specification The proposal is specified by treating the constructor "as if" it were a generic method, and the constructor call "as if" it were an invocation of that generic method. In an *object_creation_expression* of the form `new T(E₁ ...Eₓ)` where `T` has no type argument list, if there is an accessible non-generic type `T` and it has one or more accessible and applicable constructors, then overload resolution proceeds as today. However, if no such type or constructors are found, then for each generic type `T` with the same type name `T`, and for each constructor of that generic type with the parameter list `(T₁ p₁ ... Tₓ pₓ)`, [generic type inference](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1263-type-inference) is attempted, as if the constructor were a generic method `M` with the same type parameter list as `T`, with the same parameter list `(T₁ p₁ ... Tₓ pₓ)` as the constructor, and with T as its return type: ```csharp T M(T₁ p₁ ... Tₓ pₓ) ``` and as if the object creation expression were an invocation with no type arguments, and with the same argument list and target type `I` (if any) as the creation expression: ```csharp M(E₁ ...Eₓ) // without target type I i = M(E₁ ...Eₓ) // with target type ``` (Where the names `i` and `M` are otherwise invisible and not in conflict with any other names in scope.) For instance, for this type and invocation: ```csharp public class C : IEnumerable { public C(T2 t2) { ... } } IEnumerable l = new C(5); ``` Type inference would proceed as if with this method and invocation: ```csharp C M(T2 t2); IEnumerable l = M(5); ``` If type inference succeeds, and the inferred type arguments satisfy their constraints, and the constructor is applicable when the type arguments are applied to its containing generic type, then the constructor is a candidate. Overload resolution then proceeds as normal between the resulting candidate constructors. ================================================ FILE: proposals/inference-for-type-patterns.md ================================================ # Target-typed inference for type patterns Champion issue: https://github.com/dotnet/csharplang/issues/9630 *This proposal builds on the [target-typed generic type inference](https://github.com/dotnet/csharplang/blob/main/proposals/target-typed-generic-type-inference.md) proposal. ## Summary Generic type inference is extended to type patterns, which may omit a type argument list when it can be inferred from the pattern input value. For instance, given a declaration `Option intOption`, instead of: ```csharp if (intOption is Some some) ... ``` You can simply write: ```csharp if (intOption is Some some) ... // 'Some' inferred from the type of 'intOption' ``` ## Motivation Type patterns can get unwieldy when the types are generic, which seems especially grating when the information to infer the type arguments is already available in context. For [closed hierarchies](https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md) and [unions](https://github.com/dotnet/csharplang/blob/main/proposals/nominal-type-unions.md) in particular, there are already rules in place that ensure that type arguments for a case type depend functionally on those of the closed class or union. This means that those type arguments are almost *guaranteed* to be inferrable when the input value is of a closed class or union type, and the type pattern is for a case type. ## Detailed specification The proposal is specified by treating the type pattern "as if" it were a generic method, and the pattern application to the incoming value "as if" it were an invocation of that generic method with a target type of the incoming value. In a type pattern `T ...` where `T` has no type arguments, if a non-generic `T` does not exist, or is not allowed in a pattern (e.g. it is static), or is not [pattern compatible](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/patterns.md#1122-declaration-pattern) with the input type `I`, then type inference is attempted: For each generic type `T` with the same type name `T`, [generic type inference](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#1263-type-inference) is performed as if the type pattern were a generic method with the same type parameter list as `T`, with an empty parameter list, and with T as its return type: ```csharp T M() ``` and as if the pattern application were an invocation with no type arguments, with an empty argument list and with `I` as a target type: ```csharp I i = M() ``` (Where the names `i` and `M` are otherwise invisible and not in conflict with any other names in scope.) For instance, in the following example: ```csharp public record None(); public record Some(T value); public union Option(None, Some); void M(Option intOption) => intOption switch { None => ..., Some some => ..., // 'Some' inferred for 'some' } ``` Type inference proceeds as if with this method and invocation: ```csharp Some M(); Option i = M(); ``` Which leads to `int` being inferred as the type argument corresponding to `T`. If type inference succeeds and the inferred type arguments satisfy their constraints, then the type `T` is a candidate. If exactly one such candidate type is found, then that is the one inferred for use in the type pattern. Otherwise, inference fails and an error occurs. The type in the pattern must then be specified in full. ================================================ FILE: proposals/interpolated-string-handler-argument-value.md ================================================ # Interpolated string handler argument value Champion issue: ## Summary [summary]: #summary In order to solve a pain point in the creation of handler types and make them more useful in logging scenarios, we add support for interpolated string handlers to receive a new piece of information, a custom value supplied at the call site. ```cs public void LogDebug( this ILogger logger, [InterpolatedStringHandlerArgument(nameof(logger))] [InterpolatedStringHandlerArgumentValue(LogLevel.Debug)] LogInterpolatedStringHandler message); ``` ## Motivation [motivation]: #motivation C# 10 introduced [interpolated string handlers][interpolated-string-spec], which were intended to allow interpolated strings to be used in high-performance and logging scenarios, using more efficient building techniques and avoiding work entirely when the string does not need to be realized. However, a common pain point has arisen since then; for logging APIs, you will often want to have APIs such as `LogTrace`, `LogDebug`, `LogWarn`, etc, for each of your logging levels. Today, there is no way to use a single handler type for all of those methods. Instead, our guidance has been to prefer a single `Log` method that takes a `LogLevel` or similar enum, and use `InterpolatedStringHandlerArgumentAttribute` to pass that value along. While this works for new APIs, the simple truth is that we have many existing APIs that use the `LogTrace/Debug/Warn/etc` format instead. These APIs either must introduce new handler types for each of the existing methods, which is a lot of overhead and code duplication, or let the calls be inefficient. We want to allow a custom value to be passed along to the interpolated string handler type. The value would be specific to a particular method that uses interpolated string handler parameter. This would then permit parameterization based on the value, eliminating a large amount of duplication and making it viable to adopt interpolation handlers for `ILogger` and similar scenarios. Some examples of this: * [fedavorich/ISLE][isle] uses T4 to get around the bloat, by generating handlers for every log level. * [This BCL proposal][ilogger-proposal] was immediately abandoned after it was realized that there would need to be a handler type for every log level. ## Detailed design [design]: #detailed-design The compiler will recognizes the `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentValueAttribute`: ```cs namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] public sealed class InterpolatedStringHandlerArgumentValueAttribute : Attribute { public InterpolatedStringHandlerArgumentValueAttribute(object? value) { Value = value; } public object? Value { get; } } } ``` This attribute is used on parameters, to inform the compiler how to lower an interpolated string handler pattern used in a parameter position. The attribute can be used on its own or in combination with `InterpolatedStringHandlerArgument` attribute. Arrays are disallowed as argument values for the attribute in order to preserve design space. We make one small change to how interpolated string handlers perform [constructor resolution][constructor-resolution]. The change is bolded below: >2. The argument list `A` is constructed as follows: > 1. The first two arguments are integer constants, representing the literal length of `i`, and the number of _interpolation_ components in `i`, respectively. > 2. If `i` is used as an argument to some parameter `pi` in method `M1`, and parameter `pi` is attributed with `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`, > then for every name `Argx` in the `Arguments` array of that attribute the compiler matches it to a parameter `px` that has the same name. The empty string is matched to the receiver > of `M1`. > * If any `Argx` is not able to be matched to a parameter of `M1`, or an `Argx` requests the receiver of `M1` and `M1` is a static method, an error is produced and no further > steps are taken. > * Otherwise, the type of every resolved `px` is added to the argument list, in the order specified by the `Arguments` array. Each `px` is passed with the same `ref` semantics as is specified in `M1`. > 3. **If `i` is used as an argument to some parameter `pi` in method `M1`, and parameter `pi` is attributed with `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentValueAttribute`, > the attribute value is added to the argument list.** > 4. The final argument is a `bool`, passed as an `out` parameter. >3. Traditional method invocation resolution is performed with method group `M` and argument list `A`. For the purposes of method invocation final validation, the context of `M` is treated >as a _member\_access_ through type `T`. > * If a single-best constructor `F` was found, the result of overload resolution is `F`. > * If no applicable constructors were found, step 3 is retried, removing the final `bool` parameter from `A`. If this retry also finds no applicable members, an error is produced and > no further steps are taken. > * If no single-best method was found, the result of overload resolution is ambiguous, an error is produced, and no further steps are taken. ### Example ```cs // Original code var someOperation = RunOperation(); ILogger logger = CreateLogger(LogLevel.Error, ...); logger.LogWarn($"Operation was null: {operation is null}"); // Approximate translated code: var someOperation = RunOperation(); ILogger logger = CreateLogger(LogLevel.Error, ...); var loggingInterpolatedStringHandler = new LoggingInterpolatedStringHandler(20, 1, logger, LogLevel.Warn, out bool continueBuilding); if (continueBuilding) { loggingInterpolatedStringHandler.AppendLiteral("Operation was null: "); loggingInterpolatedStringHandler.AppendFormatted(operation is null); } LoggingExtensions.LogWarn(logger, loggingInterpolatedStringHandler); // Helper libraries namespace Microsoft.Extensions.Logging; { using System.Runtime.CompilerServices; [InterpolatedStringHandler] public struct LoggingInterpolatedStringHandler { public LoggingInterpolatedStringHandler(int literalLength, int formattedCount, ILogger logger, LogLevel logLevel, out bool continueBuilding) { if (logLevel < logger.LogLevel) { continueBuilding = false; } else { continueBuilding = true; // Set up the rest of the builder } } } public static class LoggerExtensions { public static void LogWarn( this ILogger logger, [InterpolatedStringHandlerArgument(nameof(logger))] [InterpolatedStringHandlerArgumentValue(LogLevel.Warn)] ref LogInterpolatedStringHandler message); } } ``` ## Drawbacks [drawbacks]: #drawbacks The extra attribute and an additional compiler complexity. ## Alternatives [alternatives]: #alternatives https://github.com/dotnet/csharplang/blob/main/proposals/interpolated-string-handler-method-names.md ## Open questions [open]: #open-questions None [interpolated-string-spec]: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md [isle]: https://github.com/fedarovich/isle/blob/main/src/Isle/Isle.Extensions.Logging/LoggerExtensions.tt [ilogger-proposal]: https://github.com/dotnet/runtime/issues/111283 [constructor-resolution]: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#constructor-resolution ## Design meetings - [LDM-2025-04-07](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-07.md#interpolated-string-handler-argument-values) ================================================ FILE: proposals/iterators-in-lambdas.md ================================================ # Iterators in lambdas Champion issue: https://github.com/dotnet/csharplang/issues/9467 ## Summary This proposal will remove the restriction that lambda expressions cannot be iterators. ## Motivation Over the years the idea of allowing lambdas to be iterators has come up several times. The proposal has generally [garnered lack luster support][iterator-meeting]. The conclusion is that it's not harmful to the language but also not a high priority. However, the [use of async streams][iterator-discussion-async-streams] in minimal API programs have made this proposal signifantly more relevant. `IAsyncEnumerator` is now used heavily in streaming APIs, including AI related services. This means that the ability to create such APIs cannot be done with the existing style of top level statements. Developers must refactor their code out to a local method once they want to use async streaming. ```cs // Desired code app.MapGet("/search", async IAsyncEnumerable( string query, VectorStoreCollection collection) => { await foreach (var result in collection.SearchAsync(query, top: 5, new() { Filter = r => r.TenantId == 8 })) { yield return result.Record; } } // Actual code app.MapGet("/search", MapForSearch); async IAsyncEnumerable MapForSearch(string query, VectorStoreCollection collection) { await foreach (var result in collection.SearchAsync(query, top: 5, new() { Filter = r => r.TenantId == 8 })) { yield return result.Record; } } ``` This motivation is simalar to the motivation for [lambdas to have optional parameters][lambda-optional-parameters]. The inability to have optional parameters in lambdas forced minimal API programs to unnecessarily fall back to local methods. ## Detailed Design This will allow lambdas that have a return type that is a recognized iterator type and contains a `yield` statement to be considered an iterator. Such lambdas will have all of the functionality and restrictions of method based iterators: - The `yield type` will be determined from the iterator type. - The iterator cannot have any `in / ref / out` parameters. - The iterator can be `async` or synchronous. - etc ... The return type can be explicit or inferred. For example both of the following are valid iterator lambdas: ```cs var lambda1 = () => { yield return 1; yield return 2; }; Func> lambda2 = () => { yield return 1; yield return 2; }; ``` ## Miscelaneous ## Open Issues ### Iterator and Yield Type Inference This proposal needs to dig into our inference rules and how it interacts with existing passes like return type inference. [iterator-meeting]: meetings\2018\LDM-2018-05-21.md [iterator-discussion-async-streams]: https://github.com/dotnet/csharplang/discussions/9393 [lambda-optional-parameters]: https://github.com/dotnet/csharplang/issues/6051 ================================================ FILE: proposals/labeled-break-continue.md ================================================ # Labeled `break` and `continue` Statements * Championed issue: https://github.com/dotnet/csharplang/issues/9875 ## Summary Allow `break` and `continue` statements to optionally specify a label that identifies which loop or `switch` statement to target, enabling cleaner control flow in nested constructs without requiring `goto` statements, or other contortions like nested functions, tuple returns, etc. ## Motivation When working with nested loops or loops containing `switch` statements, developers often need to break out of or continue an outer loop from within an inner context. Currently, there are two primary approaches to achieve this, both with significant drawbacks: ### Using `goto` statements ```csharp string foundValue = null; for (int x = 0; x < xMax; x++) { for (int y = 0; y < yMax; y++) { foundValue = GetValue(x, y); if (foundValue == target) goto FOUND; } } FOUND: ProcessValue(foundValue); ``` While `goto` works, it requires placing labels after the loop construct and doesn't clearly communicate the intent to break from a specific loop. For continuing an outer loop, the approach becomes even more awkward: ```csharp for (int x = 0; x < xMax; x++) { for (int y = 0; y < yMax; y++) { if (ShouldSkipRest(x, y)) goto CONTINUE_OUTER; } CONTINUE_OUTER: ; } ``` This pattern is confusing because the label must be placed at the end of the loop body, just before the closing brace, so that the incrementor and condition checking happen. When both `break` and `continue` are needed for the same outer loop, two separate labels are required: ```csharp for (int x = 0; x < xMax; x++) { for (int y = 0; y < yMax; y++) { if (ShouldSkipRest(x, y)) goto CONTINUE_OUTER; if (ShouldExitAll(x, y)) goto BREAK_OUTER; } CONTINUE_OUTER: ; } BREAK_OUTER: // Subsequent statements ``` ### Using flag variables ```csharp string foundValue = null; bool shouldBreak = false; for (int x = 0; x < xMax; x++) { for (int y = 0; y < yMax; y++) { foundValue = GetValue(x, y); if (foundValue == target) { shouldBreak = true; break; } } if (shouldBreak) break; } ProcessValue(foundValue); ``` This approach requires additional state management, increases code verbosity, and obscures the control flow intent. ### Proposed solution With labeled `break` and `continue`, the code becomes clearer and more maintainable: ```csharp string foundValue = null; outer: for (int x = 0; x < xMax; x++) { for (int y = 0; y < yMax; y++) { foundValue = GetValue(x, y); if (foundValue == target) break outer; } } ProcessValue(foundValue); ``` The label is placed directly on the loop it identifies, and the break/continue statement explicitly names its target. For continuing: ```csharp outer: for (int x = 0; x < xMax; x++) { for (int y = 0; y < yMax; y++) { if (ShouldSkipRest(x, y)) continue outer; } } ``` This naturally expresses "continue the outer loop," without the confusion of label placement associated with `goto`. A single label can be used for both operations: ```csharp outer: for (int x = 0; x < xMax; x++) { for (int y = 0; y < yMax; y++) { if (ShouldSkipRest(x, y)) continue outer; if (ShouldExitAll(x, y)) break outer; } } ``` This feature has been requested extensively in the C# community, with discussions dating back decades and the topic being reintroduced and rerequested continuously. Similar features exist in several other modern languages: - **Java**: [Branching Statements (Oracle Tutorial)](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html) - **JavaScript**: [Labeled Statement (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label) - **Kotlin**: [Returns and Jumps](https://kotlinlang.org/docs/returns.html) - **Swift**: [Control Flow - Labeled Statements](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/controlflow/#Labeled-Statements) - **Rust**: [Loop Labels](https://doc.rust-lang.org/reference/expressions/loop-expr.html#loop-labels) - **Go**: [Labeled statements](https://go.dev/ref/spec#Labeled_statements) - **Zig**: [Labelled loops](https://zig.guide/language-basics/labelled-loops/) - **Dart**: [Loops](https://dart.dev/language/loops) In all these cases, the languages operate in the saem way as in this specification. Namely, some constructs can have a label, and it is possible to reference that label from their respective `continue` or `break` statements. ## Detailed design ### Grammar changes The grammar for `break` and `continue` statements is extended to allow an optional identifier: ```diff break_statement - : 'break' ';' + : 'break' identifier? ';' ; continue_statement - : 'continue' ';' + : 'continue' identifier? ';' ; ``` ### Semantic rules #### Label requirements When a `break` or `continue` statement includes an identifier: 1. The identifier must refer to a label on a labeled statement that lexically contains the `break` or `continue` statement. 2. For `break` statements, the labeled statement must be one of: - An iteration statement (`for`, `foreach`, `while`, or `do` statement) - A `switch` statement 3. For `continue` statements, the labeled statement must be an iteration statement (`for`, `foreach`, `while`, or `do` statement). 4. It is a compile-time error if the identifier does not refer to a label in scope. 5. It is a compile-time error if the label refers to a statement that does not meet the requirements above. #### Updated `break` statement semantics The existing semantics of the `break` statement are updated as follows: ```diff A break statement exits the nearest enclosing switch statement, -while statement, do statement, for statement, or foreach statement. +while statement, do statement, for statement, or foreach statement, +or, if an identifier is specified, the labeled statement identified +by that label. + +When an identifier is specified, the labeled statement must be a +switch statement or iteration statement that lexically contains the +break statement. The target of a break statement is the end point of the nearest -enclosing switch statement, while statement, do statement, for statement, -or foreach statement. +enclosing construct (as determined above). ``` #### Updated `continue` statement semantics The existing semantics of the `continue` statement are updated as follows: ```diff A continue statement starts a new iteration of the nearest enclosing -while statement, do statement, for statement, or foreach statement. +while statement, do statement, for statement, or foreach statement, +or, if an identifier is specified, the labeled iteration statement +identified by that label. + +When an identifier is specified, the labeled statement must be an +iteration statement that lexically contains the continue statement. The target of a continue statement is the end point of the embedded -statement of the nearest enclosing while statement, do statement, for -statement, or foreach statement. +statement of the enclosing construct (as determined above). ``` ### Behavior A `break` statement with a label behaves exactly as if it were an unlabeled `break` statement directly within the labeled construct. Similarly, a `continue` statement with a label behaves as if it were an unlabeled `continue` directly within the labeled iteration statement. For example, these two code fragments are semantically equivalent: ```csharp // With labeled break outer: for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (i * j > 20) break outer; } } ``` ```csharp // Equivalent using goto for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (i * j > 20) goto END_OUTER; } } END_OUTER: ; ``` And for `continue`: ```csharp // With labeled continue outer: for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (ShouldSkip(i, j)) continue outer; } } ``` ```csharp // Equivalent using goto for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (ShouldSkip(i, j)) goto CONTINUE_OUTER; } CONTINUE_OUTER: ; } ``` ## Drawbacks/Alternatives ### Keep using `goto` statements C# already supports `goto`, which can accomplish the same control flow. However, `goto` has several disadvantages compared to labeled break/continue: - Requires separate labels for break vs. continue scenarios (break labels go after the loop, continue labels go before the closing brace) - Label placement is less intuitive and differs based on whether you're breaking or continuing - Less explicit about intent (jumping to a location vs. breaking/continuing a specific loop) - Brittle and error-prone: developers must ensure no statements are accidentally placed between labels and their target constructs. For example, with `goto END_LOOP;` followed by `END_LOOP:`, it's easy to inadvertently insert a statement between them during maintenance, breaking the intended control flow. Labeled loops prevent this issue by binding the label directly to the construct. - Carries historical stigma that labeled break/continue avoids ### Use flag variables As shown in the motivation section, flag variables work but add significant boilerplate and obscure the control flow logic. ### Use `break N` or `continue N` with numeric levels - Fragile during refactoring (adding/removing a loop level requires updating all numeric references) - Harder to read (must count levels to understand the target) - Less explicit than named labels - Lack of clarity (1-based? 0-based?) ### Refactor into separate methods While this is often good practice, it's not always feasible or appropriate, and sometimes introduces unnecessary complexity for what should be simple control flow. ## Related discussions and issues This proposal consolidates and addresses the following community discussions:
- [Discussion #6634: C# Break nested loop](https://github.com/dotnet/csharplang/discussions/6634) - [Issue #869: Discussion: C# Break nested loop](https://github.com/dotnet/csharplang/issues/869) - [Discussion #5525: [Proposal] Labeled loops like in Java](https://github.com/dotnet/csharplang/discussions/5525) - [Issue #1597: [Proposal] Labeled loops like in Java](https://github.com/dotnet/csharplang/issues/1597) - [Discussion #5521: nested loops interruption with break X, continue X](https://github.com/dotnet/csharplang/discussions/5521) - [Issue #4109: [Proposal]: Syntactic sugar for breaking out of or continuing nested loops](https://github.com/dotnet/csharplang/issues/4109) - [Issue #3511: [Proposal] "doublecontine", to contine outer loop](https://github.com/dotnet/csharplang/issues/3511) - [Issue #2024: break and continue inhancements](https://github.com/dotnet/csharplang/issues/2024) - [Discussion #8434: Chained Control Flow Statements: break [, break]... [,continue]](https://github.com/dotnet/csharplang/discussions/8434)
## Design meetings TBD ================================================ FILE: proposals/left-right-join-in-query-expressions.md ================================================ # Introduce `left` and `right` modifiers to the `join` query expression clauses Champion issue: ## Summary [summary]: #summary Introduce `left` and `right` modifiers to the LINQ query expression syntax `join` clause, e.g.: ```c# from student in students left join department in departments on student.DepartmentID equals department.ID select new { student.Name, department?.Name } ``` ## Motivation [motivation]: #motivation LINQ has a [Join operator](https://learn.microsoft.com/dotnet/api/system.linq.enumerable.join), which, like its SQL INNER JOIN counterpart, correlates elements of two sequences based on matching keys; the C# language has a corresponding `join` clause which translates to this operator ([section 12.20.3.5 of the C# specs](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#122035-from-let-where-join-and-orderby-clauses)). In addition to INNER JOIN, SQL also has LEFT JOIN, which returns outer elements even if there's no corresponding inner ones; LINQ and C#, in contrast, lack this operator. [The LINQ conceptual documentation shows how to combine existing operators to achieve a left join](https://learn.microsoft.com/en-us/dotnet/csharp/linq/standard-query-operators/join-operations#perform-left-outer-joins): ```c# var query = from student in students join department in departments on student.DepartmentID equals department.ID into gj from subgroup in gj.DefaultIfEmpty() select new { student.FirstName, student.LastName, Department = subgroup?.Name ?? string.Empty }; ``` Or using method syntax: ```c# var query = students .GroupJoin(departments, student => student.DepartmentID, department => department.ID, (student, departmentList) => new { student, subgroup = departmentList }) .SelectMany( joinedSet => joinedSet.subgroup.DefaultIfEmpty(), (student, department) => new { student.student.FirstName, student.student.LastName, Department = department.Name }); ``` Although functionality sufficient for expressing a left join operation, this combining approach has the following drawbacks: - It's complicated, requiring combining multiple different LINQ operators in a specific way to form a complex construct, and is easy to accidentally get wrong. Many EF users have complained about the complexity of this construct for expressing a simple SQL LEFT JOIN. - It is inefficient - the combination of operators adds significant overhead compared to a single operator using an internal lookup table (or "hash join", as Join is implemented). In .NET 10, new `LeftJoin()` and `RightJoin()` methods have been introduced into System.LINQ; see https://github.com/dotnet/runtime/issues/110292 for the API proposal, discussion, and performance information and benchmarks. Following are the relevant API signatures and examples: ```c# // API signatures: public static IEnumerable Join( this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector); public static IEnumerable LeftJoin( this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector); public static IEnumerable RightJoin( this IEnumerable outer, IEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector); // Usage examples: // Existing (inner) join operator: students are only returned if correlated departments are found var query = students.Join( departments, student => s.DepartmentID, department => department.ID, (student, department) => new { student.FirstName, student.LastName, Department = department.Name }); // New left (outer) join operator: all students are returned, even those without any correlated department // Departments without a correlated student are not returned. var query = students.LeftJoin( departments, student => s.DepartmentID, department => department.ID, (student, department) => new { student.FirstName, student.LastName, Department = department?.Name }); // New right (outer) join operator: all departments are returned, even those without any correlated student. // Students without a correlated department are not returned. var query = students.RightJoin( departments, student => s.DepartmentID, department => department.ID, (student, department) => new { student?.FirstName, student?.LastName, Department = department.Name }); ``` Aside from allowing users to more easily express SQL LEFT/RIGHT JOIN when using a LINQ provider (such as EF Core), the new APIs also allow both easier and more efficient in-memory left/right joins, exactly in the way that inner joins are already supported. Along with the introduction of the operators into System.Linq, the C# query expression syntax can be extended with new `left` and `right` modifiers for the `join` clauses, which would translate to these new methods, allowing for simpler and more efficient code where C# query syntax is used. ## Detailed design [design]: #detailed-design ### Grammar Proposed grammar change ([§11.7.1](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1117-query-expressions)): ```diff join_clause - : 'join' type? identifier 'in' expression 'on' expression 'equals' expression + : ('left' | 'right')? 'join' type? identifier 'in' expression 'on' expression 'equals' expression ; ``` The `join` clause is thus extended via new, optional `left` and `right` modifiers; the clause stays otherwise the same, with the modifiers only changing which LINQ method is translated to (see below): ```c# from student in students left join department in departments on student.DepartmentID equals department.ID select new { student.Name, department?.Name } ``` ### `join` clause specification This proposes removing the `join` clause from the [From, let, where, join and orderby clauses (§11.7.1)](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1117-query-expressions), and adding the following dedicated section for the `join` clause, including the new proposed modifiers.
Removal of `join` clause specs from [§11.7.1](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#1117-query-expressions) A `join` clause immediately followed by a `select` clause ```csharp from «x1» in «e1» join «x2» in «e2» on «k1» equals «k2» select «v» ``` ... remainder of existing join specification up through ... > the final translation of which is > > ```csharp > customers > .GroupJoin( > orders, > c => c.CustomerID, > o => o.CustomerID, > (c, co) => new { c, co }) > .Select(x => new { x, n = x.co.Count() }) > .Where(y => y.n >= 10) > .Select(y => new { y.x.c.Name, OrderCount = y.n }) > ``` > > where `x` and `y` are compiler generated identifiers that are otherwise invisible and inaccessible. > > *end example*
Following is the new proposed dedicated section for the `join` clauses. This section would be numbered as 11.17.3.6 (pushing down the existing sections), or appended at the end. Note that although the `join into` clause is moved into the new section, it is otherwise completely unaffected by this proposal; `join into` translates to `GroupJoin()`, which already performs a conceptual left join (where an outer element has no correlated inner element, it is returned with an empty inner group). #### 11.17.3.6 Join clauses ##### 11.17.3.6.1 Join clause **PREVIOUS SECTION ON JOIN REMOVED ABOVE - NOT INCLUDING JOIN INTO - MOVED HERE VERBATIM** The `left` and `right` modifiers of the `join` clause change the translation to the `LeftJoin()` and `RightJoin()` methods respectively, instead of `Join()`. Every other aspect of the translation remains the same. ##### 11.17.3.6.2 Join range variable nullability A regular (inner) join introduces a range variable whose nullability follows from its source sequence. In other words, given the following code: ```csharp from c in customersh join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total } ``` ... the nullability of the range variable `o` flows from `orders` (`o` is an `Order?` if `orders` is `List`). In contrast, since `left join` returns outer elements which don't have correlated inner elements, the inner range variable it introduces is always nullable: ```csharp from c in customersh left join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o?.OrderDate, o?.Total } ``` ... in this example, `o` is an `Order?` even if `orders` is a `List`. Note that "nullable" in this context means `T?` in an unconstrained generic context; in other words, if `orders` is a sequence of value types (e.g. `List`), then `o` has the same type as the elements of that sequence (`int`, not `int?`). `right join` operates in a similar way, with one important difference: the already-existing, outer range variable is made nullable, rather than the inner range variable: ```csharp from o in orders right join c in customersh on o.CustomerID equals c.CustomerID select new { o?.OrderDate, o?.Total, c.Name } ``` In other words, if `o` was non-nullable before the `right join` operation (since `orders` is a sequence of non-nullable elements), the `right join` operation makes it nullable. ##### 11.17.3.6.3 Join into clause
Moved `join into` section, as-is A `join`-`into` clause immediately followed by a `select` clause ```csharp from «x1» in «e1» join «x2» in «e2» on «k1» equals «k2» into «g» select «v» ``` is translated into ```csharp ( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => «v» ) ``` A `join into` clause followed by a query body clause ```csharp from «x1» in «e1» join «x2» in «e2» on «k1» equals «k2» into *g» ... ``` is translated into ```csharp from * in ( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» }) ... ``` > *Example*: The example > > ```csharp > from c in customers > join o in orders on c.CustomerID equals o.CustomerID into co > let n = co.Count() > where n >= 10 > select new { c.Name, OrderCount = n } > ``` > > is translated into > > ```csharp > from * in (customers).GroupJoin( > orders, > c => c.CustomerID, > o => o.CustomerID, > (c, co) => new { c, co }) > let n = co.Count() > where n >= 10 > select new { c.Name, OrderCount = n } > ``` > > the final translation of which is > > ```csharp > customers > .GroupJoin( > orders, > c => c.CustomerID, > o => o.CustomerID, > (c, co) => new { c, co }) > .Select(x => new { x, n = x.co.Count() }) > .Where(y => y.n >= 10) > .Select(y => new { y.x.c.Name, OrderCount = y.n }) > ``` > > where `x` and `y` are compiler generated identifiers that are otherwise invisible and inaccessible. > > *end example*
## Drawbacks [drawbacks]: #drawbacks There are no specific drawbacks to introduce `left join`/`right join` AFAICT. It's worth noting that C# query expression support for LINQ hasn't evolved in a long time - this would be the first change in quite a while. At the same time, AFAIK there hasn't been any formal deprecation/archiving of this area of the language. Note that the proposed `join` modifiers do not require any LINQ expression tree changes, as they're represented via existing MethodCallExpression's which reference the new `LeftJoin()` and `RightJoin()` methods. There is thus nothing blocking supporting them from LINQ providers (such as EF Core). ## Alternatives [alternatives]: #alternatives If support isn't added to C#, users can still use the `LeftJoin`/`RightJoin` methods being introduced into .NET 10, although this would force them to drop out of C# query syntax to method syntax. In order to keep using query syntax, users will more likely continue using the existing way of expressing left joins via `join into` + `DefaultIfEmpty()` ([docs](https://learn.microsoft.com/en-us/dotnet/csharp/linq/standard-query-operators/join-operations#perform-left-outer-joins)), which is both inefficient and more verbose. Also, since first-class left/right join is being introduced into .NET and EF 10, not including support in C# would mean a partial feature that's implemented only in some parts of the stack and not in others. ## Open questions [open]: #open-questions As noted above, this is the first proposal for evolving C#'s support around LINQ in a long while; there are quite a few other gaps in this area: additional C# query expression clauses (distinct, aggregates, set operations...), as well as evolving expression tree support to allow for newer C# constructs ([discussion](https://github.com/dotnet/csharplang/issues/2545)). We should consider our strategy going forward in this area. ================================================ FILE: proposals/multiple-using-var-discards.md ================================================ # Treat multiple `using var _` as discards Alternate proposal to [anonymous-using-declarations.md](anonymous-using-declarations.md). Champion issue (anonymous using declarations): ## Summary Allow multiple `using var _` declarations by treating them as discards when conflicts would otherwise occur. This follows the same conflict-detection philosophy as C# 9.0's lambda discard parameters, but applies it to using declarations - the one remaining context where developers are forced to create identifiers they don't want. ## Motivation C# 8.0 introduced using declarations, allowing `using` to be applied directly to local variable declarations: ```csharp using var someDisposable = GetSomeDisposable(); // someDisposable is disposed at the end of the enclosing scope ``` However, when acquiring multiple disposables solely for cleanup (without needing to reference them), developers hit a pain point: ```csharp using var _ = GetDisposable(); using var _ = GetOtherDisposable(); // Error CS0128: A local variable named '_' is already defined ``` Today, the workaround is ugly numbered discards: ```csharp using var _ = GetDisposable(); using var __ = GetOtherDisposable(); using var ___ = GetYetAnotherDisposable(); // or using var _1 = GetDisposable(); using var _2 = GetOtherDisposable(); using var _3 = GetYetAnotherDisposable(); ``` We see these patterns extensively in real-world codebases - Roslyn itself has nearly 500 cases alone, and analysis of internal and open-source projects shows thousands of instances. This is a genuine pain point that developers are actively working around with hacks. Rather than continuing to ignore this issue, we should provide a clean, first-class solution. This proposal makes the natural syntax just work: ```csharp using var _ = GetDisposable(); using var _ = GetOtherDisposable(); using var _ = GetYetAnotherDisposable(); // All three are discards ``` ### Precedent: Lambda Discard Parameters C# 9.0 introduced a similar conflict-detection approach for lambda parameters: ```csharp // C# 8.0 and earlier - ERROR Action a = (_, _) => { }; // Error CS0100: duplicate parameter '_' // C# 9.0 and later - LEGAL (both parameters are discards) Action a = (_, _) => { }; ``` This proposal applies that same philosophy to `using var _` declarations. ## Detailed Design ### No Syntax Changes This proposal requires no changes to C#'s syntax or grammar. This is purely a change to semantics and binding behavior. ### Core Rule and Scope When the compiler encounters conflicting `_` declarations that would produce CS0128 in **using var declarations** (`using var _`), it treats them as discards instead. This transformation applies ONLY to `using var _` declarations. Regular local variable declarations (`var _` without `using`) are NOT affected - developers should use normal discard syntax (`_ = ...;`) for those cases. Lambda parameters already have their own discard behavior from C# 9.0 and remain independent. The detection is purely syntactic during binding: 1. Detect conflicting `_` declarations within a local variable declaration space 2. Check if all conflicts are `using var` declarations 3. If so, treat all as discards instead of producing CS0128 ### Using Declarations Multiple `using var _` declarations in the same scope become discards: ```csharp void Method() { using var _ = GetDisposable(); using var _ = GetOtherDisposable(); using var _ = GetYetAnotherDisposable(); // All three are discards } ``` A single `using var _` with no conflicts remains a named variable (backward compatibility): ```csharp using var _ = GetDisposable(); // Named variable (current behavior preserved) ``` ### Lambda and Local Function Boundaries Lambda parameters already have their own discard behavior from C# 9.0, which operates independently. Lambdas and local functions create separate declaration spaces: ```csharp void Method() { using var _ = GetDisposable(); using var _ = GetOther(); // Both are discards (same scope) Action a = () => { using var _ = GetThird(); // Named variable (separate scope) }; } ``` ### Method and Local Function Parameters Method and local function parameters are externally visible signatures and never participate in this transformation. A method or local function parameter named `_` conflicting with a `using var _` remains an error: ```csharp void Method(int _) // Parameter must stay named (externally visible) { // ERROR CS0128: single using var _ doesn't get special treatment, conflicts with parameter using var _ = GetDisposable(); } ``` ## Specification Changes ### §7.3 Declarations - Declaration Spaces The existing rule states that local variable declaration spaces may be nested, but it is an error for conflicting declarations to have the same name (producing CS0128). This error is detected syntactically during binding. Add the following exception to this rule: > However, when multiple `using var` declarations would conflict solely because they all use the identifier `_`, then all > such conflicting declarations shall be treated as discards (§9.2.9.2) instead. The identifier `_` introduces no name > into any declaration space, and the value cannot be referenced. > > *Note*: This rule applies only when conflicts would occur. A single `using var _` with no conflicts retains its > meaning as a named variable for backward compatibility. ### §7.3 Declarations - Method and Local Function Parameters (Optional) This clarification may not be necessary if the above section is sufficiently clear, but can be added if desired: > Method and local function parameters are externally visible and must remain named identifiers. When a method or local > function parameter named `_` would conflict with a using declaration named `_`, the conflict remains a compile error. ### §9.2.9.2 Discards Update the discard specification: > A discard is indicated by the identifier `_` in the following contexts: > - [existing contexts...] > - A using var declaration (`using var _`) when multiple such declarations with the same identifier would conflict in > the same local variable declaration space (§7.3) ### §13.14 The using statement Add clarification for using declarations: > When multiple `using var _` declarations would conflict within the same local variable declaration space, the conflict > resolution rules in §7.3 apply. ## Open Design Questions ### Should this support explicit types (`using IDisposable _`)? This proposal currently specifies `using var _` only. An alternative would be to also support explicitly-typed using declarations like `using IDisposable _`. **Arguments for `var _` only:** - Discards don't normally present their type (though they do acquire one from initialization) - `var _` is the closest analog to discard syntax in the variable declaration space - Simpler, more focused feature - Avoids questions about type compatibility when mixing `var _` and `Type _` **Arguments for supporting both `var _` and `Type _`:** - Some developers prefer explicit types and would be frustrated by being forced to use `var` - The implementation complexity is similar either way - More general solution - Example of mixing: ```csharp using var _ = GetDisposable(); using IDisposable _ = GetOtherDisposable(); // Would both become discards ``` **Recommendation**: Start with `var _` only. If explicit types prove to be a significant pain point, they can be added later without breaking changes. ### Should lambda parameter `_` and `using var _` unify? This proposal keeps `using var _` discard detection separate from C# 9.0's lambda parameter discard detection. This means: ```csharp Action a = _ => { using var _ = GetDisposable(); // ERROR CS0128 (parameter and using var conflict) }; ``` **Arguments for keeping them separate:** - Simpler mental model - each feature operates independently within its own scope - Avoids complexity in the compiler's conflict detection spanning different declaration contexts - C# 9.0 lambda parameter discards already work well on their own - Clearer what's happening - conflicts are detected purely within one declaration space **Arguments for unifying:** - More consistent - both use the same philosophy of "detect conflicts, make them all discards" - Avoids surprising CS0128 errors when mixing lambda parameters and using declarations - More general solution that handles all `_` conflicts together - Example of unified behavior: ```csharp Action a = _ => { using var _ = GetDisposable(); // Both parameter and using var become discards }; Action b = (_, _) => { using var _ = GetDisposable(); // All three become discards }; ``` **Recommendation**: Start with separate detection for simplicity. The LDM can decide if unification is desirable. ## Drawbacks ### Pragmatic Tradeoffs Ideally, `_` would be treated universally as a discard in all contexts. However, achieving that ideal would require breaking changes to existing code where `_` is used as a regular identifier, and would introduce conflicts with using alias directives at the top level (`using _ = ...;`). The latter conflict is not hypothetical - we've observed `using _` in using aliases in several real codebases. Given C#'s strong commitment to compatibility, we're unlikely to get there soon without severe breaks. This proposal takes the proven pattern from C# 9.0 lambda discards and extends it minimally to solve the specific pain point with using declarations. It's non-breaking, lightweight, and focused on the one scenario where developers are forced to create unwanted identifiers. Importantly, `using var _` is already in widespread use in existing codebases - developers have been working around the CS0128 limitation with numbered discards (`_1`, `_2`, etc.) for years. This proposal simply makes that existing pattern less painful and more natural. **Future Compatibility**: This proposal does not conflict with a hypothetical future where C# might treat `_` universally as a discard. If that day comes, this feature would simply become a subset of the broader change. ### Specific Concerns 1. **Context-dependent meaning**: The meaning of `using var _` changes based on whether conflicts exist. However: - This only affects currently-illegal code (CS0128 error), so there is no breaking change - This follows the precedent established by C# 9.0 lambda discard parameters 2. **Asymmetry with regular locals**: `using var _` gets special treatment while `var _` does not. However: - This asymmetry is justified: `using` requires a declaration; regular code doesn't (use `_ = ...;` instead) - The pain point is specifically about being forced to create identifiers you don't want - This keeps the feature focused and minimal ## Alternatives ### Alternative: Apply to all `var _` declarations Extend this to all local variable declarations, not just using declarations: ```csharp var _ = ComputeSomething(); var _ = ComputeOther(); // Would be legal ``` However, this is unnecessary. Developers can and should write: ```csharp _ = ComputeSomething(); _ = ComputeOther(); ``` The pain point only exists where syntax *requires* a declaration (using declarations and lambda parameters). Making this change broadly would add complexity without addressing a real problem. ## Additional Notes ### IDE Support IDEs could provide analyzers that suggest converting numbered discards (`_1`, `_2`) in using declarations to the `using var _` pattern when multiple are present. However, no compiler diagnostics are proposed - this is purely enabling currently-illegal code to work naturally. ## Related Issues This proposal addresses [#2235](https://github.com/dotnet/csharplang/issues/2235) and champion issue [#8606](https://github.com/dotnet/csharplang/discussions/8605). ================================================ FILE: proposals/null-conditional-await.md ================================================ # null-conditional await Champion issue: ## Summary [summary]: #summary Support an expression of the form `await? e`, which awaits `e` if it is non-null, otherwise it results in `null`. ## Motivation [motivation]: #motivation This is a common coding pattern, and this feature would have nice synergy with the existing null-propagating and null-coalescing operators. ## Detailed design [design]: #detailed-design We add a new form of the *await_expression*: ```antlr await_expression : 'await' '?' unary_expression ; ``` The null-conditional `await` operator awaits its operand only if that operand is non-null. Otherwise the result of applying the operator is null. The type of the result is computed using the rules for the null-conditional operator [§11.7.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1177-null-conditional-member-access). > **NOTE:** > If `e` is of type `Task`, then `await? e;` would do nothing if `e` is `null`, and await `e` if it is not `null`. > > If `e` is of type `Task` where `K` is a value type, then `await? e` would yield a value of type `K?`. ## Drawbacks [drawbacks]: #drawbacks As with any language feature, we must question whether the additional complexity to the language is repaid in the additional clarity offered to the body of C# programs that would benefit from the feature. ## Alternatives [alternatives]: #alternatives Although it requires some boilerplate code, uses of this operator can often be replaced by an expression something like `(e == null) ? null : await e` or a statement like `if (e != null) await e`. ## Unresolved questions [unresolved]: #unresolved-questions - [ ] Requires LDM review ## Design meetings None. ================================================ FILE: proposals/pattern-variables.md ================================================ # Variable declarations under disjunctive patterns Champion issue: ## Summary - Allow variable declarations under `or` and `not` patterns and across `case` labels in a `switch` section to share code. ```cs if (e is C c or Wrapper { Prop: C c }) return c; Expr Simplify(Expr e) { switch (e) { case Mult(Const(1), var x): case Mult(var x, Const(1)): case Add(Const(0), var x): case Add(var x, Const(0)): return Simplify(x); // .. } } ``` Instead of: ```cs if (e is C c1) return c1; if (e is Wrapper { Prop: C c2 }) return c2; Expr Simplify(Expr e) { switch (e) { case Mult(Const(1), var x): return Simplify(x); case Mult(var x, Const(1)): return Simplify(x); case Add(Const(0), var x): return Simplify(x); case Add(var x, Const(0)): return Simplify(x); // .. } } ``` - Also relax single-declaration rules within expression boundaries as long as each variable is assigned once. ```cs if (e is C c || e is Wrapper { Prop: C c }) ; if (b ? e is C c : e is Wrapper { Prop: C c }) ; ``` - Also relax single-declaration rules within conditions of an `if/else`: ```cs if (e is C c) { } else if (e is Wrapper { Prop: C c }) { } ``` ## Detailed design ### Variable redeclaration - Pattern variables are allowed to be redeclared in the following locations if not already definitely assigned: - Within case labels for the same switch section (includes `when` clauses) - Within a single expression (includes `is` expressions) - Within a single pattern (includes `switch` expression arms) - Across top-level condition expressions within a single `if` statement (includes `else if`) These names can possibly reference either of variables based on the result of the pattern-matching at runtime. - Pattern variables with multiple declarations must be of the same type, excluding top-level nullability for reference types. Differences in nested nullability are subject to standard nullability conversion warnings. if (e is C c || e is Wrapper { Prop: var c }) { /* c may be null */ } ### Definite assignment For an *is_pattern_expression* of the form `e is pattern`, the definite assignment state of *v* after *is_pattern_expression* is determined by: - The state of *v* after *is_pattern_expression* is definitely assigned, if *pattern* is irrefutable. - Otherwise, the state of *v* after *is_pattern_expression* is the same as the state of *v* after *pattern*. For a *switch_section* of the form `case pattern_1 when condition_1: ... case pattern_N when condition_N:`, the definite assignment state of *v* is determined by: - The state of *v* before *condition_i* is definitely assigned, if the state of *v* after *pattern_i* is "definitely assigned when true". - The state of *v* before *switch_section_body* is definitely assigned, if the state of *v* after each *switch_section_label* is "definitely assigned when true". - Otherwise, the state of *v* is not definitely assigned. #### General definite assignment rules for pattern variables The following rule applies to any *primary_pattern* *p* that declares a variable, namely *list_pattern*, *declaration_pattern*, *recursive_pattern*, and *var_pattern*, as well as any nested subpatterns. - The state of *v* after *p* is "definitely assigned when true". #### Definite assignment rules for pattern variables declared under logical patterns For a *disjunctive_pattern* *p* of the form `left or right`, the definite assignment state of *v* is determined by: - The state of *v* before *right* is definitely assigned if and only if the state of *v* after *left* is "definitely assigned when false". - The state of *v* after *p* is "definitely assigned when true" if the state of *v* after both *left* and *right* is "definitely assigned when true". - The state of *v* after *p* is "definitely assigned when false" if the state of *v* after either *left* or *right* is "definitely assigned when false". For a *conjunctive_pattern* *p* of the form `left and right`, the definite assignment state of *v* is determined by: - The state of *v* before *right* is definitely assigned if and only if the state of *v* after *left* is "definitely assigned when true". - The state of *v* after *p* is "definitely assigned when true" if the state of *v* after either *left* or *right* is "definitely assigned when true". - The state of *v* after *p* is "definitely assigned when false" if the state of *v* after both *left* and *right* is "definitely assigned when false". For a *negated_pattern* *p* of the form `not pattern`, the definite assignment state of *v* after *p* is determined by: - If the state of *v* after *pattern* is "definitely assigned when true", then the state of *v* after *p* is "definitely assigned when false". - If the state of *v* after *pattern* is "definitely assigned when false", then the state of *v* after *p* is "definitely assigned when true". These rules cover the existing top-level `is not` pattern variables. However, in any other scenario the variables could be left unassigned. ## Unresolved questions - Would it be possible to permit different types for each variable especially within `if`/`else` chains? And if so, would it be a part of this proposal or a separate feature? (discussed in LDM 2022/10/17) ================================================ FILE: proposals/proposal-template.md ================================================ # FEATURE_NAME Champion issue: ## 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. This section can start out light before the prototyping phase but should get into specifics and corner-cases as the feature is iteratively designed and implemented. ## 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? ## Open questions [open]: #open-questions What parts of the design are still undecided? ================================================ FILE: proposals/readonly-parameters.md ================================================ # Readonly Parameters Champion issue: ## Summary [summary]: #summary We allow parameters to be marked with `readonly`. This disallows them from being assigned to or being passed by `ref` or `out`. ## Motivation [motivation]: #motivation C# users have [long requested](https://github.com/dotnet/csharplang/issues/188) the ability to mark both locals and parameters as `readonly`. The design team has somewhat resisted this for two reasons: * The view that `readonly` on parameters and locals would be an attractive nuisance more than a helpful addition. * Indecision on what a succinct syntax for locals would be to minimize the nuisance part of the first objection. However, the addition of primary constructor parameters changes that calculus, at least for parameters. A significant piece of feedback from the initial preview is that users would like to be able to ensure that primary constructor parameters are not modified. The scope of such parameters is much larger, so the danger of accidental modification is much higher. We therefore propose allowing `readonly` as a parameter modifier. This proposal makes a few assumptions about `readonly` locals as part of it: * A future design would allow `readonly` as a local modifier. * That future design might allow a shorthand for `readonly var`, or may say that `readonly var` is not allowed, and the separate shorthand is required for a `readonly` type-inferred local. What that shorthand is (`val`, `let`, `const`, or some other keyword) is beyond the scope of this proposal. These assumptions allow us to presume that to fully spell out readonlyness for a parameter or local requires a modifier, and that modifier is `readonly`. In places where types can be inferred, we offer a shorthand that combines the meanings, but otherwise `readonly` is required. ## Detailed design [design]: #detailed-design ### Syntax We modify [section 15.6.2.1](https://github.com/dotnet/csharpstandard/blob/draft-v7/standard/classes.md#15621-general) of the C# specification with the following new definition for `parameter_modifier`: ```antlr parameter_modifier : parameter_mode_modifier | 'this' | 'readonly' ; ``` The spec is not yet updated to include the draft C# 12 specification for [`ref readonly` parameters](https://github.com/dotnet/csharplang/blob/main/proposals/ref-readonly-parameters.md), but we will restrict `readonly` such that if a parameter is both `readonly` and `ref readonly`, the `readonly` must appear on the left side of the `ref readonly`, so that `readonly ref readonly` is permitted, but `ref readonly readonly` is not. ### Semantics For a `readonly` parameter, the compiler will issue an error when it is assigned to or taken as a mutable `lvalue`. This means that a `readonly` parameter cannot be passed by `out` or `ref`, but can be passed by value, `ref readonly`, or `in`. #### `partial` methods For `partial` methods, we allow the implementing `partial` method declaration to add the `readonly` modifier to a parameter if the defining `partial` method did not. If the defining partial `method` included the `readonly` modifier, the implementing `partial` method must also include it. #### Signature-only locations `abstract` members, `interface` members, delegate types, and function pointer types are not permitted to specify `readonly` on their parameters #### Overriding Overriding members are not required to match the `readonly`ness of overridden member's parameters. `readonly` may be added or removed with no effect on the program. #### Lambda parameters Unresolved question ### Emit The presence of `readonly` on a parameter has no impact to the generated code. It is not possible to determine from metadata whether a parameter is `readonly` or not. ## Drawbacks [drawbacks]: #drawbacks Earlier the proposal alluded to this feature being an attractive nuisance. To spell it out more clearly, we are worried that by introducing a verbose modifier that many people would like to be the default (including ourselves!), it will become the new "thing to do" on every method definition, even in cases when it provides no real safety benefits. ## Alternatives [alternatives]: #alternatives The [championed issue](https://github.com/dotnet/csharplang/blob/main/proposals/ref-readonly-parameters.md) has a number of alternative designs, but most center around the axis of: should we introduce a new, shorter modifier, or a shorthand that can apply to both locals and parameters? For example, `val int i` as a parameter definition would be what this proposal calls `readonly int i`. This shorthand is very inconsistent with standard C# behavior, so this proposal takes the position that we would only want to introduce a shorthand for the `readonly` + type inference case. ## Unresolved questions [unresolved]: #unresolved-questions ### Restrict to just primary ctor parameters This general feature has historically been pushed back on due to the attractive nuisanceness of the feature. We could artificially restrict this to just primary constructor parameters to avoid introducing that in general. ### Lambda parameters Today, when applying a modifier to a lambda parameter, the type must also be spelled out. For example, this is not permitted: ```cs delegate void D(ref int i); D d = (ref i) => {}; ``` The LDM has long thought about allowing the type here to be omitted, but has not yet done so. Should we make that change as part of this proposal? Or should we say that `readonly`, like `ref`, means that the type of the lambda parameter must be spelled out? ### Emit consequences This proposal states that `readonly` on parameters has no effect on the emitted code, and that it will not be possible to tell from metadata (including things that read metadata, such as reflection) that a parameter was declared as `readonly`. Are there use cases for reflecting this information in metadata, and if so, what should the emit strategy we use to convey this information be? And, if we do emit to metadata, should that change how overriding carries through that information? ================================================ FILE: proposals/readonly-setter-calls-on-non-variables.md ================================================ # Readonly setter calls on non-variables Champion issue: ## Summary Permits a readonly setter to be called on non-variable expressions, and permits object initializers to be used with value types: ```cs var c = new C(); // Remove the current CS1612 error, because ArraySegment.this is readonly: c.ArraySegmentProp[10] = new object(); // Invocation expressions already omit the CS1612 error when the setter is readonly: c.ArraySegmentMethod()[10] = new object(); // In limited cases, ref-returning indexers can be used to work around this: c.RefReturningIndexerWorkaround[10] = new object(); _ = new C { // Remove the current CS1918 error: ArraySegmentProp = { [10] = new object() }, // Remove the current CS1918 error: RefReturningIndexerWorkaround = { [10] = new object() }, }; class C { public ArraySegment ArraySegmentProp { get; set; } public ArraySegment ArraySegmentMethod() => ArraySegmentProp; public Span RefReturningIndexerWorkaround => ArraySegmentProp.AsSpan(); } // Partial declaration of System.ArraySegment for demonstration public readonly struct ArraySegment { // Implicitly readonly due to the readonly modifier on the struct public T this[int index] { get => ...; set => ...; } } ``` Currently, the code above gives the error CS1612 "Cannot modify the return value of 'C.ArraySegmentProp' because it is not a variable." This restriction is unnecessary when the setter is readonly. The restriction is there to remind you to assign the modified struct value back to the property. But there is no supported modification to the struct value when the setter is readonly, so there is no reason to assign back to the property. ```cs // Requested by the current CS1612 error: var temp = c.ArraySegmentProp; temp[10] = new object(); c.ArraySegmentProp = temp; // But this line is purposeless; 'temp' cannot have changed. ``` ## Motivation Folks who want to simulate named indexers in C# use properties that return a wrapper with an indexer on it. The wrapper that holds the indexer must be a class today, which increases GC overhead. The only reason the wrapper cannot be a struct is because of the restriction which this proposal removes: ```cs var c = new C(); // Proposal: no error because the indexer's set accessor is readonly. c.SimulatedNamedIndexer[42] = new object(); class C { public WrapperStruct SimulatedNamedIndexer => new(this); public readonly struct WrapperStruct(C c) { public object this[int index] { // Indexer accesses private state or calls private methods in 'C' get => ...; set => ...; } } } ``` This addresses recurring community requests. These requests are often around document object models, entity component systems, numerics and geometry, and other modeling scenarios, but sometimes more ordinary helpers or utilities. This is the common thread: folks would like to not have to declare a pair of accessor methods when the accessors would make logical sense together as a single property or indexer. And, they don't want a class wrapper providing the API, they want a readonly struct wrapper. ```cs // Undesirable: c.SetXyz("key", c.GetXyz("key") + 1); // Desirable: c.Xyz["key"]++; ``` These use cases would no longer be blocked if CS1612 is fully updated with an understanding of readonly structs and readonly members. ## Detailed design For part 1, the CS1612 error is not produced for assignments where the setter is readonly. (If the whole struct is readonly, then the setter is also readonly.) The setter call is emitted the same way as any non-accessor readonly instance method call. For part 2, object initializers are now permitted to be used on value types. This means that the CS1918 error will no longer be produced at all. This opens the door to assignments using readonly setters or using refs returned by getters. However, this does not open the door to assignments that would not be permitted in the desugared form. Errors such as CS1612 and CS0313 will be updated to appear within object initializers now that CS1918 is no longer blocking off the entire space. ### Null-conditional assignment The [Null-conditional assignment](https://github.com/dotnet/csharplang/blob/main/proposals/null-conditional-assignment.md) proposal enables the following syntax: ```cs a?.b = value; a?.b.c = value; a?[index] = value; ``` CS1612 errors will be produced for this new syntax in the same way that they are for the existing syntax below: ```cs a2.b = value; a2.b.c = value; a2[index] = value; ``` Thus, when the setter is readonly, the assignments will be permitted in both cases, and when the setter is not readonly, the normal CS1612 error for structs will remain in both cases. ### InlineArray InlineArray properties are covered by a separate error which this proposal does not affect: ```cs var c = new C(); // ❌ CS0313 The left-hand side of an assignment must be a variable, property or indexer c.InlineArrayProp[42] = new object(); class C { public InlineArray43 InlineArrayProp { get; set; } } [System.Runtime.CompilerServices.InlineArray(43)] public struct InlineArray43 { public T Element0; } ``` It's desirable for this error to remain, because the setter _does_ mutate the struct value. ## Specification Insertions are in **bold**, deletions are in ~~strikethrough~~. ### Updates permitting readonly setter calls on non-variables The current v8 specification draft does not yet specify readonly members (). The following updates intend to leverage the concept of a _readonly member_. A _readonly member_ is a member which either is directly marked with the `readonly` modifier or which is contained inside a _struct_type_ which is marked with the `readonly` modifier. [§12.21.2](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#12212-simple-assignment) _Simple assignment_ is updated: > When a property or indexer declared in a _struct_type_ is the target of an assignment, **either** the instance expression associated with the property or indexer access shall be classified as a variable, **or the set accessor of the property or indexer shall be a readonly member ([§16.2.2](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/structs.md#1622-struct-modifiers))**. If the instance expression is classified as a value **and the set accessor is not a readonly member**, a binding-time error occurs. ### Updates permitting object initializers for value types The CS1918 error is completely removed. There is no need to block value types. "\[T]he assignments in the nested object initializer are treated as assignments to members of the field or property." Such assignments must already conform to rules such as the one enforced by CS1612, including when inside an object initializer. [§12.8.16.3](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/expressions.md#128163-object-initializers) _Object initializers_ is updated: > A member initializer that specifies an object initializer after the equals sign is a ***nested object initializer***, i.e., an initialization of an embedded object. Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. ~~Nested object initializers cannot be applied to properties with a value type, or to read-only fields with a value type.~~ ## Downsides If this proposal is taken, it becomes a source-breaking change to remove the `readonly` keyword from a struct or setter. Without the `readonly` keyword, the errors would then be relevant and would reappear. Due to what looks like an unintentional change in the compiler, this source-breaking change is already in effect when the setter is called on an invocation expression: ```cs // Removing 'readonly' from S1 causes a CS1612 error. M().Prop = 1; S1 M() => default; public readonly struct S1 { public int Prop { get => 0; set { } } } ``` ```cs // Removing 'readonly' from S2.Prop.set causes a CS1612 error. M().Prop = 1; S2 M() => default; public struct S2 { public int Prop { get => 0; readonly set { } } } ``` ## Answered LDM questions ### Should similar assignments be permitted in object initializers? There's a separate error, CS1918 that blocks assignments through readonly setters when the assignments appear in object initializers. In addition, this error even blocks assignments to ref-returning properties and indexers, and those assignments are not blocked when they appear outside of object initializers. ```cs // ❌ CS1918 Members of property 'C.ArraySegmentProp' of type 'ArraySegment' cannot be assigned with an object // initializer because it is of a value type _ = new C { ArraySegmentProp = { [42] = new object() } }; // ~~~~~~~~~~~~~~~~ // ❌ CS1918 Members of property 'C.StructWithRefReturningIndexer' of type 'Span' cannot be assigned with an object // initializer because it is of a value type _ = new C { StructWithRefReturningIndexer = { [42] = new object() } }; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class C { public ArraySegment ArraySegmentProp { get; set; } public Span StructWithRefReturningIndexer => ArraySegmentProp.AsSpan(); } ``` Such assignments desugar to the following form, the same form in which the CS1612 warning is being removed: ```cs var temp = new C(); // Warning being removed: // CS1612 Cannot modify the return value of 'C.ArraySegmentProp' because it is not a variable temp.ArraySegmentProp[42] = new object(); ``` ```cs var temp = new C(); // Permitted today temp.StructWithRefReturningIndexer[42] = new object(); ``` Should this check be made more granular, so that members of struct types may be assigned when they would be allowed to be assigned in the desugared form? #### Answer Yes. This expansion will be included. [(LDM 2025-04-02)](https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-02.md#expansions) ================================================ FILE: proposals/rejected/README.md ================================================ ================================================ FILE: proposals/rejected/collection-expressions-in-foreach.md ================================================ # Collection Expressions in `foreach` [!INCLUDE[Specletdisclaimer](../speclet-disclaimer.md)] Closed in favor of [immediately enumerated collection expressions](../immediately-enumerated-collection-expressions.md). Champion issue: https://github.com/dotnet/csharplang/issues/9739 ## Summary [Collection Expressions](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md) introduced a terse syntax `[e1, e2, e3, etc]` to create common collection values. This proposal extends their usage to `foreach` statements, where they can be used directly as the iteration source without requiring an explicit target type. ## Motivation It is common and reasonable for developers to want to iterate over a known set of values. This pattern appears frequently in real-world code: ```csharp // Today, developers must write: foreach (var toggle in new[] { true, false }) { RunTestWithFeatureFlag(toggle); } // With this proposal, they can write: foreach (var toggle in [true, false]) { RunTestWithFeatureFlag(toggle); } ``` Another common scenario is iterating through a fixed set of stages or phases: ```csharp // Today: foreach (var phase in new[] { Phase.Parsing, Phase.Binding, Phase.Lowering, Phase.Emit }) { ExecuteCompilerPhase(phase); } // With this proposal: foreach (var phase in [Phase.Parsing, Phase.Binding, Phase.Lowering, Phase.Emit]) { ExecuteCompilerPhase(phase); } ``` Requests for this capability have been heard internally and throughout the ecosystem. This feature was originally part of the collection expressions work but was extracted to keep the initial scope minimal. Additionally, implementing this in the general case would require giving collection expressions a "natural type," which proved to be too large and complex a design space to tackle at that time. However, for `foreach` statements specifically, the problem space is much simpler. The collection is created and immediately consumed—user code cannot introspect the collection itself—giving the language and compiler broad flexibility in implementation without the complexities of determining a universal natural type. This flexibility follows the design principles of collection-expressions themselves, allowing optimal performance, with minimal syntax. ## Detailed design ### Syntax No grammar changes are required. Collection expressions are already valid expressions syntactically; this proposal only extends where they can be used semantically. ### Semantics #### Explicitly typed foreach For an explicitly typed `foreach` statement of the form: ```csharp foreach (T v in [e1, e2, ..s1, etc.]) { // ... } ``` This is interpreted as: ```csharp foreach (T v in (T[])[e1, e2, ..s1, etc.]) { // ... } ``` In other words, the collection expression is target-typed to an array of the explicitly provided iteration type `T`. #### Implicitly typed foreach For an implicitly typed `foreach` statement of the form: ```csharp foreach (var v in [e1, e2, ..s1, etc.]) { // ... } ``` The element type `T_e` is computed using the [best common type](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) - The types of all expression elements `e1`, `e2`, etc. - The [element types](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions) of all spread elements `..s1`, etc. The statement is then interpreted as: ```csharp foreach (T_e v in (T_e[])[e1, e2, ..s1, etc.]) { // ... } ``` If no best common type can be determined, a compile-time error occurs. Note: this is also akin to having a method `void M(T[] values);` and running type inference on `M([e1, e2, ..s1, etc.])` to see what type argument `T` is inferred to be. That type for `T` is then the element type for the `foreach` and the rules for an explicitly typed `foreach` statement (above) apply. ### Implementation flexibility While the semantics are defined in terms of array creation, a compliant implementation is free to optimize the collection expression however it deems appropriate, provided the observable behavior remains the same. For example: - Stored in the program's constant data segment when it contains only constant values. - Using a stack-allocated `ReadOnlySpan` for the elements when the element count is known and there are no intervening `await` expressions or `yield` statements. - Complete elision of the collection entirely. For example, `foreach (var i in [0, 1, 2, 3])` could be translated to: ```csharp for (var i = 0; i <= 3; i++) { // ... } ``` This follows the same implementation flexibility principle established in the base collection expressions [translation specification](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#collection-literal-translation). ### Design decisions #### Using explicit type vs. best common type The explicitly typed case uses the provided type `T` rather than computing best common type. This ensures the iteration type properly informs the collection expression elements. For example: ```csharp foreach (byte b in [1, 2, 3]) // Values treated as bytes { // ... } ``` If best common type was used in both cases, the values `1`, `2`, `3` would be typed as `int`, which would then fail to convert to `byte`. #### Empty collection expression The empty collection expression `[]` is: - **Legal** in the explicitly typed case: `foreach (int i in []) { }` - the target type is known (`int[]`) - **Illegal** in the implicitly typed case: `foreach (var v in []) { }` - no element type can be inferred This is not a special rule but follows naturally from the semantic translations above. #### Pointer types Because the semantics are defined in terms of arrays, pointer types are supported: ```csharp unsafe { int* p = null; foreach (var v in [p, p]) // Legal - creates int*[] { // ... } } ``` This would not be possible if the target type were `Span` or `ReadOnlySpan`, which cannot contain pointer types. ## Examples ### Valid usage ```csharp // Implicitly typed with naturally typed literals foreach (var value in [1, 2, 3, 4, 5]) // Element type is int // Explicitly typed with strings foreach (string s in ["hello", "world"]) // Element type is string // With spread elements int[] existing = { 1, 2, 3 }; foreach (var n in [0, ..existing, 4]) // Element type is int // Type inference with mixed elements IEnumerable enumerable = GetNumbers(); foreach (var n in [1, 2, ..enumerable, 3]) // Element type is int // With lambda expressions (requires explicit typing) foreach (Func f in [null, i => i, i => i * i]) // Empty collection with explicit type foreach (string s in []) // Boolean values foreach (var b in [true, false]) // Element type is bool // Null with reference types infers nullable foreach (var s in [null, "hello", "world"]) // Element type is string? // Dynamic elements dynamic d = GetDynamic(); foreach (var x in [1, d, 3]) // Element type is dynamic // Await expressions (not await foreach) foreach (var result in [await GetValueAsync(), await GetOtherValueAsync()]) // Element type is BCT of the expressions // Anonymous types foreach (var item in [new { A = 1 }, new { A = 2 }]) // Element type is the anonymous type. // Mixed arrays with collection expression int[] arr = { 1, 2 }; foreach (var a in [arr, [3, 4]]) // Legal. Inner collection expression target typed to int[]. Outer to int[][] ``` ### Invalid usage ```csharp // Error: Cannot infer element type from empty collection foreach (var x in []) // Error: No best common type between incompatible types foreach (var x in [SyntaxKind.IfKeyword, "string"]) // Error: Lambda expressions need target type foreach (var transform in [node => node.WithoutTrivia()]) // Error: No best common type when all elements are collection expressions foreach (var tokens in [[SyntaxKind.Public, SyntaxKind.Private], [SyntaxKind.Static, SyntaxKind.Async]]) // Error: await foreach doesn't work with collection expressions // (IAsyncEnumerable cannot be created from collection expression) await foreach (var compilation in [comp1, comp2, comp3]) { // ... } ``` ## Design notes ### Relationship to natural types This feature deliberately avoids giving collection expressions a general "natural type." While there is clear user demand for expressions like `var x = [1, 2, 3]`, determining what type `x` should be (array, `List`, `ImmutableArray`, etc.) involves complex trade-offs around mutability, performance, and API design. The `foreach` scenario sidesteps these issues because: 1. The collection is immediately consumed and cannot be stored or passed elsewhere 2. The compiler can choose the most efficient representation for each specific case 3. User code cannot depend on the specific collection type chosen This allows us to provide value to users now while leaving the door open for a future "natural types" feature. ### Optimization opportunities Implementations are encouraged to aggressively optimize these patterns. Since the collection's lifetime is limited to the `foreach` statement itself, compilers can: - Use stack allocation for small, known-size collections - Embed constant data directly in the assembly - Transform simple patterns into equivalent `for` loops - Use specialized enumeration patterns that avoid allocations The only requirement is that the iteration order and values match what would be produced by creating and iterating an array. These optimizations are best-effort, consistent with the approach taken in the base collection expressions specification. ### Special cases #### Nullability When `null` literals appear in a collection expression with reference types, the best common type computation will produce a nullable reference type. For example, `[null, "hello"]` has an element type of `string?`. #### Dynamic When any element in the collection expression is of type `dynamic`, the computed element type becomes `dynamic`. This follows the standard best common type rules where `dynamic` acts as a "top type" for type inference purposes. #### Nested collection expressions Collection expressions cannot be nested when using implicit typing if all elements are collection expressions, as collection expressions have no natural type. However, mixing arrays or other collections with collection expressions works: `[existingArray, [1, 2, 3]]` is valid because `existingArray` provides a concrete type for best common type computation. #### Async considerations This feature does not support `await foreach` with collection expressions, as `IAsyncEnumerable` cannot be created from a collection expression. However, `await` expressions can appear as elements: `foreach (var x in [await GetValueAsync(), await GetOtherAsync()])` is valid and the awaits are evaluated before iteration begins. ## Open questions None at this time. ## Design meetings [TBD: Links to relevant LDM notes] ================================================ FILE: proposals/rejected/declaration-expressions.md ================================================ # Declaration expressions Discussion: https://github.com/dotnet/csharplang/discussions/8935 Support declaration assignments as expressions. ## Motivation [motivation]: #motivation Allow initialization at the point of declaration in more cases, simplifying code, and allowing `var` to be used. ```csharp SpecialType ReferenceType => (var st = _type.SpecialType).IsValueType() ? SpecialType.None : st; ``` Allow declarations for `ref` arguments, similar to `out var`. ```csharp Convert(source, destination, ref List diagnostics = null); ``` ## Detailed design [design]: #detailed-design Expressions are extended to include declaration assignment. Precedence is the same as assignment. ```antlr expression : non_assignment_expression | assignment | declaration_assignment_expression // new ; declaration_assignment_expression // new : declaration_expression '=' local_variable_initializer ; declaration_expression // C# 7.0 | type variable_designation ; ``` The declaration assignment is of a single local. The type of a declaration assignment expression is the type of the declaration. If the type is `var`, the inferred type is the type of the initializing expression. The declaration assignment expression may be an l-value, for `ref` argument values in particular. If the declaration assignment expression declares a value type, and the expression is an r-value, the value of the expression is a copy. The declaration assignment expression may declare a `ref` local. There is an ambiguity when `ref` is used for a declaration expression in a `ref` argument. The local variable initializer determines whether the declaration is a `ref` local. ```csharp F(ref int x = IntFunc()); // int x; F(ref int y = RefIntFunc()); // ref int y; ``` The scope of locals declared in declaration assignment expressions is the same the scope of corresponding declaration expressions from C#7.0. It is a compile time error to refer to a local in text preceding the declaration expression. ## Alternatives [alternatives]: #alternatives No change. This feature is just syntactic shorthand after all. More general sequence expressions: see [#377](https://github.com/dotnet/csharplang/issues/377). To allow use of `var` in more cases, allow separate declaration and assignment of `var` locals, and infer the type from assignments from all code paths. ## See also [see-also]: #see-also See Basic Declaration Expression in [#595](https://github.com/dotnet/csharplang/issues/595). See Deconstruction Declaration in the [deconstruction](https://github.com/dotnet/roslyn/blob/master/docs/features/deconstruction.md) feature. ================================================ FILE: proposals/rejected/discriminated-unions.md ================================================ # Discriminated unions / `enum class` Champion issue: `enum class`es are a new kind of type declaration, sometimes referred to as discriminated unions, where each every possible instance the type is listed, and each instance is non-overlapping. An `enum class` is defined using the following syntax: ```antlr enum_class : 'partial'? 'enum class' identifier type_parameter_list? type_parameter_constraints_clause* '{' enum_class_body '}' ; enum_class_body : enum_class_cases? | enum_class_cases ',' ; enum_class_cases : enum_class_case | enum_class_case ',' enum_class_cases ; enum_class_case : enum_class | class_declaration | identifier type_parameter_list? '(' formal_parameter_list? ')' | identifier ; ``` Sample syntax: ```C# enum class Shape { Rectangle(float Width, float Length), Circle(float Radius), } ``` ## Semantics An `enum class` definition defines a root type, which is an abstract class of the same name as the `enum class` declaration, and a set of members, each of which has a type which is a subtype of the root type. If there are multiple `partial enum class` definitions, all members will be considered members of the enum class definition. Unlike a user-defined abstract class definition, the `enum class` root type is partial by default and defined to have a default *private* parameter-less constructor. Note that, since the root type is defined to be a partial abstract class, partial definitions of the *root type* may also be added, where standard syntax forms for a class body are allowed. However, no types may directly inherit from the root type in any declaration, aside from those specified as `enum class` members. In addition, no user-defined constructors are permitted for the root type. There are four kinds of `enum class` member declarations: * simple class members * complex class members * `enum class` members * value members. ### Simple class members A simple class member declaration defines a new nested "record" class (intentionally left undefined in this document) with the same name. The nested class inherits from the root type. Given the sample code above, ```C# enum class Shape { Rectangle(float Width, float Length), Circle(float Radius) } ``` the `enum class` declaration has semantics equivalent to the following declaration ```C# abstract partial class Shape { public record Rectangle(float Width, float Length) : Shape; public record Circle(float Radius) : Shape; } ``` ### Complex class members You can also nest an entire class declaration below an `enum class` declaration. It will be treated as a nested class of the root type. The syntax allows any class declaration, but it is required for the complex class member to inherit from the direct containing `enum class` declaration. ### `enum class` members `enum classes` can be nested under each other, e.g. ```C# enum class Expr { enum class Binary { Addition(Expr left, Expr right), Multiplication(Expr left, Expr right) } } ``` This is almost identical to the semantics of a top-level `enum class`, except that the nested enum class defines a nested root type, and everything below the nested enum class is a subtype of the nested root type, instead of the top-level root type. ```C# abstract partial class Expr { abstract partial class Binary : Expr { public record Addition(Expr left, Expr right) : Binary; public record Multiplication(Expr left, Expr right) : Binary; } } ``` ### Value members `enum classes` can also contain value members. Value members define public get-only static properties on the root type that also return the root type, e.g. ```C# enum class Color { Red, Green } ``` has properties equivalent to ```C# partial abstract class Color { public static Color Red => ...; public static Color Green => ...; } ``` The complete semantics are considered an implementation detail, but it is guaranteed that one unique instance will be returned for each property, and the same instance will be returned on repeated invocations. ### Switch expression and patterns There are some proposed adjustments to pattern matching and the switch expression to handle `enum classes`. Switch expressions can already match types through the variable pattern, but for currently for reference types, no set of switch arms in the switch expression are considered complete, except for matching against the static type of the argument, or a subtype. Switch expressions would be changed such that, if the root type of an `enum class` is the static type of the argument to the switch expression, and there is a set of patterns matching all members of the enum, then the switch will be considered exhaustive. Since value members are not constants and do not define new static types, they currently cannot be matched by pattern. To make this possible, a new pattern using the constant pattern syntax will be added to allow match against `enum class` value members. The match is defined to succeed if and only if the argument to the pattern match and the value returned by the `enum class` value member would be reference equal, although the implementation is not required to perform this check. ## Open questions - [ ] What does the common type algorithm say about `enum class` members? Is this valid code? * `var x = b ? new Shape.Rectangle(...) : new Shape.Circle(...)` - [ ] Adding a new pattern just for value members seems heavy handed. Is there a more general version construction that makes sense? - [ ] Value members also do not map well to a parallel nested class construction because of this - [ ] Is switching against an argument with an `enum class` static type guaranteed to be constant-time? - [ ] Should there be a way to make `enum class`es not be considered complete in the switch expression? Prefix with `virtual`? - [ ] What modifiers should be permitted on `enum class`? ================================================ FILE: proposals/rejected/fixed-sized-buffers.md ================================================ # Fixed Sized Buffers Champion issue: ## Summary [summary]: #summary Provide a general-purpose and safe mechanism for declaring fixed sized buffers to the C# language. ## Motivation [motivation]: #motivation Today, users have the ability to create fixed-sized buffers in an unsafe-context. However, this requires the user to deal with pointers, manually perform bounds checks, and only supports a limited set of types (`bool`, `byte`, `char`, `short`, `int`, `long`, `sbyte`, `ushort`, `uint`, `ulong`, `float`, and `double`). The most common complaint is that fixed-size buffers cannot be indexed in safe code. Inability to use more types is the second. With a few minor tweaks, we could provide general-purpose fixed-sized buffers which support any type, can be used in a safe context, and have automatic bounds checking performed. ## Detailed design [design]: #detailed-design One would declare a safe fixed-sized buffer via the following: ```csharp public fixed DXGI_RGB GammaCurve[1025]; ``` The declaration would get translated into an internal representation by the compiler that is similar to the following ```csharp [FixedBuffer(typeof(DXGI_RGB), 1024)] public ConsoleApp1.e__FixedBuffer_1024 GammaCurve; // Pack = 0 is the default packing and should result in indexable layout. [CompilerGenerated, UnsafeValueType, StructLayout(LayoutKind.Sequential, Pack = 0)] struct e__FixedBuffer_1024 { private T _e0; private T _e1; // _e2 ... _e1023 private T _e1024; public ref T this[int index] => ref (uint)index <= 1024u ? ref RefAdd(ref _e0, index): throw new IndexOutOfRange(); } ``` Since such fixed-sized buffers no longer require use of `fixed`, it makes sense to allow any element type. > NOTE: `fixed` will still be supported, but only if the element type is `blittable` ## Drawbacks [drawbacks]: #drawbacks * There could be some challenges with backwards compatibility, but given that the existing fixed-sized buffers only work with a selection of primitive types, it should be possible for the compiler to continue "just-working" if the user treats the fixed-buffer as a pointer. * Incompatible constructs may need to use slightly different `v2` encoding to hide the fields from old compiler. * Packing is not well defined in IL spec for generic types. While the approach should work, we will be bordering on undocumented behavior. We should make that documented and make sure other JITs like Mono have the same behavior. * Specifying a separate type for every length (an possibly another for `readonly` fields, if supported) will have impact on metadata. It will be bound by the number of arrays of different sizes in the given app. * `ref` math is not formally verifiable (since it is unsafe). We will need to find a way to update verification rules to know that our use is ok. ## Alternatives [alternatives]: #alternatives Manually declare your structures and use unsafe code to construct indexers. ## Unresolved questions [unresolved]: #unresolved-questions - should we allow `readonly`? (with readonly indexer) - should we allow array initializers? - is `fixed` keyword necessary? - `foreach`? - only instance fields in structs? ## Design meetings Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. ================================================ FILE: proposals/rejected/format.md ================================================ # Efficient Params and String Formatting ## Summary This combination of features will increase the efficiency of formatting `string` values and passing of `params` style arguments. ## Motivation The allocation overhead of formatting `string` values can dominate the performance of many text based applications: from the boxing penalty of `struct` types, the `object[]` allocation for `params` and the intermediate `string` allocations during `string.Format` calls. In order to maintain efficiency such applications often need to abandon productivity features such as `params` and `string` interpolation and move to non-standard, hand coded solutions. Consider MSBuild as an example. This is written using a lot of modern C# features by developers who are conscious of performance. Yet in one representative build sample MSBuild will generate 262MB of `string` allocation using minimal verbosity. Of that 1/2 of the allocations are short lived allocations inside `string.Format`. These features would remove much of that on .NET Desktop and get it down to nearly zero on .NET Core due to the availability of `Span` The set of language features described here will enable applications to continue using these features, with very little or no churn to their application code base, while removing the unintended allocation overhead in the majority of cases. ## Detailed Design There are a set of features that will be used here to achieve these results: - Expanding `params` to support a broader set of collection types. - Allowing for developers to customize how `string` interpolation is achieved. - Allowing for interpolated `string` to bind to more efficient `string.Format` overloads. ### Extending params The language will allow for `params` in a method signature to have the types `Span`, `ReadOnlySpan` and `IEnumerable`. The same rules for invocation will apply to these new types that apply to `params T[]`: - Can't overload where the only difference is a `params` keyword. - Can invoke by passing a series of arguments that are implicitly convertible to `T` or a single `Span` / `ReadOnlySpan` / `IEnumerable` argument. - Must be the last parameter in a method signature. - Etc ... The `Span` and `ReadOnlySpan` variants will be referred to as `Span` below for simplicity. In cases where the behavior of `ReadOnlySpan` differs it will be explicitly called out. The advantage the `Span` variants of `params` provides is it gives the compiler great flexibility in how it allocates the backing storage for the `Span` value. With a `params T[]` the compiler must allocate a new `T[]` for every invocation of a `params` method. Re-use is not possible because it must assume the callee stored and reused the parameter. This can lead to a large inefficiency in methods with lots of `params` invocations. Given `Span` variants are `ref struct` the callee cannot store the argument. Hence the compiler can optimize the call sites by taking actions like re-using the argument. This can make repeated invocations very efficient as compared to `T[]`. The language though will make no specific guarantees about how such callsites are optimized. Only note that the compiler is free to use values other than `T[]` when invoking a `params Span` method. One such potential implementation is the following. Consider all `params` invocation in a method body. The compiler could allocate an array which has a size equal to the largest `params` invocation and use that for all of the invocations by creating appropriately sized `Span` instances over the array. For example: ```csharp static class OneAllocation { static void Use(params Span spans) { ... } static void Go() { Use("jaredpar"); Use("hello", "world"); Use("a", "longer", "set"); } } ``` The compiler could choose to emit the body of `Go` as follows: ```csharp static void Go() { var args = new string[3]; args[0] = "jaredpar"; Use(new Span(args, start: 0, length: 1)); args[0] = "hello"; args[1] = "world"; Use(new Span(args, start: 0, length: 2)); args[0] = "a"; args[1] = "longer"; args[2] = "set"; Use(new Span(args, start: 0, length: 3)); } ``` This can significantly reduce the number of arrays allocated in an application. Allocations can be even further reduced if the runtime provides utilities for smarter stack allocation of arrays. This optimization cannot always be applied though. Even though the callee cannot capture the `params` argument it can still be captured in the caller when there is a `ref` or a `out / ref` parameter that is itself a `ref struct` type. ```csharp static class SneakyCapture { static ref int M(params Span span) => ref span[0]; static void Oops() { // This now holds onto the memory backing the Span ref int r = ref M(42); } } ``` These cases are statically detectable though. It potentially occurs whenever there is a `ref` return or a `ref struct` parameter passed by `out` or `ref`. In such a case the compiler must allocate a fresh `T[]` for every invocation. Several other potential optimization strategies are discussed at the end of this document. The `IEnumerable` variant is a merely a convenience overload. It's useful in scenarios which have frequent uses of `IEnumerable` but also have lots of `params` usage. When invoked in `T` argument form the backing storage will be allocated as a `T[]` just as `params T[]` is done today. ### params overload resolution changes This proposal means the language now has four variants of `params` where before it had one. It is sensible for methods to define overloads of methods that differ only on the type of a `params` declarations. Consider that `StringBuilder.AppendFormat` would certainly add a `params ReadOnlySpan` overload in addition to the `params object[]`. This would allow it to substantially improve performance by reducing collection allocations without requiring any changes to the calling code. To facilitate this the language will introduce the following overload resolution tie breaking rule. When the candidate methods differ only by the `params` parameter then the candidates will be preferred in the following order: 1. `ReadOnlySpan` 1. `Span` 1. `T[]` 1. `IEnumerable` This order is the most to the least efficient for the general case. ### Variant CoreFX is prototyping a new managed type named [Variant](https://github.com/dotnet/corefxlab/pull/2595). This type is meant to be used in APIs which expect heterogeneous values but don't want the overhead brought on by using `object` as the parameter. The `Variant` type provides universal storage but avoids the boxing allocation for the most commonly used types. Using this type in APIs like `string.Format` can eliminate the boxing overhead in the majority of cases. This type itself is not necessarily special to the language. It is being introduced in this document separately though as it becomes an implementation detail of other parts of the proposal. ### Efficient interpolated strings Interpolated strings are a popular yet inefficient feature in C#. The most common syntax, using an interpolated `string` as a `string`, translates into a `string.Format(string, params object[])` call. That will incur boxing allocations for all value types, intermediate `string` allocations as the implementation largely uses `object.ToString` for formatting as well as array allocations once the number of arguments exceeds the amount of parameters on the "fast" overloads of `string.Format`. The language will change its interpolation lowering to consider alternate overloads of `string.Format`. It will consider all forms of `string.Format(string, params)` and pick the "best" overload which satisfies the argument types. The "best" `params` overload will be determined by the rules discussed above. This means interpolated `string` can now bind to very efficient overloads like `string.Format(string format, params ReadOnlySpan args)`. In many cases this will remove all intermediate allocations. ### Customizable interpolated strings Developers are able to customize the behavior of interpolated strings with `FormattableString`. This contains the data which goes into an interpolated string: the format `string` and the arguments as an array. This though still has the boxing and argument array allocation as well as the allocation for `FormattableString` (it's an `abstract class`). Hence it's of little use to applications which are allocation heavy in `string` formatting. To make interpolated string formatting efficient the language will recognize a new type: `System.ValueFormattableString`. All interpolated strings will have a target type conversion to this type. This will be implemented by translating the interpolated string into the call `ValueFormattableString.Create` exactly as is done for `FormattableString.Create` today. The language will support all `params` options described in this document when looking for the most suitable `ValueFormattableString.Create` method. ```csharp readonly struct ValueFormattableString { public static ValueFormattableString Create(Variant v) { ... } public static ValueFormattableString Create(string s) { ... } public static ValueFormattableString Create(string s, params ReadOnlySpan collection) { ... } } class ConsoleEx { static void Write(ValueFormattableString f) { ... } } class Program { static void Main() { ConsoleEx.Write(42); ConsoleEx.Write($"hello {DateTime.UtcNow}"); // Translates into ConsoleEx.Write(ValueFormattableString.Create((Variant)42)); ConsoleEx.Write(ValueFormattableString.Create( "hello {0}", new Variant(DateTime.UtcNow))); } } ``` Overload resolution rules will be changed to prefer `ValueFormattableString` over `string` when the argument is an interpolated string. This means it will be valuable to have overloads which differ only on `string` and `ValueFormattableString`. Such an overload today with `FormattableString` is not valuable as the compiler will always prefer the `string` version (unless the developer uses an explicit cast). ## Open Issues ### ValueFormattableString breaking change The change to prefer `ValueFormattableString` during overload resolution over `string` is a breaking change. It is possible for a developer to have defined a type called `ValueFormattableString` today and use it in method overloads with `string`. This proposed change would cause the compiler to pick a different overload once this set of features was implemented. The possibility of this seems reasonably low. The type would need the full name `System.ValueFormattableString` and it would need to have `static` methods named `Create`. Given that developers are strongly discouraged from defining any type in the `System` namespace this break seems like a reasonable compromise. ### Expanding to more types Given we're in the area we should consider adding `IList`, `ICollection` and `IReadOnlyList` to the set of collections for which `params` is supported. In terms of implementation it will cost a small amount over the other work here. LDM needs to decide if the complication to the language is worth it though. The addition of `IEnumerable` removes a very specific friction point. Lacking this `params` solution many customers were forced to allocate `T[]` from an `IEnumerable` when calling a `params` method. The addition of `IEnumerable` fixes this though. There is no specific friction point that the other interfaces fix here. ## Considerations ### Variant2 and Variant3 The CoreFX team also has a non-allocating set of storage types for up to three `Variant` arguments. These are a single `Variant`, `Variant2` and `Variant3`. All have a pair of methods for getting an allocation free `Span` off of them: `CreateSpan` and `KeepAlive`. This means for a `params Span` of up to three arguments the call site can be entirely allocation free. ```csharp static class ZeroAllocation { static void Use(params Span spans) { ... } static void Go() { Use("hello", "world"); } } ``` The `Go` method can be lowered to the following: ```csharp static class ZeroAllocation { static void Go() { Variant2 _v; _v.Variant1 = new Variant("hello"); _v.Variant2 = new Variant("word"); Use(_v.CreateSpan()); _v.KeepAlive(); } } ``` This requires very little work on top of the proposal to re-use `T[]` between `params Span` calls. The compiler already needs to manage a temporary per call and do clean up work after (even if in one case it's just marking an internal temp as free). Note: the `KeepAlive` function is only necessary on desktop. On .NET Core the method will not be available and hence the compiler won't emit a call to it. ### CLR stack allocation helpers The CLR only provides only [localloc](https://learn.microsoft.com/dotnet/api/system.reflection.emit.opcodes.localloc) for stack allocation of contiguous memory. This instruction is limited in that it only works for `unmanaged` types. This means it can't be used as a universal solution for efficiently allocating the backing storage for `params Span`. This limitation is not some fundamental restriction though but instead more an artifact of history. The CLR could choose to add new op codes / intrinsics which provide universal stack allocation. These could then be used to allocate the backing storage for most `params Span` calls. ```csharp static class BetterAllocation { static void Use(params Span spans) { ... } static void Go() { Use("hello", "world"); } } ``` The `Go` method can be lowered to the following: ```csharp static class ZeroAllocation { static void Go() { Span span = RuntimeIntrinsic.StackAlloc(length: 2); span[0] = "hello"; span[1] = "world"; Use(span); } } ``` While this approach is very heap efficient it does cause extra stack usage. In an algorithm which has a deep stack and lots of `params` usage it's possible this could cause a `StackOverflowException` to be generated where a simple `T[]` allocation would succeed. Unfortunately C# is not set up for the type of inter-method analysis where it could make an educated determination of whether or not call should use stack or heap allocation of `params`. It can only really consider each call on its own. The CLR is best setup for making this type of determination at runtime. Hence we'd likely have the runtime provide two methods for universal stack allocation: 1. `Span StackAlloc(int length)`: this has the same behaviors and limitations of `localloc` except it can work on any type `T`. 1. `Span MaybeStackAlloc(int length)`: this runtime can choose to implement this by doing a stack or heap allocation. The runtime can then use the execution context in which it's called to determine how the `Span` is allocated. The caller though will always treat it as if it were stack allocated. For very simple cases, like one to two arguments, the C# compiler could always use `StackAlloc` variant. This is unlikely to significantly contribute to stack exhaustion in most cases. For other cases the compiler could choose to use `MaybeStackAlloc` instead and let the runtime make the call. How we choose will likely require a deeper investigation and examination of real world apps. But if these new intrinsics are available then it will give us this type of flexibility. ### Why not varargs? The existing [varargs](https://docs.microsoft.com/cpp/windows/variable-argument-lists-dot-dot-dot-cpp-cli) feature was considered here as a possible solution. This feature though is meant primarily for C++/CLI scenarios and has known holes for other scenarios. Additionally there is significant cost in porting this to Unix. Hence it wasn't seen as a viable solution. ## Related Issues This spec is related to the following issues: - https://github.com/dotnet/csharplang/issues/1757 - https://github.com/dotnet/csharplang/issues/179 - https://github.com/dotnet/corefxlab/pull/2595 ================================================ FILE: proposals/rejected/interpolated-string-handler-method-names.md ================================================ # Interpolated string handler method names Champion issue: ## Summary [summary]: #summary We add support for interpolated string handlers to receive a new piece of information, the name of the method they are an argument to, in order to solve a pain point in the creation of handler types and make them more useful in logging scenarios. ```cs public void LogDebug( this ILogger logger, [InterpolatedStringHandlerArgument(nameof(logger), "Method Name")] LogInterpolatedStringHandler message); ``` ## Motivation [motivation]: #motivation C# 10 introduced [interpolated string handlers][interpolated-string-spec], which were intended to allow interpolated strings to be used in high-performance and logging scenarios, using more efficient building techniques and avoiding work entirely when the string does not need to be realized. However, a common pain point has arisen since then; for logging APIs, you will often want to have APIs such as `LogTrace`, `LogDebug`, `LogWarn`, etc, for each of your logging levels. Today, there is no way to use a single handler type for all of those methods. Instead, our guidance has been to prefer a single `Log` method that takes a `LogLevel` or similar enum, and use `InterpolatedStringHandlerArgumentAttribute` to pass that value along. While this works for new APIs, the simple truth is that we have many existing APIs that use the `LogTrace/Debug/Warn/etc` format instead. These APIs either must introduce new handler types for each of the existing methods, which is a lot of overhead and code duplication, or let the calls be inefficient. We want to introduce a small addition to the possible values in `InterpolatedStringHandlerArgumentAttribute` to allow the name of the method being called to be passed along to the interpolated string handler type; this would then permit parameterization based on the method name, eliminating a large amount of duplication and making it viable for the BCL to adopt interpolation handlers for `ILogger`. Some examples of this: * [fedavorich/ISLE][isle] uses T4 to get around the bloat, by generating handlers for every log level. * [This BCL proposal][ilogger-proposal] was immediately abandoned after it was realized that there would need to be a handler type for every log level. ## Detailed design [design]: #detailed-design We make one small change to how interpolated string handlers perform [constructor resolution][constructor-resolution]. The change is bolded below: > ... > 2. The argument list `A` is constructed as follows: > 1. ... > 2. If `i` is used as an argument to some parameter `pi` in method `M1`, and parameter `pi` is attributed with `System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute`, > then for every name `Argx` in the `Arguments` array of that attribute the compiler matches it to a parameter `px` that has the same name. The empty string is matched to the receiver > of `M1`. **The string `"Method Name"` is matched to the name of `M1`.** > * If any `Argx` is not able to be matched to a parameter or the name of `M1`, or an `Argx` requests the receiver of `M1` and `M1` is a static method, an error is produced and no further > steps are taken. > * Otherwise, the type of every resolved `px` is added to the argument list, in the order specified by the `Arguments` array. Each `px` is passed with the same `ref` semantics as is specified in `M1`. **If `"Method Name"` was present in the `Arguments` array, then a type of `string` is added to the argument list in that position.** ### Example ```cs // Original code var someOperation = RunOperation(); ILogger logger = CreateLogger(LogLevel.Error, ...); logger.LogWarn($"Operation was null: {operation is null}"); // Approximate translated code: var someOperation = RunOperation(); ILogger logger = CreateLogger(LogLevel.Error, ...); var loggingInterpolatedStringHandler = new LoggingInterpolatedStringHandler(20, 1, "LogWarn", logger, out bool continueBuilding); if (continueBuilding) { loggingInterpolatedStringHandler.AppendLiteral("Operation was null: "); loggingInterpolatedStringHandler.AppendFormatted(operation is null); } LoggingExtensions.LogWarn(logger, loggingInterpolatedStringHandler); // Helper libraries namespace Microsoft.Extensions.Logging; { using System.Runtime.CompilerServices; [InterpolatedStringHandler] public struct LoggingInterpolatedStringHandler { public LoggingInterpolatedStringHandler(int literalLength, int formattedCount, string methodName, ILogger logger, out bool continueBuilding) { var methodLogLevel = methodName switch { "LogDebug" => LogLevel.Debug, "LogInfo" => LogLevel.Information, "LogWarn" => LogLevel.Warn, "LogError" => LogLevel.Error, _ => throw new ArgumentOutOfRangeException(methodName), }; if (methodLogLevel < logger.LogLevel) { continueBuilding = false; } else { continueBuilding = true; // Set up the rest of the builder } } } public static class LoggerExtensions { public static void LogWarn(this ILogger logger, [InterpolatedStringHandlerArgument("Method Name", nameof(logger))] ref LogInterpolatedStringHandler message); } } ``` ## Drawbacks [drawbacks]: #drawbacks Arguably, the magic empty string that we do is already a bit of magic; we risk further complicating the feature by adding in more magic strings that users need to know. ## Alternatives [alternatives]: #alternatives We could design a more complicated system that allows for passing of arbitrary constants to the interpolated string handler constructor; for example, it could be considered a bit of a hack that we use the name of the logging method, instead of a proper `LogLevel` enum that the logging system likely already has. However, this would be a far more complicated language feature, would need more BCL changes, and we don't know of any scenarios that actually need anything more than a string representing the method name. Given this, we've opted for the simpler approach of just passing the method name. ## Open questions [open]: #open-questions None [interpolated-string-spec]: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md [isle]: https://github.com/fedarovich/isle/blob/main/src/Isle/Isle.Extensions.Logging/LoggerExtensions.tt [ilogger-proposal]: https://github.com/dotnet/runtime/issues/111283 [constructor-resolution]: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#constructor-resolution ================================================ FILE: proposals/rejected/intptr-operators.md ================================================ # Operators should be exposed for `System.IntPtr` and `System.UIntPtr` Champion issue: ## Summary [summary]: #summary The CLR supports a set of operators for the `System.IntPtr` and `System.UIntPtr` types (`native int`). These operators can be seen in `III.1.5` of the Common Language Infrastructure specification (`ECMA-335`). However, these operators are not supported by C#. Language support should be provided for the full set of operators supported by `System.IntPtr` and `System.UIntPtr`. These operators are: `Add`, `Divide`, `Multiply`, `Remainder`, `Subtract`, `Negate`, `Equals`, `Compare`, `And`, `Not`, `Or`, `XOr`, `ShiftLeft`, `ShiftRight`. ## Motivation [motivation]: #motivation Today, users can easily write C# applications targeting multiple platforms using various tools and frameworks, such as: `Xamarin`, `.NET Core`, `Mono`, etc... When writing cross-platform code, it is often necessary to write interop code that interacts with a particular target platform in a specific manner. This could include writing graphics code, calling some System API, or interacting with an existing native library. This interop code often has to deal with handles, unmanaged memory, or even just platform-specific sized integers. The runtime provides support for this by defining a set of operators that can be used on the `native int` (`System.IntPtr`) and `native unsigned int` (`System.UIntPtr`) primitive types. C# has never supported these operators and so users have to work around the issue. This often increases code complexity and lowers code maintainability. As such, the language should begin to support these operators to help advance the language to better support these requirements. ## Detailed design [design]: #detailed-design The full set of operators supported are defined in `III.1.5` of the Common Language Infrastructure specification (`ECMA-335`). The specification is available here: [https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf). * A summary of the operators is provided below for convenience. * The unverifiable operators defined by the CLI spec are not listed and are not currently part of this proposal (although it may be worth considering these as well). * Providing a keyword (such as `nint` and `nuint`) nor providing a way to for literals to be declared for `System.IntPtr` and `System.UIntPtr` (such as 0n) is not part of this proposal (although it may be worth considering these as well). ### Unary Plus Operator ```csharp System.IntPtr operator +(System.IntPtr) ``` ```csharp System.UIntPtr operator +(System.UIntPtr) ``` ### Unary Minus Operator ```csharp System.IntPtr operator -(System.IntPtr) ``` ### Bitwise Complement Operator ```csharp System.IntPtr operator ~(System.IntPtr) ``` ```csharp System.UIntPtr operator ~(System.UIntPtr) ``` ### Cast Operators ```csharp explicit operator sbyte(System.IntPtr) // Truncate explicit operator short(System.IntPtr) // Truncate explicit operator int(System.IntPtr) // Truncate explicit operator long(System.IntPtr) // Sign Extend explicit operator byte(System.IntPtr) // Truncate explicit operator ushort(System.IntPtr) // Truncate explicit operator uint(System.IntPtr) // Truncate explicit operator ulong(System.IntPtr) // Zero Extend explicit operator System.IntPtr(int) // Sign Extend explicit operator System.IntPtr(long) // Truncate explicit operator System.IntPtr(uint) // Sign Extend explicit operator System.IntPtr(ulong) // Truncate explicit operator System.IntPtr(System.IntPtr) explicit operator System.IntPtr(System.UIntPtr) ``` ```csharp explicit operator sbyte(System.UIntPtr) // Truncate explicit operator short(System.UIntPtr) // Truncate explicit operator int(System.UIntPtr) // Truncate explicit operator long(System.UIntPtr) // Sign Extend explicit operator byte(System.UIntPtr) // Truncate explicit operator ushort(System.UIntPtr) // Truncate explicit operator uint(System.UIntPtr) // Truncate explicit operator ulong(System.UIntPtr) // Zero Extend explicit operator System.UIntPtr(int) // Zero Extend explicit operator System.UIntPtr(long) // Truncate explicit operator System.UIntPtr(uint) // Zero Extend explicit operator System.UIntPtr(ulong) // Truncate explicit operator System.UIntPtr(System.IntPtr) explicit operator System.UIntPtr(System.UIntPtr) ``` ### Multiplication Operator ```csharp System.IntPtr operator *(int, System.IntPtr) System.IntPtr operator *(System.IntPtr, int) System.IntPtr operator *(System.IntPtr, System.IntPtr) ``` ```csharp System.UIntPtr operator *(uint, System.UIntPtr) System.UIntPtr operator *(System.UIntPtr, uint) System.UIntPtr operator *(System.UIntPtr, System.UIntPtr) ``` ### Division Operator ```csharp System.IntPtr operator /(int, System.IntPtr) System.IntPtr operator /(System.IntPtr, int) System.IntPtr operator /(System.IntPtr, System.IntPtr) ``` ```csharp System.UIntPtr operator /(uint, System.UIntPtr) System.UIntPtr operator /(System.UIntPtr, uint) System.UIntPtr operator /(System.UIntPtr, System.UIntPtr) ``` ### Remainder Operator ```csharp System.IntPtr operator %(int, System.IntPtr) System.IntPtr operator %(System.IntPtr, int) System.IntPtr operator %(System.IntPtr, System.IntPtr) ``` ```csharp System.UIntPtr operator %(uint, System.UIntPtr) System.UIntPtr operator %(System.UIntPtr, uint) System.UIntPtr operator %(System.UIntPtr, System.UIntPtr) ``` ### Addition Operator ```csharp System.IntPtr operator +(int, System.IntPtr) System.IntPtr operator +(System.IntPtr, int) System.IntPtr operator +(System.IntPtr, System.IntPtr) ``` ```csharp System.UIntPtr operator +(uint, System.UIntPtr) System.UIntPtr operator +(System.UIntPtr, uint) System.UIntPtr operator +(System.UIntPtr, System.UIntPtr) ``` ### Subtraction Operator ```csharp System.IntPtr operator -(int, System.IntPtr) System.IntPtr operator -(System.IntPtr, int) System.IntPtr operator -(System.IntPtr, System.IntPtr) ``` ```csharp System.UIntPtr operator -(uint, System.UIntPtr) System.UIntPtr operator -(System.UIntPtr, uint) System.UIntPtr operator -(System.UIntPtr, System.UIntPtr) ``` ### Shift Operators ```csharp System.IntPtr operator <<(System.IntPtr, int) System.IntPtr operator >>(System.IntPtr, int) ``` ```csharp System.UIntPtr operator <<(System.UIntPtr, int) System.UIntPtr operator >>(System.UIntPtr, int) ``` ### Integer Comparison Operators ```csharp bool operator ==(int, System.IntPtr) bool operator ==(System.IntPtr, int) bool operator ==(System.IntPtr, System.IntPtr) bool operator !=(int, System.IntPtr) bool operator !=(System.IntPtr, int) bool operator !=(System.IntPtr, System.IntPtr) bool operator <(int, System.IntPtr) bool operator <(System.IntPtr, int) bool operator <(System.IntPtr, System.IntPtr) bool operator >(int, System.IntPtr) bool operator >(System.IntPtr, int) bool operator >(System.IntPtr, System.IntPtr) bool operator <=(int, System.IntPtr) bool operator <=(System.IntPtr, int) bool operator <=(System.IntPtr, System.IntPtr) bool operator >=(int, System.IntPtr) bool operator >=(System.IntPtr, int) bool operator >=(System.IntPtr, System.IntPtr) ``` ```csharp bool operator ==(uint, System.UIntPtr) bool operator ==(System.UIntPtr, uint) bool operator ==(System.UIntPtr, System.UIntPtr) bool operator !=(uint, System.UIntPtr) bool operator !=(System.UIntPtr, uint) bool operator !=(System.UIntPtr, System.UIntPtr) bool operator <(uint, System.UIntPtr) bool operator <(System.UIntPtr, uint) bool operator <(System.UIntPtr, System.UIntPtr) bool operator >(uint, System.UIntPtr) bool operator >(System.UIntPtr, uint) bool operator >(System.UIntPtr, System.UIntPtr) bool operator <=(uint, System.UIntPtr) bool operator <=(System.UIntPtr, uint) bool operator <=(System.UIntPtr, System.UIntPtr) bool operator >=(uint, System.UIntPtr) bool operator >=(System.UIntPtr, uint) bool operator >=(System.UIntPtr, System.UIntPtr) ``` ### Integer Logical Operators ```csharp System.IntPtr operator &(int, System.IntPtr) System.IntPtr operator &(System.IntPtr, int) System.IntPtr operator &(System.IntPtr, System.IntPtr) System.IntPtr operator |(int, System.IntPtr) System.IntPtr operator |(System.IntPtr, int) System.IntPtr operator |(System.IntPtr, System.IntPtr) System.IntPtr operator ^(int, System.IntPtr) System.IntPtr operator ^(System.IntPtr, int) System.IntPtr operator ^(System.IntPtr, System.IntPtr) ``` ```csharp System.UIntPtr operator &(uint, System.UIntPtr) System.UIntPtr operator &(System.UIntPtr, uint) System.UIntPtr operator &(System.UIntPtr, System.UIntPtr) System.UIntPtr operator |(uint, System.UIntPtr) System.UIntPtr operator |(System.UIntPtr, uint) System.UIntPtr operator |(System.UIntPtr, System.UIntPtr) System.UIntPtr operator ^(uint, System.UIntPtr) System.UIntPtr operator ^(System.UIntPtr, uint) System.UIntPtr operator ^(System.UIntPtr, System.UIntPtr) ``` ## Drawbacks [drawbacks]: #drawbacks The actual use of these operators may be small and limited to end-users who are writing lower level libraries or interop code. Most end-users would likely be consuming these lower level libraries themselves which would have the native sized integers, handles, and interop code abstracted away. As such, they would not have need of the operators themselves. ## Alternatives [alternatives]: #alternatives Have the framework implement the required operators by writing them directly in IL. Additionally, the runtime could provide intrinsic support for the operators defined by the framework, so as to better optimize the end performance. ## Unresolved questions [unresolved]: #unresolved-questions What parts of the design are still TBD? ## Design meetings Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. ================================================ FILE: proposals/rejected/intrinsics.md ================================================ # Compiler Intrinsics Champion issue: ## Summary This proposal provides language constructs that expose low level IL opcodes that cannot currently be accessed efficiently, or at all: `ldftn`, `ldvirtftn`, `ldtoken` and `calli`. These low level opcodes can be important in high performance code and developers need an efficient way to access them. ## Motivation The motivations and background for this feature are described in the following issue (as is a potential implementation of the feature): https://github.com/dotnet/csharplang/issues/191 This alternate design proposal comes after reviewing a prototype implementation of the original proposal by @msjabby as well as the use throughout a significant code base. This design was done with significant input from @mjsabby, @tmat and @jkotas. ## Detailed Design ### Allow address of to target methods Method groups will now be allowed as arguments to an address-of expression. The type of such an expression will be `void*`. ``` csharp class Util { public static void Log() { } } // ldftn Util.Log void* ptr = &Util.Log; ``` Given there is no delegate conversion here the only mechanism for filtering members in the method group is by static / instance access. If that cannot distinguish the members then a compile time error will occur. ``` csharp class Util { public void Log() { } public void Log(string p1) { } public static void Log(int i) { }; } unsafe { // Error: Method group Log has more than one applicable candidate. void* ptr1 = &Log; // Okay: only one static member to consider here. void* ptr2 = &Util.Log; } ``` The addressof expression in this context will be implemented in the following manner: - ldftn: when the method is non-virtual. - ldvirtftn: when the method is virtual. Restrictions of this feature: - Instance methods can only be specified when using an invocation expression on a value - Local functions cannot be used in `&`. The implementation details of these methods are deliberately not specified by the language. This includes whether they are static vs. instance or exactly what signature they are emitted with. ### handleof The `handleof` contextual keyword will translate a field, member or type into their equivalent `RuntimeHandle` type using the `ldtoken` instruction. The exact type of the expression will depend on the kind of the name in `handleof`: - field: `RuntimeFieldHandle` - type: `RuntimeTypeHandle` - method: `RuntimeMethodHandle` The arguments to `handleof` are identical to `nameof`. It must be a simple name, qualified name, member access, base access with a specified member, or this access with a specified member. The argument expression identifies a code definition, but it is never evaluated. The `handleof` expression is evaluated at runtime and has a return type of `RuntimeHandle`. This can be executed in safe code as well as unsafe. ``` RuntimeHandle stringHandle = handleof(string); ``` Restrictions of this feature: - Properties cannot be used in a `handleof` expression. - The `handleof` expression cannot be used when there is an existing `handleof` name in scope. For example a type, namespace, etc ... ### calli The compiler will add support for a new type of `extern` function that efficiently translates into a `.calli` instruction. The extern attribute will be marked with an attribute of the following shape: ``` csharp [AttributeUsage(AttributeTargets.Method)] public sealed class CallIndirectAttribute : Attribute { public CallingConvention CallingConvention { get; } public CallIndirectAttribute(CallingConvention callingConvention) { CallingConvention = callingConvention; } } ``` This allows developers to define methods in the following form: ``` csharp [CallIndirect(CallingConvention.Cdecl)] static extern int MapValue(string s, void *ptr); unsafe { var i = MapValue("42", &int.Parse); Console.WriteLine(i); } ``` Restrictions on the method which has the `CallIndirect` attribute applied: - Cannot have a `DllImport` attribute. - Cannot be generic. ## Open Issues ### CallingConvention The `CallIndirectAttribute` as designed uses the `CallingConvention` enum which lacks an entry for managed calling conventions. The enum either needs to be extended to include this calling convention or the attribute needs to take a different approach. ## Considerations ### Disambiguating method groups There was some discussion around features that would make it easier to disambiguate method groups passed to an address-of expression. For instance potentially adding signature elements to the syntax: ``` csharp class Util { public static void Log() { ... } public static void Log(string) { ... } } unsafe { // Error: ambiguous Log void *ptr1 = &Util.Log; // Use Util.Log(); void *ptr2 = &Util.Log(); } ``` This was rejected because a compelling case could not be made nor could a simple syntax be envisioned here. Also there is a fairly straight forward work around: simple define another method that is unambiguous and uses C# code to call into the desired function. ``` csharp class Workaround { public static void LocalLog() => Util.Log(); } unsafe { void* ptr = &Workaround.LocalLog; } ``` This becomes even simpler if `static` local functions enter the language. Then the work around could be defined in the same function that used the ambiguous address-of operation: ``` csharp unsafe { static void LocalLog() => Util.Log(); void* ptr = &Workaround.LocalLog; } ``` ### LoadTypeTokenInt32 The original proposal allowed for metadata tokens to be loaded as `int` values at compile time. Essentially have `tokenof` that has the same arguments as `handleof` but is evaluated at compile time to an `int` constant. This was rejected as it causes significant problem for IL rewrites (of which .NET has many). Such rewriters often manipulate the metadata tables in a way that could invalidate these values. There is no reasonable way for such rewriters to update these values when they are stored as simple `int` values. The underlying idea of having an opaque handle for metadata entries will continue to be explored by the runtime team. ## Future Considerations ### static local functions This refers to [the proposal](https://github.com/dotnet/csharplang/issues/1565) to allow the `static` modifier on local functions. Such a function would be guaranteed to be emitted as `static` and with the exact signature specified in source code. Such a function should be a valid argument to `&` as it contains none of the problems local functions have today. ### NativeCallableAttribute The CLR has a feature that allows for managed methods to be emitted in such a way that they are directly callable from native code. This is done by adding the `NativeCallableAttribute` to methods. Such a method is only callable from native code and hence must contain only blittable types in the signature. Calling from managed code results in a runtime error. This feature would pattern well with this proposal as it would allow: - Passing a function defined in managed code to native code as a function pointer (via address-of) with no overhead in managed or native code. - Runtime can introduce use site errors for such functions in managed code to prevent them from being invoked at compile time. ================================================ FILE: proposals/rejected/nullable-enhanced-common-type.md ================================================ # Nullable-Enhanced Common Type Champion issue: ## Summary [summary]: #summary There is a situation in which the current common-type algorithm results are counter-intuitive, and results in the programmer adding what feels like a redundant cast to the code. With this change, an expression such as `condition ? 1 : null` would result in a value of type `int?`, and an expression such as `condition ? x : 1.0` where `x` is of type `int?` would result in a value of type `double?`. ## Motivation [motivation]: #motivation This is a common cause of what feels to the programmer like needless boilerplate code. ## Detailed design [design]: #detailed-design We modify the specification for finding the best common type of a set of expressions [§11.6.3.15](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) to affect the following situations: - If one expression is of a non-nullable value type `T` and the other is a null literal, the result is of type `T?`. - If one expression is of a nullable value type `T?` and the other is of a value type `U`, and there is an implicit conversion from `T` to `U`, then the result is of type `U?`. This is expected to affect the following aspects of the language: - the ternary expression [§11.15](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1115-conditional-operator) - implicitly typed array creation expression [§11.7.15.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117155-array-creation-expressions) - inferring the return type of a lambda [§11.6.3.13](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116313-inferred-return-type) for type inference - cases involving generics, such as invoking `M(T a, T b)` as `M(1, null)`. More precisely, we change the following sections of the specification (insertions in bold, deletions in strikethrough): > #### Output type inferences > > An *output type inference* is made *from* an expression `E` *to* a type `T` in the following way: > > * If `E` is an anonymous function with inferred return type `U` ([§11.6.3.13](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116313-inferred-return-type)) and `T` is a delegate type or expression tree type with return type `Tb`, then a *lower-bound inference* ([§11.6.3.10](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116310-lower-bound-inferences)) is made *from* `U` *to* `Tb`. > * Otherwise, if `E` is a method group and `T` is a delegate type or expression tree type with parameter types `T1...Tk` and return type `Tb`, and overload resolution of `E` with the types `T1...Tk` yields a single method with return type `U`, then a *lower-bound inference* is made *from* `U` *to* `Tb`. > * **Otherwise, if `E` is an expression with nullable value type `U?`, then a *lower-bound inference* is made *from* `U` *to* `T` and a *null bound* is added to `T`. ** > * Otherwise, if `E` is an expression with type `U`, then a *lower-bound inference* is made *from* `U` *to* `T`. > * **Otherwise, if `E` is a constant expression with value `null`, then a *null bound* is added to `T`** > * Otherwise, no inferences are made. > #### Fixing > > An *unfixed* type variable `Xi` with a set of bounds is *fixed* as follows: > > * The set of *candidate types* `Uj` starts out as the set of all types in the set of bounds for `Xi`. > * We then examine each bound for `Xi` in turn: For each exact bound `U` of `Xi` all types `Uj` which are not identical to `U` are removed from the candidate set. For each lower bound `U` of `Xi` all types `Uj` to which there is *not* an implicit conversion from `U` are removed from the candidate set. For each upper bound `U` of `Xi` all types `Uj` from which there is *not* an implicit conversion to `U` are removed from the candidate set. > * If among the remaining candidate types `Uj` there is a unique type `V` from which there is an implicit conversion to all the other candidate types, then ~~`Xi` is fixed to `V`.~~ > - **If `V` is a value type and there is a *null bound* for `Xi`, then `Xi` is fixed to `V?`** > - **Otherwise `Xi` is fixed to `V`** > * Otherwise, type inference fails. ## Drawbacks [drawbacks]: #drawbacks There may be some incompatibilities introduced by this proposal. ## Alternatives [alternatives]: #alternatives None. ## Unresolved questions [unresolved]: #unresolved-questions - [ ] What is the severity of incompatibility introduced by this proposal, if any, and how can it be moderated? ## Design meetings None. ================================================ FILE: proposals/rejected/param-nullchecking.md ================================================ # Parameter Null Checking Champion issue: ## Summary This proposal provides a simplified syntax for validating method arguments are not `null` and throwing `ArgumentNullException` appropriately. ## Motivation The work on designing nullable reference types has caused us to examine the code necessary for `null` argument validation. Given that NRT doesn't affect code execution developers still must add `if (arg is null) throw` boiler plate code even in projects which are fully `null` clean. This gave us the desire to explore a minimal syntax for argument `null` validation in the language. While this `null` parameter validation syntax is expected to pair frequently with NRT, the proposal is fully independent of it. The syntax can be used independent of `#nullable` directives. ## Detailed Design ### Null validation parameter syntax The bang-bang operator, `!!`, can be positioned after a parameter name in a parameter list and this will cause the C# compiler to emit `null` checking code for that parameter. This is referred to as `null` validation parameter syntax. For example: ``` csharp void M(string name!!) { ... } ``` Will be translated into code similar to the following: ``` csharp void M(string name) { if (name is null) { throw new ArgumentNullException(nameof(name)); } ... } ``` There are a few guidelines limiting where `!!` can be used: 1. Only a parameter of something with an implementation can use it. For example, an abstract method parameter cannot use `!!`. Further examples include: - extern method parameters - delegate parameters - interface method parameters when the method is not a DIM 2. It must be possible to include an equivalent "check then throw" of the given parameter at the beginning of the method, ignoring syntactic limitations such as the need to replace an expression body with a block body. Because of (2), the `!!` operator cannot be used on a discard. ``` csharp System.Action lambda = (_!!, _!!) => { }; // error ``` Also because of (2), the `!!` operator cannot be used on an `out` parameter, but it can be used on a `ref` or `in` parameter. ``` csharp void M1(ref string x!!) { } // ok void M2(in string y!!) { } // ok void M3(out string z!!) { } // error ``` Declarations that have parameters and implementations can generally use `!!`. Therefore, it's permitted to use it on an indexer parameter, and the behavior is that all the indexer's accessors will insert a null check. ``` csharp public string this[string key!!] { get { ... } set { ... } } // ok ``` The implementation behavior must be that if the parameter is null, it creates and throws an `ArgumentNullException` with the parameter name as a constructor argument. The implementation is free to use any strategy that achieves this. This could result in observable differences between different compliant implementations, such as whether calls to helper methods are present above the call to the method with the null-checked parameter in the exception stack trace. We make these allowances because parameter null checks are used frequently in libraries with tight performance and size constraints. For example, to optimize code size, inlining, etc., the implementation may use helper methods to perform the null check a la the [ArgumentNullException.ThrowIfNull](https://github.com/dotnet/runtime/blob/1d08e154b942a41e72cbe044e01fff8b13c74496/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs#L56-L69) methods. The generated `null` check will occur before any developer authored code in the method. When multiple parameters contain the `!!` operator then the checks will occur in the same order as the parameters are declared. ``` csharp void M(string p1, string p2) { if (p1 is null) { throw new ArgumentNullException(nameof(p1)); } if (p2 is null) { throw new ArgumentNullException(nameof(p2)); } ... } ``` The check will be specifically for reference equality to `null`, it does not invoke `==` or any user defined operators. This also means the `!!` operator can only be added to parameters whose type can be tested for equality against `null`. This means it can't be used on a parameter whose type is known to be a value type. ``` csharp // Error: Cannot use !! on parameters who types derive from System.ValueType void G(T arg!!) where T : struct { } ``` In the case of a constructor, the `null` validation will occur before any other code in the constructor. That includes: - Chaining to other constructors with `this` or `base` - Field initializers which implicitly occur in the constructor For example: ``` csharp class C { string field = GetString(); C(string name!!): this(name) { ... } } ``` Will be roughly translated into the following: ``` csharp class C { C(string name) if (name is null) { throw new ArgumentNullException(nameof(name)); } field = GetString(); :this(name); ... } ``` Note: this is not legal C# code but instead just an approximation of what the implementation does. The `null` validation parameter syntax will also be valid on lambda parameter lists. This is valid even in the single parameter syntax that lacks parens. ``` csharp void G() { // An identity lambda which throws on a null input Func s = x!! => x; } ``` `async` methods can have null-checked parameters. The null check occurs when the method is invoked. The syntax is also valid on parameters to iterator methods. Unlike other code in the iterator the `null` validation will occur when the iterator method is invoked, not when the underlying enumerator is walked. This is true for traditional or `async` iterators. ``` csharp class Iterators { IEnumerable GetCharacters(string s!!) { foreach (var c in s) { yield return c; } } void Use() { // The invocation of GetCharacters will throw IEnumerable e = GetCharacters(null); } } ``` The `!!` operator can only be used for parameter lists which have an associated method body. This means it cannot be used in an `abstract` method, `interface`, `delegate` or `partial` method definition. ### Extending is null The types for which the expression `is null` is valid will be extended to include unconstrained type parameters. This will allow it to fill the intent of checking for `null` on all types which a `null` check is valid. Specifically that is types which are not definitely known to be value types. For example Type parameters which are constrained to `struct` cannot be used with this syntax. ``` csharp void NullCheck(T1 p1, T2 p2) where T2 : struct { // Okay: T1 could be a class or struct here. if (p1 is null) { ... } // Error if (p2 is null) { ... } } ``` The behavior of `is null` on a type parameter will be the same as `== null` today. In the cases where the type parameter is instantiated as a value type the code will be evaluated as `false`. For cases where it is a reference type the code will do a proper `is null` check. ### Intersection with Nullable Reference Types Any parameter which has a `!!` operator applied to it's name will start with the nullable state being not `null`. This is true even if the type of the parameter itself is potentially `null`. That can occur with an explicitly nullable type, such as say `string?`, or with an unconstrained type parameter. When a `!!` syntax on parameters is combined with an explicitly nullable type on the parameter then a warning will be issued by the compiler: ``` csharp void WarnCase( string? name!!, // Warning: combining explicit null checking with a nullable type T value1!! // Okay ) ``` ## Open Issues None ## Considerations ### Constructors The code generation for constructors means there is a small, but observable, behavior change when moving from standard `null` validation today and the `null` validation parameter syntax (`!!`). The `null` check in standard validation occurs after both field initializers and any `base` or `this` calls. This means a developer can't necessarily migrate 100% of their `null` validation to the new syntax. Constructors at least require some inspection. After discussion though it was decided that this is very unlikely to cause any significant adoption issues. It's more logical that the `null` check run before any logic in the constructor does. Can revisit if significant compat issues are discovered. ### Warning when mixing ? and ! There was a lengthy discussion on whether or not a warning should be issued when the `!!` syntax is applied to a parameter which is explicitly typed to a nullable type. On the surface it seems like a nonsensical declaration by the developer but there are cases where type hierarchies could force developers into such a situation. Consider the following class hierarchy across a series of assemblies (assuming all are compiled with `null` checking enabled): ``` csharp // Assembly1 abstract class C1 { protected abstract void M(object o); } // Assembly2 abstract class C2 : C1 { } // Assembly3 abstract class C3 : C2 { protected override void M(object o!!) { ... } } ``` Here the author of `C3` decided to add `null` validation to the parameter `o`. This is completely in line with how the feature is intended to be used. Now imagine at a later date the author of Assembly2 decides to add the following override: ``` csharp // Assembly2 abstract class C2 : C1 { protected override void M(object? o) { ... } } ``` This is allowed by nullable reference types as it's legal to make the contract more flexible for input positions. The NRT feature in general allows for reasonable co/contravariance on parameter / return nullability. However the language does the co/contravariance checking based on the most specific override, not the original declaration. This means the author of Assembly3 will get a warning about the type of `o` not matching and will need to change the signature to the following to eliminate it: ``` csharp // Assembly3 abstract class C3 : C2 { protected override void M(object? o!!) { ... } } ``` At this point the author of Assembly3 has a few choices: - They can accept / suppress the warning about `object?` and `object` mismatch. - They can accept / suppress the warning about `object?` and `!!` mismatch. - They can just remove the `null` validation check (delete `!!` and do explicit checking) This is a real scenario but for now the idea is to move forward with the warning. If it turns out the warning happens more frequently than we anticipate then we can remove it later (the reverse is not true). ### Implicit property setter arguments The `value` argument of a parameter is implicit and does not appear in any parameter list. That means it cannot be a target of this feature. The property setter syntax could be extended to include a parameter list to allow the `!!` operator to be applied. But that cuts against the idea of this feature making `null` validation simpler. As such the implicit `value` argument just won't work with this feature. ## Future Considerations None ================================================ FILE: proposals/rejected/params-span.md ================================================ # `params ReadOnlySpan` Champion issue: ## Summary Avoid heap allocation of implicitly allocated `params` arrays. ## Motivation `params` array parameters provide a convenient way to call a method that takes an arbitrary length list of arguments. However, using an array type for the parameter means the compiler must implicitly allocate an array on the heap at each call site. If we extend `params` support to `ReadOnlySpan`, where the implicitly created span cannot escape the calling method, the underlying buffer at the call site may be created on the stack instead. If overload resolution prefers `params ReadOnlySpan` over `params T[]`, then adding a `params ReadOnlySpan` overload to an existing API would reduce allocations when recompiling callers. The benefit of `params ReadOnlySpan` is primarily for APIs that don't already include optimized overloads. Commonly used APIs such as `Console.WriteLine()` and `StringBuilder.AppendFormat()` that already have non-`params` overloads for callers with few arguments would benefit less. ```csharp // Existing API with params and non-params overloads public static class Console { public static void WriteLine(string value); public static void WriteLine(string format, object arg0); public static void WriteLine(string format, object arg0, object arg1); public static void WriteLine(string format, object arg0, object arg1, object arg2); public static void WriteLine(string format, params object[] arg); } // New API with single overload abstract class Logger { public abstract void Log(string format, params ReadOnlySpan args); } ``` ## Detailed design ### Extending `params` A `params` parameter type may be `System.ReadOnlySpan` for a valid type argument `T`. A call in [_expanded form_](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11642-applicable-function-member) to a method with a `params ReadOnlySpan` parameter will result in a `ReadOnlySpan` instance implicitly created by the compiler. ```csharp log.Log("({0}, {1}, {2})", x, y, z); // Potentially emitted as: log.Log("({0}, {1}, {2})", new System.ReadOnlySpan(new object[] { x, y, z })); ``` A `params` parameter must be the last parameter in the method signature and cannot include a `ref`, `out`, or `in` modifier. Two overloads cannot differ by `params` modifier alone. `params` parameters are marked in metadata with a `System.ParamArrayAttribute`. A `params ReadOnlySpan` is implicitly `scoped`. The parameter _cannot_ be annotated with `[UnscopedRef]` and _cannot_ be declared `scoped` explicitly. Within the `params` method, the compiler will use escape analysis to report diagnostics if the span is captured or returned. ### Overload resolution Overload resolution prefers overloads that are applicable in [_normal form_](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11642-applicable-function-member) over [_expanded form_](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11642-applicable-function-member). [_Better function member_](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11643-better-function-member) will prefer `params ReadOnlySpan` over `params T[]` for overloads applicable in [_expanded form_](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11642-applicable-function-member): > In case the parameter type sequences `{P1, P2, ..., Pn}` and `{Q1, Q2, ..., Qn}` are equivalent (i.e. each `Pi` has an identity conversion to the corresponding `Qi`), the following tie-breaking rules are applied, in order, to determine the better function member. > > * If `Mp` is a non-generic method and `Mq` is a generic method, then `Mp` is better than `Mq`. > * ... > * Otherwise if one member is a non-lifted operator and the other is a lifted operator, the non-lifted one is better. > * **Otherwise, if both methods have `params` parameters and are applicable only in their expanded forms, and the `params` types are distinct types with equivalent element type (there is an identity conversion between element types), the more specific `params` type is the first of:** > * **`ReadOnlySpan`** > * **`T[]`** > * Otherwise, neither function member is better. Overload resolution will prefer `params T[]` at callsites where the type substituted for `T` is not a valid generic type argument since a `params ReadOnlySpan` will not be applicable in those cases. The types that are valid as array elements but not as generic type arguments are: - _pointers_ and - _function pointers_. ### Allocation and reuse The compiler will include the following optimizations for implicitly allocated `params` buffers. Additional optimizations may be added in future for cases where the compiler can determine there are _no reachable aliases_ to the buffer. The compiler _will allocate the buffer on the stack_ for a `params ReadOnlySpan` argument when - the parameter is implicitly or explicitly `scoped` _which is required from source_, - the argument is implicitly allocated, and - the runtime supports [_fixed size buffers of managed types_](#runtime-stack-allocation). The compiler _will reuse the buffer_ allocated on the stack for implicit arguments to `params ReadOnlySpan` and `params ReadOnlySpan` when there is an identity conversion between element types `T` and `U`. For target frameworks that do not support _fixed size buffers_, implicitly allocated buffers will be allocated on the heap. The parameter must be `scoped` to ensure the implicitly allocated buffer is not returned or aliased which might prevent allocating on the stack or reusing the buffer. The buffer is allocated on the stack regardless of argument length or element size. The buffer is allocated to the length of the longest `params` argument across all applicable uses for matching `T`. To _opt out_ of compiler optimizations at a call site, the calling code should allocate the span explicitly (directly or indirectly using `new ReadOnlySpan(new[] { ... })`). The span for a particular `params` argument will be a slice of the buffer matching the argument length at that call site. At runtime, the stack space for the buffer is reserved for the lifetime of the method, regardless of where in the method the buffer is used. Reuse is within the same method and thread of execution only and may be across distinct call sites _or_ repeated calls from the same call site. Before exiting a C# scope, the compiler ensures the buffer contains no references from the scope. ### Runtime stack allocation There is a runtime request to support fields of [_fixed size buffers of managed types_](https://github.com/dotnet/runtime/issues/61135). With _fixed size buffer_ fields, we can define `ref struct` types that allow using locals for stack allocated buffers. For example, consider a `FixedSizeBuffer3` type defined below which includes an inline buffer with 3 items: ```csharp ref struct FixedSizeBuffer3 { public fixed T Items[3]; // pseudo-code for inline fixed size buffer } ``` With that type, a call to `log.Log("({0}, {1}, {2})", x, y, z)` could be emitted as: ```csharp var _tmp = new FixedSizeBuffer3(); _tmp.Items[0] = x; _tmp.Items[1] = y; _tmp.Items[2] = z; // Logger.Log(string format, params ReadOnlySpan args); log.Log("({0}, {1}, {2})", MemoryMarshal.CreateReadOnlySpan(ref _tmp.Items, 3)); ``` Ideally the base class library will provide types such as `FixedSizeBuffer1`, `FixedSizeBuffer2`, etc. for a limited number of span lengths. And if the compilation requires buffers for other span lengths, the compiler will generate and emit the additional types. ### Example Consider the following extension method for logging the contents of a dictionary: ```csharp static void LogDictionary(this Logger log, Dictionary dictionary) { log.Log("Dictionary"); foreach (var (k, v) in dictionary) log.Log("{0}, {1}", k, v); log.Log("Count = {0}", dictionary.Count); } ``` The method could be lowered to: ```csharp static void LogDictionary(this Logger log, Dictionary dictionary) { FixedSizeBuffer2 _tmp = new FixedSizeBuffer2(); log.Log("Dictionary", new ReadOnlySpan(Array.Empty()); // no reuse foreach (var (k, v) in dictionary) { _tmp.Items[0] = k; _tmp.Items[1] = v; log.Log("{0}, {1}", MemoryMarshal.CreateReadOnlySpan(ref _tmp.Items, 2)); // reuse MemoryMarshal.CreateSpan(ref _tmp.Items, 2).Clear(); // clear } _tmp.Items[0] = dictionary.Count; log.Log("Count = {0}", MemoryMarshal.CreateReadOnlySpan(ref _tmp.Items, 1)); // reuse slice } ``` ## Open issues ### Support `params scoped T[]`? Allow a `params T[]` to be marked as `scoped` and allocate argument arrays on the stack at call sites? That would avoid heap allocation at each call site, but allocations could only be reused at call sites with matching argument type _and length_. ### Support `params Span`? Support `params Span` to allow the `params` method to modify the span contents, even though the effects are only observable at call sites that explicitly allocate the span? ### Support `params IEnumerable`, etc.? If we're extending `params` to support `ReadOnlySpan`, should we also support `params` parameters of other collection types, including interfaces and concrete types? The reason to support `params ReadOnlySpan` is to improve performance of existing callers by allowing stack allocation of `params` buffers. The reason to extend `params` to other collection types is not performance but to support implicit collections at call sites while _also_ supporting APIs or call sites that use collections other than arrays. For APIs, supporting `params` and other collection types is already possible through overloads: ```csharp abstract class Logger { public abstract void Log(string format, IEnumerable args); public void Log(string format, params object[] args) { Log(format, (IEnumerable)args); } } ``` And for callers where the API takes an explicit collection type rather than `params`, [_collection literals_](https://github.com/dotnet/csharplang/issues/5354) provide a simple syntax that reduces the need for `params`. ```csharp log.Log("({0}, {1}, {2})", [x, y, z]); abstract class Logger { public abstract void Log(string format, IEnumerable args); } ``` That said, this proposal doesn't prevent extending `params` to other types in the future. ### Opting out Should we allow opt-ing out of _implicit allocation_ on the call stack? Perhaps an attribute that can be applied to a method, type, or assembly. ## Related proposals - https://github.com/dotnet/csharplang/issues/1757 - https://github.com/dotnet/csharplang/blob/main/proposals/format.md#extending-params ================================================ FILE: proposals/rejected/readonly-locals.md ================================================ # readonly locals and parameters Champion issue: ## Summary [summary]: #summary Allow locals and parameters to be annotated as readonly in order to prevent shallow mutation of those locals and parameters. ## Motivation [motivation]: #motivation Today, the `readonly` keyword can be applied to fields; this has the effect of ensuring that a field can only be written to during construction (static construction in the case of a static field, or instance construction in the case of an instance field), which helps developers avoid mistakes by accidentally overwriting state which should not be modified. But fields aren't the only places developers want to ensure that values aren't mutated. In particular, it's common to create a local variable to store temporary state, and accidentally updating that temporary state can result in erroneous calculations and other such bugs, especially when such "locals" are captured in lambdas, at which point they are lifted to fields, but there's no way today to mark such lifted fields as `readonly`. ## Detailed design [design]: #detailed-design Locals will be annotatable as `readonly` as well, with the compiler ensuring that they're only set at the time of declaration (certain locals in C# are already implicitly readonly, such as the iteration variable in a 'foreach' loop or the used variable in a 'using' block, but currently a developer has no ability to mark other locals as `readonly`). Such `readonly` locals must have an initializer: ```csharp readonly long maxBytesToDelete = (stream.LimitBytes - stream.MaxBytes) / 10; ... maxBytesToDelete = 0; // Error: can't assign to readonly locals outside of declaration ``` And as shorthand for `readonly var`, the existing contextual keyword `let` may be used, e.g. ```csharp let maxBytesToDelete = (stream.LimitBytes - stream.MaxBytes) / 10; ... maxBytesToDelete = 0; // Error: can't assign to readonly locals outside of declaration ``` There are no special constraints for what the initializer can be, and can be anything currently valid as an initializer for locals, e.g. ```csharp readonly T data = arg1 ?? arg2; ``` `readonly` on locals is particularly valuable when working with lambdas and closures. When an anonymous method or lambda accesses local state from the enclosing scope, that state is captured into a closure by the compiler, which is represented by a "display class." Each "local" that's captured is a field in this class, yet because the compiler is generating this field on your behalf, you have no opportunity to annotate it as `readonly` in order to prevent the lambda from erroneously writing to the "local" (in quotes because it's really not a local, at least not in the resulting MSIL). With `readonly` locals, the compiler can prevent the lambda from writing to local, which is particularly valuable in scenarios involving multithreading where an erroneous write could result in a dangerous but rare and hard-to-find concurrency bug. ```csharp readonly long index = ...; Parallel.ForEach(data, item => { T element = item[index]; index = 0; // Error: can't assign to readonly locals outside of declaration }); ``` As a special form of local, parameters will also be annotatable as `readonly`. This would have no effect on what the caller of the method is able to pass to the parameter (just as there's no constraint on what values may be stored into a `readonly` field), but as with any `readonly` local, the compiler would prohibit code from writing to the parameter after declaration, which means the body of the method is prohibited from writing to the parameter. ```csharp public void Update(readonly int index = 0) // Default values are ok though not required { ... index = 0; // Error: can't assign to readonly parameters ... } ``` `readonly` parameters do not affect the signature/metadata emitted by the compiler for that method, and simply affect how the compiler handles the compilation of the method's body. Thus, for example, a base virtual method could have a `readonly` parameter, and that parameter could be writable in an override. As with fields, `readonly` for locals and parameters is shallow, affecting the storage location but not transitively affecting the object graph. However, also as with fields, calling a method on a `readonly` local/parameter struct will actually make a copy of the struct and call the method on the copy, in order to avoid internal mutation of `this`. `readonly` locals and parameters can't be passed as `ref` or `out` arguments, unless/until `ref readonly` is also supported. ## Alternatives [alternatives]: #alternatives - `val` could be used as an alternative shorthand to `let`. ## Unresolved questions [unresolved]: #unresolved-questions - `readonly ref` / `ref readonly` / `readonly ref readonly`: I've left the question of how to handle `ref readonly` as separate from this proposal. - This proposal does not tackle readonly structs / immutable types. That is left for a separate proposal. ## Design meetings - Briefly discussed on Jan 21, 2015 () ================================================ FILE: proposals/rejected/records.md ================================================ # records Champion issue: ## Summary [summary]: #summary Records are a new, simplified declaration form for C# class and struct types that combine the benefits of a number of simpler features. We describe the new features (caller-receiver parameters and *with-expression*s), give the syntax and semantics for record declarations, and then provide some examples. ## Motivation [motivation]: #motivation A significant number of type declarations in C# are little more than aggregate collections of typed data. Unfortunately, declaring such types requires a great deal of boilerplate code. *Records* provide a mechanism for declaring a datatype by describing the members of the aggregate along with additional code or deviations from the usual boilerplate, if any. See [Examples](#examples), below. ## Detailed design [design]: #detailed-design ### caller-receiver parameters Currently a method parameter's *default-argument* must be - a *constant-expression*; or - an expression of the form `new S()` where `S` is a value type; or - an expression of the form `default(S)` where `S` is a value type We extend this to add the following - an expression of the form `this.Identifier` This new form is called a *caller-receiver default-argument*, and is allowed only if all of the following are satisfied - The method in which it appears is an instance method; and - The expression `this.Identifier` binds to an instance member of the enclosing type, which must be either a field or a property; and - The member to which it binds (and the `get` accessor, if it is a property) is at least as accessible as the method; and - The type of `this.Identifier` is implicitly convertible by an identity or nullable conversion to the type of the parameter (this is an existing constraint on *default-argument*). When an argument is omitted from an invocation of a function member for a corresponding optional parameter with a *caller-receiver default-argument*, the value of the receiver's member is implicitly passed. > **Design Notes**: the main reason for the caller-receiver parameter is to support the *with-expression*. The idea is that you can declare a method like this > ```cs > class Point > { > public readonly int X; > public readonly int Y; > public Point With(int x = this.X, int y = this.Y) => new Point(x, y); > // etc > } > ``` > and then use it like this > ```cs > Point p = new Point(3, 4); > p = p.With(x: 1); > ``` > To create a new `Point` just like an existing `Point` but with the value of `X` changed. > > It is an open question whether or not the syntactic form of the *with-expression* is worth adding once we have support for caller-receiver parameters, so it is possible we would do this *instead of* rather than *in addition to* the *with-expression*. - [ ] **Open issue**: What is the order in which a *caller-receiver default-argument* is evaluated with respect to other arguments? Should we say that it is unspecified? ### with-expressions A new expression form is proposed: ```antlr primary_expression : with_expression ; with_expression : primary_expression 'with' '{' with_initializer_list '}' ; with_initializer_list : with_initializer | with_initializer ',' with_initializer_list ; with_initializer : identifier '=' expression ; ``` The token `with` is a new context-sensitive keyword. Each *identifier* on the left of a *with_initializer* must bind to an accessible instance field or property of the type of the *primary_expression* of the *with_expression*. There may be no duplicated name among these identifiers of a given *with_expression*. A *with_expression* of the form > *e1* `with` `{` *identifier* = *e2*, ... `}` is treated as an invocation of the form > *e1*`.With(`*identifier2*`:` *e2*, ...`)` Where, for each method named `With` that is an accessible instance member of *e1*, we select *identifier2* as the name of the first parameter in that method that has a caller-receiver parameter that is the same member as the instance field or property bound to *identifier*. If no such parameter can be identified that method is eliminated from consideration. The method to be invoked is selected from among the remaining candidates by overload resolution. > **Design Notes**: Given caller-receiver parameters, many of the benefits of the *with-expression* are available without this special syntax form. We are therefore considering whether or not it is needed. Its main benefit is allowing one to program in terms of the names of fields and properties, rather than in terms of the names of parameters. In this way we improve both readability and the quality of tooling (e.g. go-to-definition on the identifier of a *with_expression* would navigate to the property rather than to a method parameter). - [ ] **Open issue**: This description should be modified to support extension methods. ### pattern-matching See the [Pattern Matching Specification](csharp-8.0/patterns.md#positional-pattern) for a specification of `Deconstruct` and its relationship to pattern-matching. > **Design Notes**: By virtue of the compiler-generated `Deconstruct` as specified herein, and the specification for pattern-matching, a record declaration > ```cs > public class Point(int X, int Y); > ``` > will support positional pattern-matching as follows > ```cs > Point p = new Point(3, 4); > if (p is Point(3, var y)) { // if X is 3 > Console.WriteLine(y); // print Y > } > ``` ### record type declarations The syntax for a `class` or `struct` declaration is extended to support value parameters; the parameters become properties of the type: ```antlr class_declaration : attributes? class_modifiers? 'partial'? 'class' identifier type_parameter_list? record_parameters? record_class_base? type_parameter_constraints_clauses? class_body ; struct_declaration : attributes? struct_modifiers? 'partial'? 'struct' identifier type_parameter_list? record_parameters? struct_interfaces? type_parameter_constraints_clauses? struct_body ; record_class_base : class_type record_base_arguments? | interface_type_list | class_type record_base_arguments? ',' interface_type_list ; record_base_arguments : '(' argument_list? ')' ; record_parameters : '(' record_parameter_list? ')' ; record_parameter_list : record_parameter | record_parameter ',' record_parameter_list ; record_parameter : attributes? type identifier record_property_name? default_argument? ; record_property_name : ':' identifier ; class_body : '{' class_member_declarations? '}' | ';' ; struct_body : '{' struct_members_declarations? '}' | ';' ; ``` > **Design Notes**: Because record types are often useful without the need for any members explicitly declared in a class-body, we modify the syntax of the declaration to allow a body to be simply a semicolon. A class (struct) declared with the *record-parameters* is called a *record class* (*record struct*), either of which is a *record type*. - [ ] **Open issue**: We need to include *primary_constructor_body* in the grammar so that it can appear inside a record type declaration. - [ ] **Open issue**: What are the name conflict rules for the parameter names? Presumably one is not allowed to conflict with a type parameter or another *record-parameter*. - [ ] **Open issue**: We need to specify the scope of the record-parameters. Where can they be used? Presumably within instance field initializers and *primary_constructor_body* at least. - [ ] **Open issue**: Can a record type declaration be partial? If so, must the parameters be repeated on each part? #### Members of a record type In addition to the members declared in the *class-body*, a record type has the following additional members: ##### Primary Constructor A record type has a `public` constructor whose signature corresponds to the value parameters of the type declaration. This is called the *primary constructor* for the type, and causes the implicitly declared *default constructor* to be suppressed. At runtime the primary constructor * initializes compiler-generated backing fields for the properties corresponding to the value parameters (if these properties are compiler-provided; [see 1.1.2](#1.1.2)); then * executes the instance field initializers appearing in the *class-body*; and then * invokes a base class constructor: * If there are arguments in the *record_base_arguments*, a base constructor selected by overload resolution with these arguments is invoked; * Otherwise a base constructor is invoked with no arguments. * executes the body of each *primary_constructor_body*, if any, in source order. - [ ] **Open issue**: We need to specify that order, particularly across compilation units for partials. - [ ] **Open Issue**: We need to specify that every explicitly declared constructor must chain to the primary constructor. - [ ] **Open issue**: Should it be allowed to change the access modifier on the primary constructor? - [ ] **Open issue**: In a record struct, it is an error for there to be no record parameters? ##### Primary constructor body ```antlr primary_constructor_body : attributes? constructor_modifiers? identifier block ; ``` A *primary_constructor_body* may only be used within a record type declaration. The *identifier* of a *primary_constructor_body* shall name the record type in which it is declared. The *primary_constructor_body* does not declare a member on its own, but is a way for the programmer to provide attributes for, and specify the access of, a record type's primary constructor. It also enables the programmer to provide additional code that will be executed when an instance of the record type is constructed. - [ ] **Open issue**: We should note that a struct default constructor bypasses this. - [ ] **Open issue**: We should specify the execution order of initialization. - [ ] **Open issue**: Should we allow something like a *primary_constructor_body* (presumably without attributes and modifiers) in a non-record type declaration, and treat it like we would the code of an instance field initializer? ##### Properties For each record parameter of a record type declaration there is a corresponding `public` property member whose name and type are taken from the value parameter declaration. Its name is the *identifier* of the *record_property_name*, if present, or the *identifier* of the *record_parameter* otherwise. If no concrete (i.e. non-abstract) public property with a `get` accessor and with this name and type is explicitly declared or inherited, it is produced by the compiler as follows: * For a *record struct* or a `sealed` *record class*: * A `private` `readonly` field is produced as a backing field for a `readonly` property. Its value is initialized during construction with the value of the corresponding primary constructor parameter. * The property's `get` accessor is implemented to return the value of the backing field. * Each "matching" inherited virtual property's `get` accessor is overridden. > **Design notes**: In other words, if you extend a base class or implement an interface that declares a public abstract property with the same name and type as a record parameter, that property is overridden or implemented. - [ ] **Open issue**: Should it be possible to change the access modifier on a property when it is explicitly declared? - [ ] **Open issue**: Should it be possible to substitute a field for a property? ##### Object Methods For a *record struct* or a `sealed` *record class*, implementations of the methods `object.GetHashCode()` and `object.Equals(object)` are produced by the compiler unless provided by the user. - [ ] **Open issue**: We should precisely specify their implementation. - [ ] **Open issue**: We should also add the interface `IEquatable` for the record type and specify that implementations are provided. - [ ] **Open issue**: We should also specify that we implement every `IEquatable.Equals`. - [ ] **Open issue**: We should specify precisely how we solve the problem of Equals in the face of record inheritance: specifically how we generate equality methods such that they are symmetric, transitive, reflexive, etc. - [ ] **Open issue**: It has been proposed that we implement `operator ==` and `operator !=` for record types. - [ ] **Open issue**: Should we auto-generate an implementation of `object.ToString`? ##### `Deconstruct` A record type has a compiler-generated `public` method `void Deconstruct` unless one with any signature is provided by the user. Each parameter is an `out` parameter of the same name and type as the corresponding parameter of the record type. The compiler-provided implementation of this method shall assign each `out` parameter with the value of the corresponding property. See [the pattern-matching specification](csharp-8.0/patterns.md#positional-pattern) for the semantics of `Deconstruct`. ##### `With` method Unless there is a user-declared member named `With` declared, a record type has a compiler-provided method named `With` whose return type is the record type itself, and containing one value parameter corresponding to each *record-parameter* in the same order that these parameters appear in the record type declaration. Each parameter shall have a *caller-receiver default-argument* of the corresponding property. In an `abstract` record class, the compiler-provided `With` method is abstract. In a record struct, or a sealed record class, the compiler-provided `With` method is `sealed`. Otherwise the compiler-provided `With` method is `virtual and its implementation shall return a new instance produced by invoking the primary constructor with the parameters as arguments to create a new instance from the parameters, and return that new instance. - [ ] **Open issue**: We should also specify under what conditions we override or implement inherited virtual `With` methods or `With` methods from implemented interfaces. - [ ] **Open issue**: We should say what happens when we inherit a non-virtual `With` method. > **Design notes**: Because record types are by default immutable, the `With` method provides a way of creating a new instance that is the same as an existing instance but with selected properties given new values. For example, given > ```cs > public class Point(int X, int Y); > ``` > there is a compiler-provided member > ```cs > public virtual Point With(int X = this.X, int Y = this.Y) => new Point(X, Y); > ``` > Which enables an variable of the record type > ```cs > var p = new Point(3, 4); > ``` > to be replaced with an instance that has one or more properties different > ```cs > p = p.With(X: 5); > ``` > This can also be expressed using the *with_expression*: > ```cs > p = p with { X = 5 }; > ``` ### Examples #### Compatibility of record types Because the programmer can add members to a record type declaration, it is often possible to change the set of record elements without affecting existing clients. For example, given an initial version of a record type ```cs // v1 public class Person(string Name, DateTime DateOfBirth); ``` A new element of the record type can be compatibly added in the next revision of the type without affecting binary or source compatibility: ```cs // v2 public class Person(string Name, DateTime DateOfBirth, string HomeTown) { // Note: below operations added to retain binary compatibility with v1 public Person(string Name, DateTime DateOfBirth) : this(Name, DateOfBirth, string.Empty) {} public static void operator is(Person self, out string Name, out DateTime DateOfBirth) { Name = self.Name; DateOfBirth = self.DateOfBirth; } public Person With(string Name, DateTime DateOfBirth) => new Person(Name, DateOfBirth); } ``` #### record struct example This record struct ```cs public struct Pair(object First, object Second); ``` is translated to this code ```cs public struct Pair : IEquatable { public object First { get; } public object Second { get; } public Pair(object First, object Second) { this.First = First; this.Second = Second; } public bool Equals(Pair other) // for IEquatable { return Equals(First, other.First) && Equals(Second, other.Second); } public override bool Equals(object other) { return (other as Pair)?.Equals(this) == true; } public override int GetHashCode() { return (First?.GetHashCode()*17 + Second?.GetHashCode()).GetValueOrDefault(); } public Pair With(object First = this.First, object Second = this.Second) => new Pair(First, Second); public void Deconstruct(out object First, out object Second) { First = self.First; Second = self.Second; } } ``` - [ ] **Open issue**: should the implementation of Equals(Pair other) be a public member of Pair? - [ ] **Open issue**: This implementation of `Equals` is not symmetric in the face of inheritance. - [ ] **Open issue**: Should a record declare `operator ==` and `operator !=`? > **Design notes**: Because one record type can inherit from another, and this implementation of `Equals` would not be symmetric in that case, it is not correct. We propose to implement equality this way: > ```cs > public bool Equals(Pair other) // for IEquatable > { > return other != null && EqualityContract == other.EqualityContract && > Equals(First, other.First) && Equals(Second, other.Second); > } > protected virtual Type EqualityContract => typeof(Pair); > ``` > Derived records would `override EqualityContract`. The less attractive alternative is to restrict inheritance. #### sealed record example This sealed record class ```cs public sealed class Student(string Name, decimal Gpa); ``` is translated into this code ```cs public sealed class Student : IEquatable { public string Name { get; } public decimal Gpa { get; } public Student(string Name, decimal Gpa) { this.Name = Name; this.Gpa = Gpa; } public bool Equals(Student other) // for IEquatable { return other != null && Equals(Name, other.Name) && Equals(Gpa, other.Gpa); } public override bool Equals(object other) { return this.Equals(other as Student); } public override int GetHashCode() { return (Name?.GetHashCode()*17 + Gpa?.GetHashCode()).GetValueOrDefault(); } public Student With(string Name = this.Name, decimal Gpa = this.Gpa) => new Student(Name, Gpa); public void Deconstruct(out string Name, out decimal Gpa) { Name = self.Name; Gpa = self.Gpa; } } ``` #### abstract record class example This abstract record class ```cs public abstract class Person(string Name); ``` is translated into this code ```cs public abstract class Person : IEquatable { public string Name { get; } public Person(string Name) { this.Name = Name; } public bool Equals(Person other) { return other != null && Equals(Name, other.Name); } public override bool Equals(object other) { return Equals(other as Person); } public override int GetHashCode() { return (Name?.GetHashCode()).GetValueOrDefault(); } public abstract Person With(string Name = this.Name); public void Deconstruct(Person self, out string Name) { Name = self.Name; } } ``` #### combining abstract and sealed records Given the abstract record class `Person` above, this sealed record class ```cs public sealed class Student(string Name, decimal Gpa) : Person(Name); ``` is translated into this code ```cs public sealed class Student : Person, IEquatable { public override string Name { get; } public decimal Gpa { get; } public Student(string Name, decimal Gpa) : base(Name) { this.Name = Name; this.Gpa = Gpa; } public override bool Equals(Student other) // for IEquatable { return Equals(Name, other.Name) && Equals(Gpa, other.Gpa); } public bool Equals(Person other) // for IEquatable { return (other as Student)?.Equals(this) == true; } public override bool Equals(object other) { return (other as Student)?.Equals(this) == true; } public override int GetHashCode() { return (Name?.GetHashCode()*17 + Gpa.GetHashCode()).GetValueOrDefault(); } public Student With(string Name = this.Name, decimal Gpa = this.Gpa) => new Student(Name, Gpa); public override Person With(string Name = this.Name) => new Student(Name, Gpa); public void Deconstruct(Student self, out string Name, out decimal Gpa) { Name = self.Name; Gpa = self.Gpa; } } ``` ## Drawbacks [drawbacks]: #drawbacks As with any language feature, we must question whether the additional complexity to the language is repaid in the additional clarity offered to the body of C# programs that would benefit from the feature. ## Alternatives [alternatives]: #alternatives We considered adding *primary constructors* in C# 6. Although they occupy the same syntactic surface as this proposal, we found that they fell short of the advantages offered by records. ## Unresolved questions [unresolved]: #unresolved-questions Open questions appear in the body of the proposal. ## Design meetings TBD ================================================ FILE: proposals/rejected/recordsv2.md ================================================ # Records v2 Champion issue: In the past we've thought about records as a feature to enable working with data. "Working with data" is a big group with a number of facets, so it may be interesting to look at each in isolation. Let's start by looking at an example of records today and some of its drawbacks. For instance, a simple record would be defined today as follows ```C# public class UserInfo { public string Username { get; set; } public string Email { get; set; } public bool IsAdmin { get; set; } = false; } ``` and the usage would read ```C# void M() { var userInfo = new UserInfo() { Username = "andy", Email = "angocke@microsoft.com", IsAdmin = true }; } ``` There are significant advantages in this code: 1. The definition is version resilient, properties can easily be added or moved 2. Properties can be set in any order, and the names in the initialization always match the accessors 3. Properties with default values can simply be skipped The first flaw is that the properties must now be mutable. # Mutability What we'd like is for C# to provide a way to set a `readonly` member in object initializers. Since some types may not have been designed with this initialization in mind, we'd also like it to be opt-in. The proposed solution is a new modifier, `initonly`, that can be applied to properties and fields: ```C# public class UserInfo { public initonly string Username { get; } public initonly string Email { get; } public initonly bool IsAdmin { get; } = false; } ``` The codegen for this is surprisingly straight forward: we just set the readonly field. Specifically, the lowered properties would look like: ```C# public class UserInfo { private readonly string _username; public string get_Username() => _username; [return: modreq(initonly)] public void set_Username(string value) { _username = value; } ... } ``` The CLR considers setting readonly fields to be unverifiable, but not unsafe. To support a more advanced verifier, the following rule is proposed: a readonly field can be modified only inside `initonly` methods, or on a new object that is on the CLR stack and has not been published via a store or method call. This should solve many of the problems with mutability in the `UserInfo` example, while not requiring complicated or brittle emit strategies. However, immutability does present a new problem: easily constructing an object with changes. # With-ing When programming with immutability, making changes to an object is done by constructing a copy with changes instead of making the changes directly on the object. Unfortunately, there's no convenient way to do this in C#, even with current-style records. It's been previously proposed that some kind of autogenerated "With" method be provided for records that implements that functionality. If we have such a mechanism, it's important that it work with `initonly` members. To achieve this, it's proposed that we add a `with` expression, analogous to an object initializer. Sample usage would be as follows: ```C# var userInfo = new UserInfo() { Username = "andy", Email = "angocke@microsoft.com", IsAdmin = true }; var newUserName = userInfo with { Username = "angocke" }; ``` The resulting `newUserName` object would be a copy of `userInfo`, with `Username` set to "angocke". The codegen on the `with` expression would also be similar to the object initializer: a new object is constructed, and then the `initonly` `Username` setter would be called in the method body. Of course, the difference here is that the new object being constructed is not a simple new object creation, it is a duplicate of the original object. To provide this functionality, we require that the object provide a "With constructor" that provides a duplicate object. A sample `With` constructor would look like: ```C# class UserInfo { ... [WithConstructor] // placeholder syntax, up for debate public UserInfo With() { return new UserInfo() { Username = this.Username, Email = this.Email, IsAdmin = this.IsAdmin }; } } ``` Notably, the `with` expression will set `initonly` members, just like the object initializer, so to support verification we must ensure that the object cannot have been published before the `initonly` members are set. To enforce this, the `WithConstructor` attribute (or equivalent syntax) will enforce a new rule for the method: all return statements must directly contain an object creation expression, possibly with an object initializer. If the `With` constructor requires validation, the user may introduce a constructor to do that validation, e.g. ```C# class UserInfo { ... private UserInfo(UserInfo original) { // validation code } [WithConstructor] public UserInfo With() => new UserInfo(this); } ``` The last piece of complexity associated with `With` is inheritance. If your record is extensible, you will need to provide a new `With` for the subclass. This can be achieved as follows: ```C# class Base { ... protected Base(Base original) { // validation } [WithConstructor] public virtual Base With() => new Base(this); } class Derived : Base { ... protected Derived(Derived original) : base(original) { // validation } [WithConstructor] public override Derived With() => new Derived(this); } ``` Note one additional piece of complexity here: in order to override the `With` constructor with the derived type the language will also need to support covariant return types in overrides. There is already a separate proposal for this feature [here](https://github.com/dotnet/csharplang/blob/725763343ad44a9251b03814e6897d87fe553769/proposals/covariant-returns.md). **Drawbacks** - Making all return statements in `WithConstructor`s contain new object expressions is restrictive. This could be possibly be mitigated by flow analysis that ensures the new object doesn't escape the method - Supporting variance in overrides through compiler tricks will require stub methods, which will grow quadratically with the inheritance depth. The need for a stub method is due to a runtime requirement that override signatures match exactly. If the runtime requirement were loosened, the stub methods would not be required at all. - Using chained constructors of the form `Type(Type original)` effectively reserves that constructor for the use of the pattern. Since constructors have unique semantics and cannot be re-named this could be limiting. ## Wrapping it all up: Records The above features enable a style of programming that was very difficult before. But even with the new features it could be quite verbose and error prone to annotate everything yourself. There are also a few items, like Equals and GetHashCode, which can already be written today, it's just laborious. Moreover, a significant flaw in implementing equality on top of these new primitives is that structural equality is something that should change with your data type as new data is added, but when handling it manually it is likely that these things can get out of sync. Therefore, it is proposed that C# support new syntax for records, not for providing new features, but for setting defaults and generating code designed for use in records. Example syntax would look like ```C# data class UserInfo { public string Username { get; } public string Email { get; } public bool IsAdmin { get; } = false; } ``` The generated code for this class would regard all public fields and auto-properties as structural members of the record. Record members could be customized using a new `RecordMember(bool)` attribute that could be used to either include or exclude members. Record members would be `initonly` by default and equality would be autogenerated for the class based on the record members. At any point the behavior of these members could be customized simply by declaring them in source. The user-written implementation would replace the default implementation in all pattern usage. Note that equality in the face of inheritance is complex, but seems to have been adequately solved in the [other records proposal](records.md). ## Primary constructors Previous record proposal have also included a new syntax for a parameter list on the type itself, e.g. ```C# class Point(int X, int Y); ``` In the new design, the parameter list would be an orthogonal C# feature, which could be cleanly integrated with records. If a primary constructor is included in a record, it would have new defaults, just like public fields and auto-properties: the parameters in the primary constructor would be used to generate public record-member properties with the same name. In addition, the primary constructor could now be used to auto-generate a deconstructor. For example, the following record with a primary constructor ```C# data class Point(int X, int Y); ``` would be equivalent to ```C# data class Point { public int X { get; } public int Y { get; } public Point(int x, int y) { X = x; Y = y; } public void Deconstruct(out int X, out int Y) { X = this.X; Y = this.Y; } } ``` and the final generation of the above would be ```C# class Point { public initonly int X { get; } public initonly int Y { get; } public Point(int x, int y) { X = x; Y = y; } protected Point(Point other) : this(other.X, other.Y) { } [WithConstructor] public virtual Point With() => new Point(this); public void Deconstruct(out int X, out int Y) { X = this.X; Y = this.Y; } // Generated equality } ``` Note that we've taken one other piece of information into account for a data class with a primary constructor: instead of setting the primary fields inside the generated protected constructor, we delegate to the primary constructor. If the Point class had another non-primary record member, e.g. ```C# data class Point(int X, int Y) { public int Z { get; } } ``` then that would change the generated protected constructor as follows: ```C# class Point { // ... protected Point(Point other) : this(other.X, other.Y) { Z = other.Z; } // ... } ``` Notably, this doesn't answer what to do about inheritance of records with primary constructors. For instance, ```C# data class A(int X, int Y); data class B(int X, int Y, int Z) : A; ``` Rather than resolving in an arbitrary manner, a more explicit approach could require that a parameter list be provided with the base list, e.g. ```C# data class A(int X, int Y); data class B(int X, int Y, int Z) : A(X, Y); ``` The parameter list in the base list would then be applied to a `base` call in the generated primary constructor: ```C# class B { // .. public B(int x, int y, int z) : base(x, y) // .. } ``` As for what a primary constructor could mean outside of a record, that is still open to further proposal. ================================================ FILE: proposals/rejected/self-constraint.md ================================================ # Self constraint for generic type parameters Champion issue: ## Summary [summary]: #summary A single generic type parameter on a given interface declaration can be specified to be the `self type` and the corresponding type argument must be a type that implements the interface. In addition, a type implementing any constructed form of the interface directly (not through its base class), must use itself as a type argument for the self type type parameter in that constructed form. ## Motivation [motivation]: #motivation When dealing with static abstracts in interfaces for operator declarations, the interface must currently declare one of the generic type parameters to have a recursive constraint: ```csharp interface IAdditionOperators where TSelf : IAdditionOperators { static abstract TResult operator +(TSelf left, TOther right); } ``` This recursive constraint allows the language to infer that the constrainted type parameter is the implementing type and therefore that the `operator` will meet the C# requirement that at least one of the types participating in the operator signature be the declaring type. However, this rule breaks down somewhat when going outside operators: ```csharp interface IAdditiveIdentity where TSelf : IAdditiveIdentity { static abstract TSelf AdditiveIdentity { get; } } ``` In the above example, even though the intent here is the same as for operators, it is perfectly legal for a user to define and implement `struct S : IAdditiveIdentity { ... }` assuming that `int` also implements `IAdditiveIdentity`. This also hinders various usages of `static abstracts in interfaces` in that there is no way to provide the underlying `constraint` token required for the emitted call and so users in the C# 10 preview must declare their own wrapper methods to handle the transition. For example, say a user explicitly implements an interface: ```csharp interface IAdditiveIdentity where TSelf : IAdditiveIdentity { static abstract TSelf AdditiveIdentity { get; } } public struct Half : IAdditiveIdentity { public Half IAdditiveIdentity.AdditiveIdentity { get; } } ``` For non-static abstract members, this can be invoked simply by utilizing the interface `((ISomeInterface)value).SomeMethod()`. While for `static abstracts` there is no current equivalent and users must instead declare a wrapper method: ```csharp internal static T GetAdditiveIdentity() where T : IAdditiveIdentity { return T.AdditiveIdentity; } ``` This allows them to then call `GetAdditiveIdentity()` to achieve the desired result. However, this is verbose and becomes unnecessary with a proper `self constraint` which would allow `IAdditiveIdentity.AdditiveIdentity` to correctly resolve to the implementation of `Half.AdditiveIdentity`, whether implicitly or explicitly implemented. ## Detailed design [design]: #detailed-design The following syntax would become valid: ```csharp interface IAdditionOperators { static abstract TResult operator +(TSelf left, TOther right); } interface IAdditiveIdentity { static abstract TSelf AdditiveIdentity { get; } } ``` This would cause the constraint for the `TSelf` generic type parameter to have a `modreq` emitted (similarly to how the `modreq` for the `unmanaged` constraint works) that indicates it is the "self type" as well as the standard recursive constraint that was already being declared beforehand. The language can then depend on this information when interacting with the interface and know, for example, that `IAdditionOperators` should resolve to the corresponding APIs exposed on `int`. Only a single generic-type parameter would be allowed to be annotated as the `self type` as there is no reason for two types representing the same contract. The self-constrained type argument must still be specified at the use site. That is the user must still fully specify `struct Half : IAdditiveIdentity` and could not choose to elide or otherwise not specify ``. ** TODO: Insert list of actual spec changes required here ** ## Drawbacks [drawbacks]: #drawbacks The new syntax may be confusing to some users as the constraint is now specified in a different location. Utilizing the new syntax would be a breaking change for existing types and so you could not, for example, modify `System.IEquatable` to be `System.IEquatable`. ## Alternatives [alternatives]: #alternatives Consider exposing the constraint in a different manner such as: ```csharp interface IAdditiveIdentity where TSelf : this { static abstract TSelf AdditiveIdentity { get; } } ``` ## Unresolved questions [unresolved]: #unresolved-questions Should you be able to place additional constraints on the `self` type? For example, would `this TSelf` and `where TSelf : this, struct` be valid if you only wanted the interface to be implementable by struct types? Does this need to be a `modopt` (or `modreq`?) or can it be expressed in some way such that existing types can "move forward" if they have existing scenarios where this was the intended contract? How does this play into variance for classes and possible eventual support for static abstracts in classes? Would variance conversions between interface types lead to a loss of information in some way or another? Etc. Would a proper self constraint allow a simpler model for encoding the `call` in IL? That is, if the runtime also supports a proper self constraint then rather than requiring ``constrained. !!T call !0 class IAdditiveIdentity`1.get_AdditiveIdentity()`` it could simply support ``call !0 class IAdditiveIdentity`1.get_AdditiveIdentity()``. Could the self type be represented as an associated type rather than as a type parameter to an interface? Obligatory general syntax questions: should we use a keyword other than `this`? Should the keyword go where the proposal puts it, or with the rest of the type constraints? ## Design meetings ================================================ FILE: proposals/rejected/static-delegates.md ================================================ # Static Delegates (superseded by [../csharp-9.0/function-pointers.md](../csharp-9.0/function-pointers.md)) Champion issue: ## Summary [summary]: #summary Provide a general-purpose, lightweight callback capability to the C# language. ## Motivation [motivation]: #motivation Today, users have the ability to create callbacks using the `System.Delegate` type. However, these are fairly heavyweight (such as requiring a heap allocation and always having handling for chaining callbacks together). Additionally, `System.Delegate` does not provide the best interop with unmanaged function pointers, namely due being non-blittable and requiring marshalling anytime it crosses the managed/unmanaged boundary. With a few minor tweaks, we could provide a new type of delegate that is lightweight, general-purpose, and interops well with native code. ## Detailed design [design]: #detailed-design One would declare a static delegate via the following: ```C# static delegate int Func() ``` One could additionally attribute the declaration with something similar to `System.Runtime.InteropServices.UnmanagedFunctionPointer` so that the calling convention, string marshalling, and set last error behavior can be controlled. NOTE: Using `System.Runtime.InteropServices.UnmanagedFunctionPointer` itself will not work, as it is only usable on Delegates. The declaration would get translated into an internal representation by the compiler that is similar to the following ```C# struct e__StaticDelegate { IntPtr pFunction; static int WellKnownCompilerName(); } ``` That is to say, it is internally represented by a struct that has a single member of type `IntPtr` (such a struct is blittable and does not incur any heap allocations): * The member contains the address of the function that is to be the callback. * The type declares a method matching the method signature of the callback. * The name of the struct should not be user-constructible (as we do with other internally generated backing structures). * For example: fixed size buffers generate a struct with a name in the format of `e__FixedBuffer` (`<` and `>` are part of the identifier and make the identifier not constructible in C#, but still useable in IL). * The name of the method declaration should be a well known name used across all static delegate types (this allows the compiler to know the name to look for when determining the signature). The value of the static delegate can only be bound to a static method that matches the signature of the callback. Chaining callbacks together is not supported. Invocation of the callback would be implemented by the `calli` instruction. ## Drawbacks [drawbacks]: #drawbacks Static Delegates would not work with existing APIs that use regular delegates (one would need to wrap said static delegate in a regular delegate of the same signature). * Given that `System.Delegate` is represented internally as a set of `object` and `IntPtr` fields (http://source.dot.net/#System.Private.CoreLib/src/System/Delegate.cs), it would be possible to allow implicit conversion of a static delegate to a `System.Delegate` that has a matching method signature. It would also be possible to provide an explicit conversion in the opposite direction, provided the `System.Delegate` conformed to all the requirements of being a static delegate. Additional work would be needed to make Static Delegate readily usable in the core framework. ## Alternatives [alternatives]: #alternatives TBD ## Unresolved questions [unresolved]: #unresolved-questions What parts of the design are still TBD? ## Design meetings Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. ================================================ FILE: proposals/relaxed-partial-ref-ordering.md ================================================ # Relaxed ordering for `partial` and `ref` modifiers Champion issue: https://github.com/dotnet/csharplang/issues/9804 ## Summary [summary]: #summary - https://github.com/dotnet/csharplang/issues/946 - https://github.com/dotnet/csharplang/blob/main/meetings/2020/LDM-2020-09-09.md#champion-relax-ordering-constraints-around-ref-and-partial-modifiers-on-type-declarations Allow the `partial` modifier to appear in any position in a modifier list on a type or member declaration. Allow the `ref` modifier to appear in any position in a modifier list on a struct declaration. ```cs // All errors in this sample would be removed by adopting the proposal: internal partial class C { } partial internal class C { } // error internal ref struct RS { } ref internal struct RS { } // error internal ref partial struct RS { } internal partial ref struct RS { } // error partial ref internal struct RS { } // error partial class Program { public partial Program(); partial public Program() { } // error public partial int Prop { get; set; } partial public int Prop { get => field; set; } // error public partial void Method(); partial public void Method() { } // error partial public event Action Event; // error public partial event Action Event { add { } remove { } } } ``` ## Motivation [motivation]: #motivation Most modifiers in the language can be written in any order. It feels arbitrary that `partial` and `ref` use a more stringent rule, and need to come at the end of the modifier list. This is especially cumbersome in the case of `ref partial` on structs, where even the opposite order `partial ref` is not permitted. In order to avoid getting in the user's way needlessly, we should allow `partial` and `ref` modifiers to appear in any position in a modifier list, just like the other modifiers. ## Detailed design [design]: #detailed-design Note that some of the below grammars are from C# 7 and are missing more recent modifiers such as `required` and `file`. Since we have a precedent for contextual keywords `required` and `file` being modifiers, we believe we know how to parse `partial` as a modifier in any valid position it could appear in. The method grammar [(§15.6.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1561-general) is updated as follows: ```diff method_modifiers - : method_modifier* 'partial'? + : method_modifier* ; method_modifier : ref_method_modifier | 'async' ; ref_method_modifier : 'new' | 'public' | 'protected' | 'internal' | 'private' | 'static' | 'virtual' | 'sealed' | 'override' | 'abstract' | 'extern' + | 'partial' | unsafe_modifier // unsafe code support ; ``` The property grammar [(§15.7.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1571-general) is updated as follows: ```diff property_declaration - : attributes? property_modifier* 'partial'? type member_name property_body + : attributes? property_modifier* type member_name property_body ; property_modifier : 'new' | 'public' | 'protected' | 'internal' | 'private' | 'static' | 'virtual' | 'sealed' | 'override' | 'abstract' | 'extern' + | 'partial' ; ``` The event grammar [(§15.8.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1581-general) is updated as follows: ```diff event_declaration - : attributes? event_modifier* 'partial'? 'event' type variable_declarators ';' + : attributes? event_modifier* 'event' type variable_declarators ';' - | attributes? event_modifier* 'partial'? 'event' type member_name + | attributes? event_modifier* 'event' type member_name '{' event_accessor_declarations '}' ; event_modifier : 'new' | 'public' | 'protected' | 'internal' | 'private' | 'static' | 'virtual' | 'sealed' | 'override' | 'abstract' | 'extern' + | 'partial' | unsafe_modifier // unsafe code support ; ``` The indexer grammar [(§15.9.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1591-general) is updated as follows: ```diff indexer_declaration - : attributes? indexer_modifier* 'partial'? indexer_declarator indexer_body + : attributes? indexer_modifier* indexer_declarator indexer_body - | attributes? indexer_modifier* 'partial'? ref_kind indexer_declarator ref_indexer_body + | attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body ; indexer_modifier : 'new' | 'public' | 'protected' | 'internal' | 'private' | 'virtual' | 'sealed' | 'override' | 'abstract' | 'extern' + | 'partial' | unsafe_modifier // unsafe code support ; ``` Instance constructor declaration syntax [(§15.11.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#15111-general) is updated as follows: ```diff constructor_declaration - : attributes? constructor_modifier* 'partial'? constructor_declarator constructor_body + : attributes? constructor_modifier* constructor_declarator constructor_body ; constructor_modifier : 'public' | 'protected' | 'internal' | 'private' | 'extern' + | 'partial' | unsafe_modifier // unsafe code support ; ``` The classes grammar [(§15.2.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1521-general) is updated as follows: ```diff class_declaration - : attributes? class_modifier* 'partial'? 'class' identifier + : attributes? class_modifier* 'class' identifier type_parameter_list? class_base? type_parameter_constraints_clause* class_body ';'? ; class_modifier : 'new' | 'public' | 'protected' | 'internal' | 'private' | 'abstract' | 'sealed' | 'static' + | 'partial' | unsafe_modifier // unsafe code support ; ``` The interfaces grammar [(§18.2.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/interfaces.md#1821-general) is updated as follows: ```diff interface_declaration - : attributes? interface_modifier* 'partial'? 'interface' + : attributes? interface_modifier* 'interface' identifier variant_type_parameter_list? interface_base? type_parameter_constraints_clause* interface_body ';'? ; interface_modifier : 'new' | 'public' | 'protected' | 'internal' | 'private' + | 'partial' | unsafe_modifier // unsafe code support ; ``` The structs grammar [(§18.2.1)](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/structs.md#1621-general) is updated as follows: ```diff struct_declaration - : attributes? struct_modifier* 'ref'? 'partial'? 'struct' + : attributes? struct_modifier* 'struct' identifier type_parameter_list? struct_interfaces? type_parameter_constraints_clause* struct_body ';'? ; ``` ```diff struct_modifier : 'new' | 'public' | 'protected' | 'internal' | 'private' | 'readonly' + | 'ref' + | 'partial' | unsafe_modifier // unsafe code support ; ``` ## Drawbacks [drawbacks]: #drawbacks N/A ## Alternatives [alternatives]: #alternatives We could decide that we don't want to change this. In that case, the next time we spec allowing a new declaration kind to be `partial` or `ref`, we won't have to spend time thinking about whether now is the time to remove the ordering restriction. ## Open questions [open]: #open-questions N/A ================================================ FILE: proposals/speclet-disclaimer.md ================================================ > [!NOTE] > This article is a feature specification. The specification serves as the design document for the feature. It includes proposed specification changes, along with information needed during the design and development of the feature. These articles are published until the proposed spec changes are finalized and incorporated in the current ECMA specification. > > There may be some discrepancies between the feature specification and the completed implementation. Those differences are captured in the pertinent [language design meeting (LDM) notes](https://github.com/dotnet/csharplang/tree/main/meetings). > > You can learn more about the process for adopting feature speclets into the C# language standard in the article on the [specifications](https://learn.microsoft.com/dotnet/csharp/specification/feature-spec-overview). ================================================ FILE: proposals/standard-unions.md ================================================ # Standard Unions ## Summary A family of nominal type unions exist in the *TBD* namespace that can be used across assemblies as the standard way to specify a type union without needing to declare and name one. ```csharp public union Union(T1, T2); public union Union(T1, T2, T3); public union Union(T1, T2, T3, T4); ... Union x = 10; ... var _ = x switch { int v => ..., string v => ... } ``` ## Detailed Design * A small number of generic nominal type unions named `Union` are added to a runtime library. * The union are declared using the *Nominal Type Union* syntax and generated by the C# compiler. ### Runtime Library The union types are added to the same assembly that houses the `ValueTuple`, `Func` and `Action` types. ================================================ FILE: proposals/target-typed-generic-type-inference.md ================================================ # Target-typed generic type inference ## Summary Generic type inference may take a target type into account. For instance, given: ```csharp public class MyCollection { public static MyCollection Create() { ... } } public class MyCollection : IEnumerable { ... } ``` We would allow the `Create` method to be called without type argument when it can be inferred from a target type: ```csharp IEnumerable c = MyCollection.Create(); // 'T' = 'string' inferred from target type ``` ## Motivation Generic factory methods often need explicit type arguments, even when the information is clear from context; i.e. from the target type. That's because generic type inference only takes arguments into account, not target types. This would also make generic type inference a more helpful addition in non-method situations, as proposed for [constructor calls](https://github.com/dotnet/csharplang/blob/main/proposals/inference-for-constructor-calls.md) and [type patterns](https://github.com/dotnet/csharplang/blob/main/proposals/inference-for-type-patterns.md). Specifically, [union types](https://github.com/dotnet/csharplang/blob/main/proposals/nominal-type-unions.md) and [closed classes](https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md) would benefit from this, as they are frequently a target type when case types are constructed or matched. ## Detailed design Generic type inference currently has a [first phase](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12632-the-first-phase) that consists of collecting "bounds" on type parameters based on comparing each parameter type with its corresponding incoming argument. A second phase then uses the collected bounds for each type parameter to infer the corresponding type argument, if possible. This proposal adds to the first phase a facility for also collecting bounds by comparing the generic method's *return type* with the *target type* for the invocation, if one exists (new text in **bold**): > For each of the method arguments `Eᵢ`, an input type inference ([§12.6.3.7](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12637-input-type-inferences)) is made from `Eᵢ` to the corresponding parameter type `Tᵢ`. > > **Additionally, if the invocation has a target type `T`, then an *upper-bound inference* ([§12.6.3.12](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#126312-upper-bound-inferences)) is made from `T` to `Tₑ`.** Here `Tₑ`, introduced [earlier in the section](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12631-general), refers to the return type of the generic method being invoked. Note that, unlike arguments, which generally introduce a *lower bound* on a parameter type (or an *exact bound* when the parameter is e.g. a `ref` parameter), a target type introduces an *upper bound* on the return type. Intuitively, type inference must pick type arguments to make sure that each parameter type is "big enough" for its argument to fit, while (with this proposal) the return type is "small enough" to fit the target type. ## Example In the `MyCollection` example above, an *upper-bound inference* will be made from the target type `IEnumerable` to the return type of the method, `MyCollection`. Because `IEnumerable` is "covariant" in `T`, this recursively leads to an *upper-bound inference* from `string` to `T`, which puts an *upper bound* `string` on the type parameter `T` itself. When `T` is "fixed" in the second phase, `string` is the only bound on it, and becomes the inferred type argument for `T`. ## Open questions - This is probably a breaking change! Certainly, like all improvements to type inference, it may cause new candidates to succeed, leading to ambiguity or to the new candidate to be picked. Additionally it may change what is inferred for already-successful candidates, or even thwart the inference completely. However, consider that if a target type causes such a change in the inference of a type parameter, it is likely because the current inference result - without the target type - would cause a subsequent type error in assignment to the target! So perhaps the break isn't as bad, but of course it needs to be investigated! ## Implementation considerations The point of this section is to argue that the proposal is probably not expensive from an implementation point of view. The argument is somewhat gnarly, and can be safely skipped for the purposes of just understanding the proposal at the language level. The type inference machinery required for this feature is all already there in the compiler. To see this, we can systematically "map" examples such as `MyCollection` to code for which the inference works today. We're going to rewrite the `Create` method - the one that we want to do type inference for - into one that doesn't return its result, but instead passes it to a `Receptacle` delegate: an extra optional parameter that can receive the result: ```csharp //public static MyCollection Create() { ... ; return result; } public static void Create(Receptacle>? use = null!) { ... ; use?.Invoke(result); } ``` `Receptacle` is a delegate type that is *contravariant* in `T`: ```csharp public delegate void Receptacle(T value); ``` (Incidentally, `Receptacle` is identical to `Action`.) At the point of inference, instead of assigning the result of `Create` to the fresh variable `c`, we need to create a `Receptacle` for the variable: ```csharp // IEnumerable c = MyCollection.Create(); IEnumerable c; Receptacle> set_c = value => c = value; ``` Now we're ready to call our modified `Create` method, passing in the `Receptacle` for `c`: ```csharp MyCollection.Create(set_c); // 'T' = 'string' inferred from receptacle type ``` Type inference succeeds with `T` = `string`! To see that this is isomorphic to the inference in the proposed feature, let's follow type inference through the first couple of steps: 1. The [first phase](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12632-the-first-phase) performs an *input type inference* from the argument `set_c` to the parameter type `Receptacle>`. 2. The [input type inference](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12637-input-type-inferences) performs a *lower-bound inference* from `set_c`'s type `Receptacle>` to the parameter type `Receptacle>`. 3. The [lower-bound inference](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#126311-lower-bound-inferences) matches up the `Receptacle<...>` on each side, and, because the type parameter of `Receptacle` is contravariant, performs an *upper-bound inference* from `IEnumerable` to `MyCollection`. And now we're at the point where this proposal begins: Performing an *upper-bound inference* from the target type to the return type. In other words, the existence of contravariant type parameters in C# and their ability to "flip the sign" in type inference is how this expressiveness is already there. In fact, an alternative and equivalent (but more convoluted) way of specifying the proposal would be through such a mapping of the code in terms of the existing type inference machinery. ================================================ FILE: proposals/target-typed-static-member-access.md ================================================ # Target-typed static member access Champion issue: Thanks to those who provided insight and input into this proposal, especially @CyrusNajmabadi! ## Summary This feature enables a type name to be omitted from static member access when it is the same as the target type. This reduces construction and consumption verbosity for factory methods, nested derived types, enum values, constants, singletons, and other static members. Besides making existing types easier to use, this feature also opens the door so that when designing discriminated unions, the option is available to lower to nested derived types without compromising ease of construction and consumption. ```cs type.GetMethod("Name", .Public | .Instance | .DeclaredOnly); // BindingFlags.Public | ... if (someString.Equals("Value", .OrdinalIgnoreCase)) // StringComparison.OrdinalIgnoreCase ... control.ForeColor = .Red; // Color.Red entity.InvoiceDate = .Today; // DateTime.Today // Production (static members on Option) Option option = condition ? .None : .Some(42); // Production (nested derived types) CustomResult result = condition ? new .Success(42) : new .Error("message"); // Consumption (nested derived types) return result switch { .Success(var val) => val, .Error => defaultVal, }; [AttributeUsage(.Class | .Struct | .Interface | .Enum | .Delegate)] class MyAttribute : Attribute; ``` ## Motivation This feature removes painful boilerplate, matches with industry trends, and responds to the community's steady interest in this feature in terms of discussions and upvotes. Repeating a full type name before each `.` can be redundant. This happens often enough that it would make sense to put the choice in developers' hands to avoid the redundancy. Today, there's no scalable workaround for the verbosity in the following example: ```cs public void M(Result, string> result) { switch (result) { case Result, string>.Success(var array): ... case Result, string>.Error(var message): ... } } ``` This feature brings a dramatic quality-of-life improvement: ```cs public void M(Result, string> result) { switch (result) { case .Success(var array): ... case .Error(var message): ... } } ``` The industry has already started moving in the same direction. The Swift, Dart, and Zig languages have all added the `.xyz` syntax with the same meaning as this proposal for C#, and Rust has an RFC proposing the same dotted syntax: - Swift calls it [implicit member expressions](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions#Implicit-Member-Expression). - Dart calls it "dot shorthands" or [static access shorthand](https://github.com/dart-lang/language/blob/c31243942eacadcc1be8cf81016f758fe831b99c/working/3616%20-%20enum%20value%20shorthand/proposal-simple-lrhn.md). - Rust has an active RFC which calls it [path inference](https://github.com/rust-lang/rfcs/pull/3444). - Zig calls it [enum literals](https://ziglang.org/documentation/master/#Enum-Literals). This feature also puts more options on the table for the design of discriminated unions by improving the ergonomics of nested derived types. This ergonomic improvement is worth doing anyway for the sake of existing types, but it also evens the playing field among the various lowering options for discriminated unions. ## Detailed design ### Basic expression There is a new primary expression, _target-typed member binding expression_, which starts with `.` and is followed by an identifier: `.Xyz`. What makes it a target-typed member binding, rather than some other kind of member binding, is its location as a primary expression. If this expression appears in a location where there is no target type, a compiler error is produced. Otherwise, this expression is bound in the same way as though the identifier had been qualified with the target type. To determine whether there is a target type and what that target type is, the language adds an implicit _target-typed member binding conversion_, from _target-typed member binding expression_ to any type. The conversion succeeds regardless of the target type. This allows errors to be handled in binding after the conversion succeeds, such as not finding an accessible or applicable member with the given identifier, or such as the expression evaluating to an incompatible type. For example: ```cs SomeTarget a = .A; // Same as SomeTarget.A, succeeds SomeTarget b = .B; // Same as SomeTarget.B, fails with accessibility error SomeTarget c = .C; // Same as SomeTarget.C, fails trying to assign `int` to `SomeTarget` SomeTarget d = .D; // Same as SomeTarget.D, succeeds via implicit conversion SomeTarget e = .E; // Same as SomeTarget.E, fails to locate a static member // etc class SomeTarget { public static SomeTarget A; private static SomeTarget B; public static int C; public static string D; public SomeTarget E; public static implicit operator SomeTarget(string d) => ... } ``` The fact that the conversion succeeds regardless of target type also enables [target-typing with invocations](#target-typing-with-invocations). This is sufficient to allow this new construct to be combined with constructs which allow target-typing. For example: ```cs SomeTarget a = condition ? .A : .D; ``` If the resulting bound member is a constant, it may be used in locations which require constants, no differently than if it was qualified: ```cs const BindingFlags f = .Public; ``` ### Pattern matching A core scenario for this proposal is to be able to match nested derived types without repeating the containing type name, which may be long especially with generics. ```cs public void M(Result, string> result) { switch (result) { case .Success(var array): ... case .Error(var message): ... } } ``` This includes nested patterns: ```cs expr is { A: .Some(0) or .None } ``` Any type expression in a type pattern may begin with a `.`. It is bound as though it was qualified with the type of the expression or pattern being matched against. If such qualified access is permitted, the target-typed type pattern is permitted. If such qualified access is not permitted, the target-typed type pattern fails with the same message. ### Target-typing with overloadable operators A core scenario for this proposal is using bitwise operators on flags enums. To enable this without adding arbitrary limitations, this proposal enables target-typing on the operands of overloadable operators. ```cs GetMethod("Name", .Public | .Static) MyFlags f = ~.None; if ((myFlags & ~.Mask) != 0) { ... } ``` Other target-typed expressions besides `.Xyz` will be able to benefit from this, such as `null`, `default` and `[]`. One exception to this target-typed `new`, which explicitly states that it may not appear as an operand of a unary or binary operator. If desired, we could lift this restriction so that it is not the odd one out: ```cs Point p = origin + new(50, 100); ``` This is done by adding three new conversions: _unary operator target-typing conversion_, _binary operator target-typing conversion_, and _binary cross-operand target-typing conversion_. For a unary operator expression such as `~e`, we define a new implicit _unary operator target-typing conversion_ that permits an implicit conversion from the unary operator expression to any type `T` for which there is a conversion-from-expression from `e` to `T`. For a binary operator expression such as `e1 | e2`, we define a new implicit _binary operator target-typing conversion_ that permits an implicit conversion from the binary operator expression to any type `T` for which there is a conversion-from-expression from `e1` to `T` and/or from `e2` to `T`. For _either operand_ of a binary operator expression such as `e1 | e2`, if one expression has a type `T` and the other expression does not have a type, and there is a conversion-from-expression from the typeless operand expression to `T`, we define a new implicit _binary cross-operand target-typing conversion_ that permits an implicit conversion from the typeless operand expression to `T`. Target-typing from one operand to another is helpful in the following scenario: ```cs M(BindingFlags.Public | .Static | .DeclaredOnly); // Succeeds M(.Public | .Static | .DeclaredOnly); // ERROR: overload resolution fails void M(BindingFlags p) => ... void M(string p) => ... ``` ### Target-typing with invocations A core scenario for this proposal is calling factory methods. This enables the use of the feature with some of today's class and struct types. ```cs SomeResult = .Error("Message"); Option M() => .Some(42); ``` To enable target-typing for the invoked expression within an invocation expression, a new conversion is added, _invocation target-typing conversion_. For an invocation expression such as `e(...)` where the invoked expression `e` is a _target-typed member binding expression_, we define a new implicit _invocation target-typing conversion_ that permits an implicit conversion from the invocation expression to any type `T` for which there is a _target-typed member binding conversion_ from `e` to `Tₑ`. Even though the conversion always succeeds when the invoked expression `e` is a _target-typed member binding expression_, further errors may occur if the invocation expression cannot be bound for any of the same reasons as though the _target-typed member binding expression_ was a non-target-typed expression, qualified as a member of `T`. For instance, the member might not be invocable, or might return a type other than `T`. ### Target-typing after the `new` keyword A core scenario for this proposal is enable the `new` operator to look up nested derived types. This provides symmetry between production and consumption of values with class DUs and with today's type hierarchies based on nested derived classes or records. ```cs new .Case1(arg1, arg2) ``` This balances the consumption syntax: ```cs du switch { .Case1(var arg1, var arg2) => ... } ``` This would continue to be target-typed static member access (since nested types are members of their containing type), which is distinct from target-typed `new` since a definite type is provided to the `new` operator. TODO: spec the conversion ### Notes As with target-typed `new`, targeting a nullable value type should access members on the inner value type: ```cs Point? p = new(); // Equivalent to: new Point() Point? p = .Empty; // Equivalent to: Point.Empty ``` As with target-typed `new`, overload resolution is not influenced by the presence of a target-typed static member expression. If overload resolution was influenced, it would become a breaking change to add any new static member to a type. ```cs M(.Empty); // Overload ambiguity error void M(string p) { } void M(object p) { } ``` Finally, extension members are included in the lookup. ### Factory containers #### Summary (factory containers) Target-typed static members will be found on separate factory container types, for example: - `Option> result = .Some([42]);` - Calls `Some` on the nongeneric `Option` type - `SearchValues separators = .Create(',', ';');` - Calls `Create(ReadOnlySpan)` on the nongeneric `SearchValues` type - `Tensor c = .Add(a, b);` - Calls `Add` on the nongeneric `Tensor` type This will happen automatically when the factory member is on a nongeneric sibling type with the same name. There is also an opt-in model to enable the same lookup in classes where the name does not match. For example: - `IEnumerable numbers = .Range(1, 10);` - Calls `Range` on the `Enumerable` type - `IEqualityComparer comparer = .OrdinalIgnoreCase;` - Calls `OrdinalIgnoreCase` on the `StringComparer` type - `IEqualityComparer comparer = .Default;` - Calls `Default` on `EqualityComparer` For the above examples to work, the `IEnumerable` interface definition would be decorated with an attribute referring to the `Enumerable` class, and the `IEqualityComparer` interface definition would be decorated with an attribute referring to the `StringComparer` and `EqualityComparer<>` classes. #### Motivation (factory containers) A common .NET pattern for obtaining a value of some generic type is to call a factory method on a nongeneric type of the same name so that the type argument can be inferred. The core libraries follow this pattern. For example: - `KeyValuePair.Create` to obtain a `KeyValuePair` - `Task.FromResult` to obtain a `Task` - `Vector.Add` to obtain a `Vector`, along with many other factory methods for other operations. - `Tensor.Add` to obtain a `Tensor`, along with many other factory methods for other operations. - `Vector128.Add` to obtain a `Vector128`, along with many other factory methods for other operations. - `ImmutableArray.Create` to obtain an `ImmutableArray` - `Tuple.Create` to obtain a `Tuple` - `Channel.CreateBounded` to obtain a `Channel` - `SearchValues.Create(ReadOnlySpan)` to obtain a `SearchValues`, and overloads for `` and `` - `Expression.Lambda` to obtain an `Expression` This pattern has wide uptake in community APIs as well. The SDK steers users in this direction with the [CA1000: Do not declare static members on generic types](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1000) rule. Following these patterns and warnings, existing `Option` types should put their `.Some` factory method on a nongeneric class, `Option.Some`. This enables generic type inference, so `Option.Some(42)` can be written. However, this means that the method `Option.Some` does not exist. This would cause an odd asymmetry: in the core proposal, you'd be able to write `.None`, but not `.Some(val)`. The `None` member is on `Option` because there are no type inference opportunities, in the same manner as `ImmutableArray.Empty`. But the `Some` member is on nongeneric `Option`, not on `Option`. There is a clear relationship between `Option` and `Option`, `KeyValuePair` and `KeyValuePair`, `Task` and `Task`, `ImmutableArray` and `ImmutableArray`, `Tensor` and `Tensor`. The relationship is clear both from the naming convention and due to the static members on the factory type that return instances of the generic type. This doesn't always involve generics, either. There is a community request for `Color color = .Red;` to refer to `Colors.Red`. This requires forming an association between the [`Color` struct](https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.color) and the [`Colors` class](https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.colors). It would be a lost opportunity not to make use of these clear relationships in order to make consumption more consistent. In addition, if a separate proposal were to make `IEnumerable` the target type of spreads and foreach, the syntax `foreach (var x in .Range(1, 10))` or `[.. .Repeat(1, 10)]` would just fall out. #### Detailed design (factory containers) As a guiding principle, the outcome for the call site can be thought of as equivalent to static extension methods being provided on the generic type which directly call the factory member on the generic type. The specifics below are aimed at this equivalence. Member lookup for a _target-typed member binding expression_ would consider not just static members on the targeted type, but also _applicable factory members_, a subset of the static members of the target type's related _factory container types_. A type `F` serves as a _factory container type_ for another type `G` if `F` is explicitly referenced by a FactoryContainerAttribute on `G` as defined below, or implicitly if `F` has no type parameters of its own and `G` does have type parameters of its own, and they are both declared in the same module, and they are both declared in either the same containing type if any, or the same namespace if not. A member of a _factory container type_ is an _applicable factory member_ if it is static and the result of evaluating the member has an identity conversion to the target type. If the _target-typed member binding expression_ is the expression of an invocation expression, then the result of evaluating the invocation is considered instead, and any inferred type arguments in the invocation that appear in the return type will be inferred outside-in to match the target type. For example, `Option opt = .Some(42)` will infer `Option.Some`. The filter of _applicable factory members_ means that `IEqualityComparer comparer = .OrdinalIgnoreCase;` will work if `IEqualityComparer` declares `[FactoryContainer(typeof(StringComparer))]`, but `IEqualityComparer comparer = .OrdinalIgnoreCase;` will fail with an error that `.OrdinalIgnoreCase` cannot be found, rather than an error that it returns a comparer with an incompatible type. This is the definition of the attribute that enables a type to use factory members in another type when constructed through target-typed static member access: ```cs namespace System.Runtime.CompilerServices; [AttributeUsage(.Class | .Struct | .Interface | .Enum | .Delegate, AllowMultiple = true, Inherited = false)] public sealed class FactoryContainerAttribute(Type containerType) : Attribute { public Type ContainerType { get; } = containerType; } ``` Errors will be produced in the following scenarios: - If the attribute is used in older language versions. - If the referenced type contains no members that could be _applicable factory members_ for any generic instantiation of the target type which are at least as accessible as the target type. #### Alternatives (factory containers) Static extension methods would be able to achieve the same end goal of writing `.Some(42)` for `Option` or `.Range(1, 10)` for `IEnumerable` or `.OrdinalCompareCase` for `IEqualityComparer`. This would not fall foul of the [CA1000: Do not declare static members on generic types](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1000) rule, since the extension method itself is not declared on the generic type. While this shows the power of combining the core proposal with static extension methods, there are scaling problems with using static extension methods to provide the missing consistency in consumption syntax. 1. This would not be automatic out of the box. It would become best practice to declare static extension methods on your own types to enable the nicer consumption pattern. This would not be done consistently, and users would run into the inconsistencies. 1. The core libraries would likely not be willing to declare such static extension methods for the core types. In general, they prefer not to bloat metadata by declaring simple forwarders to other methods. ## Specification `'.' identifier type_argument_list?` is consolidated into a standalone syntax, `member_binding`, and this new syntax is added as a production of the [§12.8.7 Member access](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1287-member-access) grammar: ```diff member_access - : primary_expression '.' identifier type_argument_list? - | predefined_type '.' identifier type_argument_list? - | qualified_alias_member '.' identifier type_argument_list? + : primary_expression member_binding + | predefined_type member_binding + | qualified_alias_member member_binding + | member_binding ; +member_binding + '.' identifier type_argument_list? base_access - : 'base' '.' identifier type_argument_list? + : 'base' member_binding | 'base' '[' argument_list ']' ; null_conditional_member_access - : primary_expression '?' '.' identifier type_argument_list? + : primary_expression '?' member_binding (null_forgiving_operator? dependent_access)* ; dependent_access - : '.' identifier type_argument_list? // member access + : member_binding // member access | '[' argument_list ']' // element access | '(' argument_list? ')' // invocation ; null_conditional_projection_initializer - : primary_expression '?' '.' identifier type_argument_list? + : primary_expression '?' member_binding ; object_creation_expression - : 'new' type '(' argument_list? ')' object_or_collection_initializer? - | 'new' type object_or_collection_initializer + : 'new' (type | member_binding) '(' argument_list? ')' object_or_collection_initializer? + | 'new' (type | member_binding) object_or_collection_initializer ; ``` TODO: clarify that `member_access` is only valid as a `member_binding` if a conversion exists which binds it. No conversion exists which binds `.A.B`, `.A?.B`, `.A->B`, `.A().B`, and so on, so they are not valid expressions. Similar statement needed about `object_creation_expression` with `member_binding` for `new .A().B`. TODO: disambiguation rule for `A ? . B ? . C : D` TODO: patterns ## Limitations One of the use cases this feature serves is production and consumption of values of nested derived types. But one consumption scenario that is left out of this improvement is `results.OfType<.Error>()`. It's not possible to target-type in this location because the `T` is not correlated with `results`. This problem would likely only be solvable in a general way with annotations that would need to ship with the `OfType` declaration. A new operator could solve this, such as `results.SelectNonNull(r => r as .Error?)`. ## Drawbacks ### Ambiguities There are a couple of ambiguities, with [parenthesized expressions](#ambiguity-with-parenthesized-expression) and [conditional expressions](#ambiguity-with-conditional-expression). See each link for details. ## Anti-drawbacks There's been a separate request to mimic VB's `With` construct, allowing dotted access to the expression anywhere within the block: ```cs with (expr) { .PropOnExpr = 42; list.Add(.PropOnExpr); } ``` This doesn't seem to be a popular request among the language team members who have commented on it. If we go ahead with the proposed target-typing for `.Name` syntax, this seals the fate of the requested `with` statement syntax shown here. ## Alternatives #### Workaround: `using static` As a mitigation, `using static` directives can be applied as needed at the top of the file or globally. This allows syntax such as `GetMethod("Name", Public | Static)` today. This comes with a severe limitation, however, in that it doesn't help much with generic types. If you import `Option`, you can write `is Some`, but only for `Option` and not `Option` or any other constructed type. Secondly, the `using static` workaround suffers from lack of precedence. Anything in scope with the same name takes precedence over the member you're trying to access. For example, `case IBinaryOperation { OperatorKind: Equals }` binds to `object.Equals` and fails. The proposed syntax for this feature solves this with the leading `.`, which unambiguously shows that the identifier that follows comes from the target type, and not from the current scope. Third, the `using static` workaround is an imprecise hammer. It puts the names in scope in places where you might not want them. Imagine `var materialType = Slate;`: Maybe you thought this was an enum value in your roofing domain, but accidentally picked up a web color instead. The `using static` approach has also not found broad adoption over fully qualifying. There are very low hit counts on grep.app and github.com for `using static System.Reflection.BindingFlags`. ### Alternative: no sigil The target-typed static member access feature benefits from the precision of the `.` sigil, but it does not require a sigil. Here's how the feature would look without a sigil: ```cs type.GetMethod("Name", Public | Instance | DeclaredOnly); // BindingFlags.Public | ... control.ForeColor = Red; // Color.Red entity.InvoiceDate = Today; // DateTime.Today // Production (static members on Option) Option option = condition ? None : Some(42); // Production (nested derived types) CustomResult result = condition ? new Success(42) : new Error("message"); // Consumption (nested derived types) return result switch { Success(var val) => val, Error => defaultVal, }; ``` A sigil is strongly recommended for two reasons: user comprehension, and power. Firstly, the feature would be **harder to understand** without a sigil. The presence of `.` makes reading much more efficient. If no such marker is in place, it will slow down understanding of code, in both directions. Any identifier could suddenly be target-typing, and any existing occurrence of target-typing could change meaning with spooky action at a distance if conflicted names are imported, or defined in scope at some higher level. For example, is `Instance` using target-typing? What about `Error`? ```cs public ResultCodes PerformTask() { var methods = type.GetMethods(Instance); // ... return Error; } ``` Impossible to say without thinking through a lot of context. You'd have to know if there are any members named `Instance` or `Error` in the current class or base classes, or any containing classes and their base classes, or if there are any types in the namespace or in any imported namespaces named `Instance` or `Error`, or if any class is imported that has a static member named `Instance` or `Error`. How about this? Is `ModifierKeys` ending up target-typed? Is `MaxValue`? ```cs if (ModifierKeys == Keys.Control) { Value = MaxValue; } ``` A sigil thus provides essential context. With a sigil, there is no longer any spooky action at a distance which changes the fundamental meaning of the expression. (The language has prior art in `default` and `new()`. In both cases, when the type is dropped, the remaining syntax cannot stand alone without a target type.) Secondly, the feature would become **less powerful** without a sigil. To avoid changes in meaning, this would have to prefer binding to other things in the current scope name, with target-typing as a fallback. This would result in unpleasant interruptions with no recourse other than typing out the full type name. These interruptions are expected to be frequent enough to hamper the success of the feature. This specific sigil is a good fit with modern language sensibilities and audiences. The Swift, Dart, and Zig languages have all added the `.xyz` syntax with the same meaning as this proposal for C#, and Rust has an RFC proposing the same dotted syntax: - Swift calls it [implicit member expressions](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/expressions#Implicit-Member-Expression). - Dart calls it "dot shorthands" or [static access shorthand](https://github.com/dart-lang/language/blob/c31243942eacadcc1be8cf81016f758fe831b99c/working/3616%20-%20enum%20value%20shorthand/proposal-simple-lrhn.md). - Rust has an active RFC which calls it [path inference](https://github.com/rust-lang/rfcs/pull/3444). - Zig calls it [enum literals](https://ziglang.org/documentation/master/#Enum-Literals). ## Open questions ### Ambiguity with parenthesized expression This is valid grammar today, which fails in binding if `A` is a type and not a value: `(A).B`. The new grammar we're adding would allow this to be parsed as a cast followed by a target-typed static member access. This new interpretation is consistent with `(A)new()` and `(A)default` working today, but it would not be practically useful. `A.B` is a simpler and clearer way to write the same thing. Should `(A).B` continue to fail when `A` is a type, or be made to work the same as `A.B`? **Recommendation:** `(A).B` should continue to fail when `A` is a type. Even though blocking this syntax is an additional rule, the syntax is not beneficial. ### Ambiguity with conditional expression There is an ambiguity if target-typed static member access is used as the first branch of a conditional expression, where it would parse today as a null-safe dereference: `expr ? .Name : ...` We can follow the approach already taken for the similar ambiguity in collection expressions with `expr ? [` possibly being an indexer and possibly being a collection expression. Alternatively, target-typed static member access could be always disallowed within the first branch of a conditional expression unless surrounded by parens: `expr ? (.Name) : ...`. The downside is that this puts a usability burden onto users, since the compiler can work out the ambiguity by looking ahead for the `:` as with collection expressions. **Recommendation:** Allow `expr ? .Name :` by looking ahead for `:`, just as with collection expressions. ### Allowing overload resolution to be informed by target-typed access In [Notes](#notes), it is currently specified that overload resolution is not influenced by `.Xyz` expressions. This is consistent with `new()`. One risk of allowing overload resolution to be influenced is that the following code would break if a new static member `CustomType.MinValue` was created: ```cs M(.MinValue); void M(int p) { } void M(CustomType p) { } ``` This comes with one fairly significant downside in a common use case, which is that the following code fails: ```cs // error CS0121: The call is ambiguous between the following methods or properties: // 'Dictionary.Dictionary(int)' and 'Dictionary.Dictionary(IEqualityComparer)' Dictionary d1 = new Dictionary(.OrdinalIgnoreCase); // Same issue here Dictionary d2 = [with(.OrdinalIgnoreCase), a: b, c: d]; ``` Also consider more deeply nested expressions which allow target typed static member access, such as: ```cs new Dictionary(ignoreCase ? .OrdinalIgnoreCase : .Ordinal); ``` Is it worth making overload resolution smarter to allow this, perhaps by adding a tiebreaker when target-typed member access expressions resolve for exactly one overload? Alternatively, because this issue (while significant) does not crop up in many APIs, is this a good use case for OverloadResolutionPriorityAttribute on constructors and Create methods that fall into this issue? **Recommendation:** If the runtime is willing to use ORPA, keep target-typed member access free of interactions with overload resolution, just like target-typed new. ## Examples ### Pattern matching enums ```cs public static string GetTypeString(ContractTransactionMode mode) { return mode switch { .Preproduction => Preproduction, .MoltPreproduction => MoltPreproduction, .Production => ProductionFixedWeekly, .EggProduction => ProductionPerGradeADozen, .LayerBonus => LayerBonus, .PulletGrower or .PulletGrowerFinal or .PulletGrowingFinal or .PulletGrowingAP or .PulletGrowingAR or .PulletGrowerBonus => PulletGrowerOrGrowingPayment, }; } ``` ```cs if (alias.ItemAliasType is not (.Vendor or .User)) ... ``` ### Returning enums ```cs private static ContractTransactionMode GetMode(string paymentType) { return paymentType switch { Preproduction => .Preproduction, MoltPreproduction => .MoltPreproduction, ProductionFixedWeekly => .Production, ProductionPerGradeADozen => .EggProduction, PulletGrowerOrGrowingPayment => .Pullet, LayerBonus => .LayerBonus, }; } ``` ### Translating one enum to another ```cs LineItemType lineItemType = transactionType switch { .Invoice => .Sale, .CreditMemo => .Return, }; ``` ### Using System.Reflection enums All the focus is on the meaning and not on repeating the containing enum name four and five times: ```cs foreach (var property in type.GetProperties(.Public | .NonPublic | .Static | .Instance | .DeclaredOnly)) { if (property.SetMethod is not { } setter) { continue; } if ((setter.Attributes & .MemberAccessMask) is not (.Public or .Family or .FamORAssem)) { continue; } // ... } ``` ### Using generated interop (e.g. CsWin32) It's typical to see generated enum member names be the same as the original constant, which is to say that the enum name itself is pretty redundant: `FILE_ACCESS_FLAGS.FILE_GENERIC_READ | FILE_ACCESS_FLAGS.FILE_GENERIC_WRITE` Instead, this could just mention the well-known constant names which are what you'd search anyway : `.FILE_GENERIC_READ | .FILE_GENERIC_WRITE` ================================================ FILE: proposals/top-level-members.md ================================================ # Top-Level Members Champion issue: https://github.com/dotnet/csharplang/issues/9803 ## Summary Allow some members (methods, operators, extension blocks, fields, constants) to be declared in namespaces and make them available when the corresponding namespace is imported (this is a similar concept to instance extension members which are also usable without referencing the container class). ```cs // util.cs namespace MyApp; void Print(string s) => Console.WriteLine(s); string Capitalize(this string input) => input.Length == 0 ? input : char.ToUpper(input[0]) + input[1..]; ``` ```cs // app.cs #!/usr/bin/env dotnet using MyApp; Print($"Hello, {args[0].Capitalize()}!"); ``` ```cs // Fields are useful: namespace MyUtils; string? cache; string GetValue() => cache ??= Compute(); ``` ```cs // Simplifies extensions: namespace System.Linq; extension(IEnumerable e) { public IEnumerable AsEnumerable() => e; } ``` ## Motivation - Avoid boilerplate utility static classes. - Evolve top-level statements from C# 9. ## Detailed design - Some members can be declared directly in a namespace (file-scoped or block-scoped). - Allowed kinds currently are: methods, operators, extension blocks, fields, constants. - Existing declarations like classes still work the same, there shouldn't be any ambiguity. - There is no ambiguity with top-level statements because those are not allowed inside namespaces. - Top-level members in a namespace are semantically members of an "implicit" class which: - is `static` and `partial`, - has accessibility either `internal` (by default) or `public` (if any member is also `public`), - has a generated unspeakable name `<>TopLevel`, - has the namespace in which it is declared in, - is synthesized per each namespace and compilation unit (so having top-level members in the same namespace across assemblies can lead to [ambiguities](#drawbacks)). For top-level members, this means: - The `static` modifier is disallowed (the members are implicitly static). - The default accessibility is `internal`. `public` and `private` is also allowed. `protected` and `file` is disallowed. - Overloading is supported. - `extern` and `partial` are supported. - XML doc comments work. - Metadata: - The implicit class is recognized only if it has an attribute `[TopLevel]` (full attribute name is TBD). - Usage (if there is an appropriately-shaped `[TopLevel]` type in namespace `NS`): - `using NS;` implies `using static NS.<>TopLevel;`. - Lookup for `NS.Member` can find `NS.<>TopLevel.Member` (useful for disambiguation). - Nothing really changes for extension member lookup (the class name is already not used for that). - Entry points: - Top-level `Main` methods can be entry points. - Top-level statements are generated into `Program.Main` (speakable function; previously it was unspeakable). This is a breaking change: there could be a conflict with an existing `Program.Main` method declared by the user. - Simplify the logic: TLS entry-points are normal candidates (previously they were not considered to be candidates and for example `-main` could not be used to point to them). This is a breaking change: if the user has custom `Main` methods and top-level statements, they will get an error now because the compiler doesn't know which entrypoint to choose (to fix that, they can specify `-main`). ## Drawbacks - Polluting namespaces with loosely organized helpers. - Requires tooling updates to properly surface and organize top-level methods in IntelliSense, refactorings, etc. - Entry point resolution breaking changes. ## Alternatives - Support `args` keyword in top-level members (just like it can be accessed in top-level statements). But we have `System.Environment.GetCommandLineArgs()`. - Allow capturing variables from top-level statements inside non-`static` top-level members. Could be used to refactor a single-file program into multi-file program just by extracting functions to separate files. But it would mean that a method's implementation (top-level statements) can influence what other methods see (which variables are available in top-level members). - Allow declaring top-level members outside namespaces as well. - Would introduce ambiguities with top-level statements. - Could be brought to scope via `extern alias`. - To avoid needing to specify those in project files (e.g., so file-based apps also work), there could be a syntax for that like `extern alias Util = Util.dll`. - Or they could be in scope only in the current assembly. - Allow declaring top-level statements inside namespaces as well. - Top-level local functions would introduce ambiguities with top-level methods. Wouldn't be a breaking change though, just need to decide which one wins. - If we ever allow the `file` modifier on members (methods, fields, etc.), that would be naturally useful for top-level members, too. `file` members would be scoped to the current file. Compare that with `private` members which are scoped to the current _namespace_. - Indentation concerns about current utility/extension methods could be resolved with [file-scoped types](https://github.com/dotnet/csharplang/discussions/928) instead, i.e., allowing something like `class MyNamespace.MyClass;` (although beware that `class MyClass;` has already a valid meaning today). That wouldn't solve the use-site though, where you'd still need `using static MyNamespace.MyClass;` instead of just `using MyNamespace;` as with this proposal. - We could have something similar to VB's modules which are mostly like static classes but their members don't need to be qualified with the module name if they are brought to scope via an import: ```vb Imports N Namespace N Module M Sub F() End Sub End Module End Namespace Class C Sub Main() F() End Sub End Class ``` F# has something similar, too: ```fs module Utilities = let M() = () open Utilities M() ``` For example, C# could have `implicit` classes like: ```cs public implicit static class Utilities { public static void M() { } } ``` Combined with top-level classes feature mentioned above, this could look like: ```cs public implicit static class Utilities; public static void M() { } extension(int) { /* ... */ } ``` This makes the declaration side a bit more complicated to write, but it avoids problems with naming the implicit static class. Open questions for this alternative: - Should we allow non-`static` members? ## Open questions - Which member kinds? Methods, fields, constants, properties, indexers, events, constructors, operators. - Accessibility: what should be the default and which modifiers should be allowed? - Clustering: currently each namespace per assembly gets its `<>TopLevel` class. - Shape of the synthesized static class (currently `[TopLevel] <>TopLevel`). - Should it be speakable at least in other languages (so it's usable from VB/F#)? - We could make that opt in via some attribute (`[file: TopLevel("MyTopLevelClassName")]`). - The naming could be based on the assembly name and/or the file name. - If the name was constant, that could lead to ambiguity errors when the same namespace is declared across multiple assemblies which are then referenced in one place. - Should we simplify the TLS entry point logic? Should it be a breaking change? - Should we require the `static` modifier (and keep our doors open if we want to introduce some non-`static` top-level members in the future)? - Should we disallow mixing top-level members and existing declarations in one file? - Or we could limit their relative ordering, like top-level statements vs. other declarations are limited today. - Allowing such mixing might be surprising, for example: ```cs namespace N; int s_field; int M() => s_field; // ok static class C { static int M() => s_field; // error, `s_field` is not visible here } ``` - Disallowing such mixing might be surprising too, for example, consider there is an existing code: ```cs namespace N; class C; ``` and I just want to add a new declaration to it which fails and forces me to create a new file or namespace block: ```cs namespace N; extension(object) {} // error class C; ``` - Should we [relax the order of mixing top-level statements and declarations](https://github.com/dotnet/csharplang/discussions/5780) as part of this feature? - Do we need new name conflict rules for declarations and/or usages? For example, should the following be an error when declared (and/or when used)? ```cs namespace NS; int Foo; class Foo { } ``` ================================================ FILE: proposals/type-parameter-inference-from-constraints.md ================================================ # Type Parameter Inference from Constraints Champion issue: https://github.com/dotnet/csharplang/issues/9453 ## Summary Allow type inference to succeed in overload resolution scenarios by promoting generic constraints of inferred type parameters to "fake arguments" during type inference, enabling the bounds of type variables to participate in the inference process. An example is: ```cs List l = [1, 2, 3]; M(l); // Today: TElement cannot be inferred. With this proposal, successful call. void M(TEnumerable t) where TEnumerable : IEnumerable { Console.WriteLine(string.Join(",", t)); } ``` ## Motivation Currently, C# type inference can fail in scenarios where the compiler has all the information it needs to determine the correct type parameters through constraint relationships. This leads to verbose code requiring explicit type arguments or prevents valid overloads from being considered. This has long been a thorn in the side of C# users: no less than 9 different issues/discussions on it have come up over the past decade on csharplang. * https://github.com/dotnet/roslyn/issues/5023 * https://github.com/dotnet/roslyn/issues/15166 * https://github.com/dotnet/csharplang/discussions/478 * https://github.com/dotnet/csharplang/discussions/741 * https://github.com/dotnet/csharplang/discussions/997 * https://github.com/dotnet/csharplang/discussions/1018 * https://github.com/dotnet/csharplang/discussions/6930 * https://github.com/dotnet/csharplang/discussions/7262 * https://github.com/dotnet/csharplang/discussions/8767 There was even one implementation of a proposed change, https://github.com/dotnet/roslyn/pull/7850, but LDM looked at this in 2016 and decided that it would be too potentially breaking. Since then, C# has taken larger breaking change steps; most notably for this proposal, adding natural types to lambdas and method groups in overload resolution, but also adding things like target-typing for ternary expressions, adding span conversions as first-class conversions in the language, the `field` keyword, and others. Given this, now is an excellent time to re-examine the concern on the breaking change here, and potentially move forward with the proposal. Credit to [@HellBrick](https://github.com/HellBrick) for the [original proposed mechanics](https://github.com/dotnet/roslyn/issues/5023#issuecomment-154728796) of the design. This proposal has been further refined from their original starting point. ### Use cases this supports: ```csharp private static void M(T Object) where T : IEnumerable, IComparable { } private class MyClass : IComparable, IEnumerable { } private static void CallMyFunction() { var c = new MyClass(); M(c); } ``` ## Detailed design ### Changes to Type Inference Algorithm We modify the type inference process described in [§12.6.3](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#1263-type-inference) to include constraint relationships in the dependence relationship between type variables. #### Enhanced Dependence Relationship - Modified Spec Text The following text from [§12.6.3.6 Dependence](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12636-dependence) is modified: > **12.6.3.6 Dependence** > > An *unfixed* type variable `Xᵢ` *depends directly on* an *unfixed* type variable `Xₑ` if one of the following holds: > > - For some argument `Eᵥ` with type `Tᵥ` `Xₑ` occurs in an *input type* of `Eᵥ` with type `Tᵥ` and `Xᵢ` occurs in an *output type* of `Eᵥ` with type `Tᵥ`. > - **`Xᵢ` occurs in a constraint for `Xₑ`.** > > `Xₑ` *depends on* `Xᵢ` if `Xₑ` *depends directly on* `Xᵢ` or if `Xᵢ` *depends directly on* `Xᵥ` and `Xᵥ` *depends on* `Xₑ`. Thus "*depends on*" is the transitive but not reflexive closure of "*depends directly on*". #### Enhanced Fixing Process - Modified Spec Text The following text from [§12.6.3.12 Fixing](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#126312-fixing) is modified: > **12.6.3.12 Fixing** > > An *unfixed* type variable `Xᵢ` with a set of bounds is *fixed* as follows: > > - The set of *candidate types* `Uₑ` starts out as the set of all types in the set of bounds for `Xᵢ`. > - Each bound for `Xᵢ` is examined in turn: For each exact bound U of `Xᵢ` all types `Uₑ` that are not identical to `U` are removed from the candidate set. For each lower bound `U` of `Xᵢ` all types `Uₑ` to which there is *not* an implicit conversion from `U` are removed from the candidate set. For each upper-bound U of `Xᵢ` all types `Uₑ` from which there is *not* an implicit conversion to `U` are removed from the candidate set. > - If among the remaining candidate types `Uₑ` there is a unique type `V` to which there is an implicit conversion from all the other candidate types, then `Xᵢ` is fixed to `V` **and a lower-bound inference is performed from `V` to each of the types in `Xᵢ`'s constraints, if any**. > - Otherwise, type inference fails. ## Drawbacks The primary concern with this proposal is that it introduces potential breaking changes in overload resolution. Code that currently compiles and calls one overload might start calling a different overload after this feature is implemented. **Example breaking change:** ```cs void M(object obj) { Console.WriteLine("Called non-generic overload"); } void M(T t) where T : IEnumerable { Console.WriteLine("Called generic overload"); } // Call site: M("test"); // Currently prints "Called non-generic overload", would print "Called generic overload" ``` This is somewhat similar to the breaks that occurred with lambda and method group natural types; the most common change there was that type inference failed on an instance method, and then fell back to an extension method instead. And, similarly to that proposal, the likelihood is that the new overload chosen is actually the more "correct" one; it's more likely to be what the user intended. There are options to mitigate this break if we so choose; we could do two runs of overload resolution, first without constraint promotion, then if that fails to find a single applicable overload we could rerun with constraint promotion. This would be significantly more complex, but it could be done, and would mitigate the breaking change. ## Alternatives There are a couple of other options: * Introduce a new keyword at generic parameter declaration, such as `void M(TEnumerable t) where TEnumerable : IEnumerable`, and only perform the promotion for such parameters. While this is doable, and entirely mitigates the breaking change, it immediately because the "default" that everyone should use, and not doing so is a bug on the author's part, and thus is not good for the future of the language. * Partial type inference - https://github.com/dotnet/csharplang/issues/8968 covers partial type inference, so that users could use an `_` or an empty identifier to avoid restating what can be inferred from the signature, and only state what cannot be inferred. While this is a decent idea, it doesn't fully solve this issue, as we want to avoid needing to specify _any_ type parameters in this case when they can be inferred. * Associated types - https://github.com/dotnet/csharplang/issues/8712 covers this. Another very related proposal, and one this proposal does not rule out. But partial type inference will cover more scenarios around inference that won't be fixed by associated types, for scenarios where the input is truly not an associated type but has constraints based on other type parameters. ## Open questions [open]: #open-questions TBD ================================================ FILE: proposals/unions.md ================================================ # Unions Champion issue: https://github.com/dotnet/csharplang/issues/9662 ## Summary [summary]: #summary *Unions* is a set of interlinked features, that combine to provide C# support for union types: - *Union types*: Structs and classes that have a `[Union]` attribute are recognized as *union types*, and support the *union behaviors*. - *Case types*: Union types have a set of *case types*, which is given by parameters to constructors and factory methods. - *Union behaviors*: Union types support the following *union behaviors*: - *Union conversions*: There are implicit *union conversions* from each case type to a union type. - *Union matching*: Pattern matching against union values implicitly "unwraps" their contents, applying the pattern to the underlying value instead. - *Union exhaustiveness*: Switch expressions over union values are exhaustive when all case types have been matched, without need for a fallback case. - *Union nullability*: Nullability analysis has enhanced tracking of the null state of a union's contents. - *Union patterns*: All union types follow a basic *union pattern*, but there are additional optional patterns for specific scenarios. - *Union declarations*: A shorthand syntax allows declaration of union types directly. The implementation is "opinionated" - a struct declaration that follows the basic union pattern and stores the contents as a single reference field. - *Union interfaces*: A few interfaces are known by the language and used in its implementation of union declarations. ## Motivation [motivation]: #motivation Unions are a long-requested C# feature, which allows expressing values from a closed set of types in a way that pattern matching can trust to be exhaustive. The separation between union *types* and union *declarations* allows C# to have a succinct union declaration syntax with opinionated semantics, while also allowing existing types or types with other implementation choices to opt into union behaviors. The proposed unions in C# are unions of *types* and not "discriminated" or "tagged". "Discriminated unions" can be expressed in terms of "type unions" by using fresh type declarations as case types. Alternatively they can be implemented as a [closed hierarchy](https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md), which is another, related, upcoming C# feature focused on exhaustiveness. ## Detailed design [design]: #detailed-design ### Union types Any class or struct type with a `System.Runtime.CompilerServices.UnionAttribute` attribute is considered a *union type*: ```csharp namespace System.Runtime.CompilerServices { [AttributeUsage(Class | Struct, AllowMultiple = false)] public class UnionAttribute : Attribute; } ``` A union type must follow a certain pattern of public *union members*, which must either be declared on the union type itself or delegated to a "union member provider". Some union members are mandatory, and others are optional. A union type has a set of *case types* which are established based on the signatures of certain union members. The contents of a union value can be accessed through a `Value` property. The language assumes that `Value` only ever contains a value of one of the case types, or null (see [Well-formedness](#well-formedness)). #### Union member providers By default, union members are found on the union type itself. However, if the union type *directly contains* a declaration of an interface called `IUnionMembers` then the interface acts as a *union member provider*. In that case, union members are *only* found on the union member provider, not on the union type itself. A union member provider interface must be public, and the union type itself must implement it as an interface. We use the term *union-defining type* for the type where the union members are found: The union member provider if it exists, and the union type itself otherwise. #### Union members Union members are looked up by name and signature on the union-defining type. They do not have to be declared directly on the union-defining type, but can be inherited. It is an error for any union member not to be public. The creation members and the `Value` property are mandatory, and are collectively referred to as the *basic union pattern*. The `HasValue` and `TryGetValue` members are collectively referred to as the *non-boxing union access pattern*. The different union members are described in the following. #### Union creation members Union creation members are used to create new union values from a case type value. If the union-defining type is the union type itself, each constructor with a single parameter is a *union constructor*. The case types of the union are identified as the set of types built from parameter types of these constructors in the following way: - If the parameter type is a nullable type (whether a value or a reference), the case type is the underlying type - Otherwise, the case type is the parameter type. ```csharp // Union constructor making `Dog` a case type public Pet(Dog value) { ... } ``` ```csharp // Union constructor making `int` a case type public Union(int? value) { ... } ``` ```csharp // Union constructor making `string` a case type public Union(string? value) { ... } ``` If the union-defining type is a union member provider, each static `Create` method with a single parameter and a return type that is identity-convertible to the union type itself is a *union factory method*. The case types of the union are identified as the set of types built from parameter types of these factory methods in the following way: - If the parameter type is a nullable type (whether a value or a reference), the case type is the underlying type - Otherwise, the case type is the parameter type. ```csharp // Union factory method making `Cat` a case type public static Pet Create(Cat value) { ... } ``` ```csharp // Union factory method making `int` a case type public static Union Create(int? value) { ... } ``` ```csharp // Union factory method making `string` a case type public static Union Create(string? value) { ... } ``` Union constructors and union factory methods are referred to collectively as *union creation members*. The single parameter of a union creation member must be a by-value or `in` parameter. A union type must have at least one union creation member, and therefore at least one case type. #### Value property The `Value` property allows access to the value contained in a union, regardless of its case type. Every union-defining type must declare a `Value` property of type `object?` or `object`. The property must have a `get` accessor and may optionally have an `init` or `set` accessor, which can be of any accessibility and is not used by the compiler. ```csharp // Union 'Value' property public object? Value { get; } ``` #### Non-boxing access members A union type can choose to additionally implement the *non-boxing union access pattern*, which allows strongly typed conditional access to each case type, as well as a way to check for null. This allows the compiler to implement pattern matching more efficiently when case types are value types and stored as such within the union. The non-boxing access members are: - A `HasValue` property of type `bool` with a public `get` accessor. It may optionally have an `init` or `set` accessor, which can be of any accessibility and is not used by the compiler. - A `TryGetValue` method for each case type. The method returns `bool` and takes a single out-parameter of a type that is identity-convertible to the case type. ```csharp // Non-boxing access members public bool HasValue { get { ... } } public bool TryGetValue(out Dog value) { ... } ``` `HasValue` is expected to return true if and only if the union's `Value` is not null. `TryGetValue` is expected to return true if and only if the union's `Value` is of the given case type, and if so, deliver that value in the method's out parameter. #### Well-formedness The language and compiler make a number of behavioral assumptions about union types. If a type qualifies as a union type but does not satisfy those assumptions, then union behaviors may not work as expected. * *Soundness*: The `Value` property always evaluates to null or to a value of a case type. That is true even for the default value of the union type. * *Stability*: If a union value is created from a case type, the `Value` property will match that case type or null. If a union value is created from a `null` value, the `Value` property will be `null`. * *Creation equivalence*: If a value is implicitly convertible to two different case types then the creation member for either of those case types has the same observable behavior when called with that value. * *Access pattern consistency*: The behavior of the `HasValue` and `TryGetValue` non-boxing access members, if present, is observably equivalent to that of checking against the `Value` property directly. #### Examples of union types `Pet` implements the basic union pattern on the union type itself: ```csharp [Union] public record struct Pet { // Creation members = case types are 'Dog' and 'Cat' public Pet(Dog value) => Value = value; public Pet(Cat value) => Value = value; // 'Value' property public object? Value { get; } } ``` `IntOrBool` implements the non-boxing access pattern on the union type itself: ```csharp public record struct IntOrBool { private bool _isBool; private int _value; public IntOrBool(int value) => (_isBool, _value) = (false, value); public IntOrBool(bool value) => (_isBool, _value) = (true, value ? 1 : 0); public object Value => _isBool ? _value is 1 : _value; public bool HasValue => true; public bool TryGetValue(out int value) { value = _value; return !_isBool; } public bool TryGetValue(out bool value) { value = _isBool && _value is 1; return _isBool; } } ``` *Note:* This is just an example of how the non-boxing access pattern might be implemented. The user code can store the content any way it likes. In particular, it does not prevent the implementation from boxing! The `non-boxing` in its name refers to allowing the compiler's pattern matching implementation to access each case type in a strongly typed way, as opposed to the `object?`-typed `Value` property. `Result` implements the basic pattern via a union member provider: ```csharp public record class Result : Result.IUnionMembers { object? _value; public interface IUnionMembers { public static Result Create(T value) => new() { _value = value }; public static Result Create(Exception value) => new() { _value = value }; public object? Value { get; } } object? IUnionMembers.Value => _value; } ``` ### Union behaviors The union behaviors are generally implemented by means of the basic union pattern. If the union offers the non-boxing access pattern, union pattern matching will preferentially make use of it. #### Union conversions A *union conversion* implicitly converts to a union type from each of its case types. Specifically, there's a union conversion to a union type `U` from a type or expression `E` if there's a standard implicit conversion from `E` to a type `C` and `C` is a parameter type of a *union creation member* of `U`. If union type `U` is a struct, there's a union conversion to type `U?` from a type or expression `E` if there's a standard implicit conversion from `E` to a type `C` and `C` is a parameter type of a *union creation member* of `U`. A union conversion is not itself a standard implicit conversion. It may therefore not participate in a user-defined implicit conversion or another union conversion. There are no explicit union conversions beyond the implicit union conversions. Thus, even if there is an explicit conversion from `E` to a union's case type `C`, that doesn't mean there is an explicit conversion from `E` to that union type. A union conversion is executed by calling the union's creation member: ``` c# Pet pet = dog; // becomes Pet pet = new Pet(dog); // and Result result = "Hello" //becomes Result result = Result.IUnionMembers.Create("Hello"); ``` It is an error if overload resolution does not find a single best candidate member, or if that member is not one of the union type's union members. Union conversion is just another "form" of an implicit user-defined conversion. An applicable user-defined conversion operator "shadows" union conversion. The rationale behind this decision: > If someone written a user-defined operator, it should get priority. > In other words, if the user actually wrote their own operator, they want us to call it. > Existing types with conversion operators transformed into union types continue to work > the same way with respect to existing code utilizing the operators today. In the following example an implicit user-defined conversion takes priority over a union conversion. ``` c# struct S1 : System.Runtime.CompilerServices.IUnion { public S1(int x) => ... public S1(string x) => ... object System.Runtime.CompilerServices.IUnion.Value => ... public static implicit operator S1(int x) => ... } class Program { static S1 Test1() => 10; // implicit operator S1(int x) is used static S1 Test2() => (S1)20; // implicit operator S1(int x) is used } ``` In the following example, when explicit cast is used in code, an explicit user-defined conversion takes priority over a union conversion. But, when there is no explicit cast in code, a union conversion is used because explicit user-defined conversion is not applicable. ``` c# struct S2 : System.Runtime.CompilerServices.IUnion { public S2(int x) => ... public S2(string x) => ... object System.Runtime.CompilerServices.IUnion.Value => ... public static explicit operator S2(int x) => ... } class Program { static S2 Test3() => 10; // Union conversion S2.S2(int) is used static S2 Test4() => (S2)20; // explicit operator S2(int x) } ``` #### Union matching When the incoming value of a pattern is of a union type or of a nullable of a union type, the nullable value and the underlying union value's contents may be "unwrapped", depending on the pattern. For the unconditional `_` and `var` patterns, the pattern is applied to the incoming value itself. For example: ```csharp if (GetPet() is var pet) { ... } // 'pet' is the union value returned from `GetPet` ``` However, all other patterns get implicitly applied to the underlying union's `Value` property: ``` c# if (GetPet() is Dog dog) { ... } // 'Dog dog' is applied to 'GetPet().Value' if (GetPet() is null) { ... } // 'null' is applied to 'GetPet().Value' if (GetPet() is { } value) { ... } // '{ } value' is applied to 'GetPet().Value' ``` For logical patterns, this rule is applied individually to the branches, bearing in mind that the left branch of an `and` pattern can affect the incoming type of the right branch: ``` c# GetPet() switch { var pet and not null => ... // 'var pet' applies to the incoming 'Pet' and 'not null' to its 'Value' not null and var value => ... // 'not null' applies to the 'Value' as does 'var value' because of the // left branch changing the incoming type to `object?`. } ``` *Note:* This rule means that `GetPet() is Pet pet` will likely not succeed, as `Pet` is applied to the _contents_, not to the `Pet` union itself. *Note:* The reason for the different treatment of unconditional `var` pattern (as well as `_`, which is essentially a shorthand for `var _`) is an assumption that their use is qualitatively different from other patterns. `var` patterns are used simply to name the value being matched against, oftentimes in nested patterns, such as `PetOwner{ Pet: var pet }`. Here, the helpful semantics is for `pet` to retain the union type `Pet`, instead of the `Value` property being dereferenced to a useless `object?` type. If the incoming value is a class type, then the `null` pattern will succeed regardless of whether the union value itself is `null` or its contained value is `null`: ```csharp if (result is null) { ... } // if (result == null || result.Value == null) ``` Other union matching patterns will succeed only when the union value itself is not `null`. ```csharp if (result is 1) { ... } // if (result != null && result.Value is 1) ``` Similarly, if the incoming value is a nullable values type (wrapping a struct union type), then the `null` pattern will succeed regardless of whether the incoming value itself is `null` or its contained value is `null`: ```csharp if (result is null) { ... } // if (result.HasValue == false || result.GetValueOrDefault().Value == null) ``` Other union matching patterns will succeed only when the the incoming value itself is not `null`. ```csharp if (result is 1) { ... } // if (result.HasValue && result.GetValueOrDefault().Value is 1) ``` The compiler will prefer implementing pattern behavior by means of members prescribed by the non-boxing access pattern. While it is free to do any optimization within the bounds of the well-formedness rules, the following are the minimum set guaranteed to be applied: * For a pattern that implies checking for a specific type `T`, if a `TryGetValue(S value)` method is available, and there is an identity, or implicit reference/boxing conversion from `T` to `S`, then that method is used to obtain the value. The pattern is then applied to that value. If there is more than one such method, then any where the conversion from `T` to `S` is not a boxing conversion is preferred if available. If there is still more than one method, one is chosen in an implementation-defined manner. * Otherwise, for a pattern that implies checking for `null`, if a `HasValue` property is available, that property is used to check if the union value is null. * Otherwise, the pattern is applied to the result of accessing the `IUnion.Value` property on the incoming union. [The is-type operator](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1214121-the-is-type-operator) applied to a union type has the same meaning as a type pattern applied to the union type. #### Union exhaustiveness A union type is assumed to be "exhausted" by its case types. This means that a `switch` expression is exhaustive if it handles all of a union's case types: ``` c# var name = pet switch { Dog dog => ..., Cat cat => ..., // No warning about non-exhaustive switch }; ``` #### Nullability The null state of a union's `Value` property is tracked like any other property, with these modifications: - When a union creation member is called (explicitly or through a union conversion), the new union's `Value` gets the null state of the incoming value. - When the non-boxing access pattern's `HasValue` or `TryGetValue(...)` are used to query the contents of a union type (explicitly or via pattern matching), it impacts `Value`'s nullability state in the same way as if `Value` had been checked directly: The null state of `Value` becomes "not null" on the `true` branch. Even when a union switch is otherwise exhaustive, if the null state of the incoming union's `Value` property is "maybe null", a warning will be given on unhandled null. ``` c# Pet pet = GetNullableDog(); // 'pet.Value' is "maybe null" var value = pet switch { Dog dog => ..., Cat cat => ..., // Warning: 'null' not handled } ``` ### Union interfaces The following interfaces are used by the language in its implementation of union features. #### Union access interface The `IUnion` interface marks a type as a union type at compile time and provides a way to access union contents at runtime. ```csharp public interface IUnion { // The value of the union or null object? Value { get; } } ``` Unions generated by the compiler implement this interface. Example use: ```csharp if (value is IUnion { Value: null }) { ... } ``` ### Union declarations Union declarations are a succinct and opinionated way of declaring union types in C#. They declare a struct which uses a single object reference for storing its `Value`, which means: * *Boxing*: Any value types among their case types will be boxed on entry. * *Compactness*: Union values only contain a single field. The intent is for union declarations to cover the vast majority of use cases quite nicely. The two main reasons for hand coding specific union types rather than use union declarations are expected to be: * Adapting existing types to the union patterns to gain union behaviors. * Implementing a different storage strategy for e.g. efficiency or interop reasons. #### Syntax A union declaration has a name and a list of *union constructors* types. ``` antlr union_declaration : attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list? '(' type (',' type)* ')' struct_interfaces? type_parameter_constraints_clause* (`{` struct_member_declaration* `}` | ';') ; ``` In addition to the restrictions on struct members ([§16.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#163-struct-members)), the following applies to union members: * Instance fields, auto-properties or field-like events are not permitted. * Explicitly declared public constructors with a single parameter are not permitted. * Explicitly declared constructors must use a `this(...)` initializer to (directly or indirectly) delegate to one of the generated constructors. The *union constructors* types can be any type that converts to `object`, e.g., interfaces, type parameters, nullable types and other unions. It is fine for resulting cases to overlap, and for unions to nest or be null. Examples: ```csharp // Union of existing types public union Pet(Cat, Dog, Bird); // Union with function member public union OneOrMore(T, IEnumerable) { public IEnumerable AsEnumerable() => Value switch { IEnumerable list => list, T value => [value], } } // "Discriminated" union with freshly declared case types public record class None(); public record class Some(T value); public union Option(None, Some); #### Lowering A union declaration is lowered to a struct declaration with * the same attributes, modifiers, name, type parameters and constraints, * implicit implementations of `IUnion`, * a `public object? Value { get; }` auto-property, * a public constructor for each *union constructor* type, * any members in the union declaration's body. It is an error for user-declared members to conflict with generated members. Example: ``` c# public union Pet(Cat, Dog){ ... } ``` Is lowered to: ``` c# [Union] public struct Pet : IUnion { public Pet(Cat value) => Value = value; public Pet(Dog value) => Value = value; public object? Value { get; } ... // original body } ``` ## Open questions [open]: #open-questions ### [Resolved] Is union declaration a record? > A union declaration is lowered to a record struct I think this default behavior is unnecessary and, given that it is not configurable, going to significantly limit usage scenarios. Records generate a lot of code that is either unused or doesn't match specific requirements. For example, records are pretty much forbidden in compiler's code base because of that code bloat. I think that it would be better to change the default: - By default, a union declaration declares a regular struct with just union-specific members. - A user can declare a record union: ``` record union U(E1, ...) ... ``` **Resolution:** A union declaration is a plain struct, not record struct. The ```record union ...``` isn't supported ### [Resolved] Union declaration syntax It looks like the proposed syntax is incomplete or unnecessarily limiting. For example, it looks like base clause is not permitted. However, I can easily imagine a need to implement an interface, for example. I think that apart from the element-types-list the syntax should match regular `struct`/`record struct` declaration where the `struct` keyword is replaced with `union` keyword. **Resolution:** The restriction is removed. ### [Resolved] Union declaration members > Instance fields, auto-properties or field-like events are not permitted. This feels arbitrary and absolutely unnecessary. **Resolution:** The restriction is kept. ### [Resolved] Nullable value types as Union case types > The case types of the union are identified as the set of parameter types from these constructors. > The case types of the union are identified as the set of parameter types from these factory methods. At the same time: > A `TryGetValue` method for each case type. The method returns `bool` and takes a single out-parameter of a type that corresponds to the given case type in the following way: > - If the case type is a nullable value type, the type of the parameter should be identity-convertible to the underlying type > - Otherwise, the type should be identity-convertible to the case type. Is there an advantage to have a nullable value type among the case types especially that a type pattern cannot use nullable value type as the target type? It feels like we could simply say that, if constructor's/factory's parameter type is a nullable value type, then corresponding case type is the underlying type. Then we wouldn't need that extra clause for the `TryGetValue` method, all out parameters are case types. **Resolution:** The suggestion is approved ### [Resolved] Default nullable state of `Value` property > For union types where none of the case types are nullable, the default state for `Value` is "not null" rather than "maybe null". With the new design, where `Value` property is not defined in some general interface, but is an API that specifically belongs to the declared type, the rule quoted above feels like over-engineering. Moreover, the rule likely will force consumers to use nullable types in situations where otherwise nullable types wouldn't be used. For example, consider the following union declaration: ``` c# union U1(int, bool, DateTime); ``` According to the quoted rule, the default state for `Value` is "not null". But that doesn't match behavior of the type, `default(U1).Value` is `null`. In order to realign the behavior, consumer is forced to make at least one case type nullable. Something like: ``` c# union U1(int?, bool, DateTime); ``` But that is likely undesirable, consumer might not want to allow explicit creation with `int?` value. Proposal: Remove the quoted rule, nullable analysis should use annotations from the `Value` property to infer its default nullability. **Resolution:** The proposal is approved ### [Resolved] Union matching for Nullable of a union value type > When the incoming value of a pattern is of a union type, the union value's contents may be "unwrapped", depending on the pattern. Should we expand this rule to scenarios when incoming value of a pattern is of a `Nullable`? Consider the following scenario: ``` C# static bool Test1(StructUnion? u) { return u is 1; } static bool Test2(ClassUnion? u) { return u is 1; } ``` The meaning of ```u is 1``` in Test1 and Test2 are very different. In Test1 it is not a union matching, in Test2 it is. Perhaps "union matching" should "dig" through `Nullable` as pattern matching usually does in other situations. If we go with that, then the union matching `null` pattern against `Nullable` should work as against classes. I.e. the pattern is true when ```(!nullableValue.HasValue || nullableValue.Value.Value is null)```. **Resolution:** The proposal is approved. ### What to do about "bad" APIs? What should compiler do about union matching APIs that look like a match, but otherwise "bad"? For example, compiler finds TryGetValue/HasValue with matching signature, but it is "bad" because a required custom modifier or it requires an unknown feature, etc. Should compiler silently ignore the API or report an error? Similar, the API might be marked as Obsolete/Experimental. Should compiler report any diagnostics, silently use the API or silently not use the API? ### What if types for union declaration are missing What happens if `UnionAttribute`, `IUnion` or `IUnion` are missing? Error? Synthesize? Something else? ### [Resolved] Design of generic IUnion interface Arguments have been made that `IUnion` should not inherit from `IUnion` or constrain its type parameter to `IUnion`. We should revisit. **Resolution:** The `IUnion` interface is removed for now. ### [Resolved] Nullable value types as case types and their interaction with `TryGetValue` The rules above state that if a case type is a nullable value type, the parameter type used in a corresponding `TryGetValue` method should be the *underlying* type. This is motivated by the fact that a `null` value would never be yielded through this method. On the consumption side, a nullable value type is not allowed as a type pattern, whereas a match against the underlying type should be able to map to a call of this method. We should confirm that we agree with this unwrapping. **Resolution:** Agreed/confirmed ### *The non-boxing union access pattern* Need to specify precise rules for finding suitable `HasValue` and `TryGetValue` APIs. Is inheritance involved? Is read/write `HasValue` an acceptable match? Etc. ### [Resolved] `TryGetValue` matching conversions The Union Matching section says: > For a pattern that implies checking for a specific type `T`, if a `TryGetValue(S value)` > method is available, and there is an implicit conversion from `T` to `S`, > then that method is used to obtain the value. Is the set of implicit conversions restricted in any way? For example, are user-defined conversions allowed? What about tuple conversions and other not so trivial conversions? Some of those are even standard conversions. Is the set of `TryGetValue` methods restricted in any other way? For example, Union Patterns section implies that only methods with a parameter type matching a case type are considered: > a `public bool TryGetValue(out T value)` method for each case type `T`. It would be good to have an explicit answer. **Resolution:** Only implicit identity, or reference, or boxing conversions are considered ### `TryGetValue` and nullable analysis > When the non-boxing access pattern's `HasValue` or `TryGetValue(...)` > are used to query the contents of a union type (explicitly or via pattern matching), > it impacts `Value`'s nullability state in the same way as if `Value` had been > checked directly: The null state of `Value` becomes "not null" on the `true` branch. Is the set of `TryGetValue` methods restricted in any way? For example, Union Patterns section implies that only methods with a parameter type matching a case type are considered: > a `public bool TryGetValue(out T value)` method for each case type `T`. It would be good to have an explicit answer. ### Clarify rules around `default` values of struct union types *Note*: The default nullability rule mentioned below has been removed. *Note*: The "default" well-formedness rules mentioned below have been removed. We should confirm that this is what we want. [Nullability](#Nullability) section says: > For union types where none of the case types are nullable, the default state for `Value` is "not null" rather than "maybe null". Given that, for the example below, current implementation considers `Value` of `s2` as "not null": ``` c# S2 s2 = default; struct S2 : System.Runtime.CompilerServices.IUnion { public S2(int x) => throw null!; public S2(bool x) => throw null!; object? System.Runtime.CompilerServices.IUnion.Value => throw null!; } ``` At the same time, [Well-formedness](#Well-formedness) section says: >* *Default value*: If a union type is a value type, it's default value has `null` as its `Value`. >* *Default constructor*: If a union type has a nullary (no-argument) constructor, the resulting union has `null` as its `Value`. An implementation like that will be in contradiction with nullable analysis behavior for the example above. Should the [Well-formedness](#Well-formedness) rules be adjusted, or should state of `Value` of `default` be "maybe null"? If the latter, should initialization ```S2 s2 = default;``` produce a nullability warning? ### Confirm that a type parameter is never a union type, even when constrained to one. ``` C# class C1 : System.Runtime.CompilerServices.IUnion { private readonly object _value; public C1(int x) { _value = x; } public C1(string x) { _value = x; } object System.Runtime.CompilerServices.IUnion.Value => _value; } class Program { static bool Test1(T u) where T : C1 { return u is int; // Not a union matching } static bool Test2(T u) where T : C1 { return u is string; // Not a union matching } } ``` ### Should post-condition attributes affect default nullability of a Union instance? *Note*: The default nullability rule mentioned below has been removed. And we no longer infer default nullability of `Value` property from union creation methods. Therefore, the question is obsolete/no longer applicable to the current design. > For union types where none of the case types are nullable, the default state for `Value` is "not null" rather than "maybe null". Is the warning expected in the following scenario ``` c# #nullable enable struct S1 : System.Runtime.CompilerServices.IUnion { public S1(int x) => throw null!; public S1([System.Diagnostics.CodeAnalysis.NotNull] bool? x) => throw null!; object? System.Runtime.CompilerServices.IUnion.Value => throw null!; } class Program { static void Test2(S1 s) { // warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). // For example, the pattern 'null' is not covered. _ = s switch { int => 1, bool => 3 }; // } } ``` ### Union conversions #### [Resolved] Where do they belong among other conversions priority-wise? Union conversions feel like another form of a user-defined conversion. Therefore, current implementation classifies them right after a failed attempt to classify an implicit user-defined conversion, and, in case of existence is treated as just another form of a user-defined conversion. This has the following consequences: - An implicit user-defined conversion takes priority over a union conversion - When explicit cast is used in code, an explicit user-defined conversion takes priority over a union conversion - When there is no explicit cast in code, a union conversion takes priority over an explicit user-defined conversion ``` c# struct S1 : System.Runtime.CompilerServices.IUnion { public S1(int x) => ... public S1(string x) => ... object System.Runtime.CompilerServices.IUnion.Value => ... public static implicit operator S1(int x) => ... } struct S2 : System.Runtime.CompilerServices.IUnion { public S2(int x) => ... public S2(string x) => ... object System.Runtime.CompilerServices.IUnion.Value => ... public static explicit operator S2(int x) => ... } class Program { static S1 Test1() => 10; // implicit operator S1(int x) is used static S1 Test2() => (S1)20; // implicit operator S1(int x) is used static S2 Test3() => 10; // Union conversion S2.S2(int) is used static S2 Test4() => (S2)20; // explicit operator S2(int x) } ``` Need to confirm this is the behavior that we like. Otherwise the conversion rules should be clarified. **Resolution:** Approved by the working group. #### [Resolved] Ref-ness of constructor's parameter Currently language allows only by-value and `in` parameters for user-defined conversion operators. It feels like reasons for this restriction are also applicable to constructors suitable for union conversions. **Proposal:** Adjust definition of a `case type constructor` in `Union types` section above: ``` diff -For each public constructor with exactly one parameter, the type of that parameter is considered a *case type* of the union type. +For each public constructor with exactly one **by-value or `in`** parameter, the type of that parameter is considered a *case type* of the union type. ``` **Resolution:** Approved by the working group for now. However, we might consider "splitting" the set of case type constructors and the set of constructors suitable for union type conversions. #### [Resolved] Nullable Conversions [Nullable Conversions](https://github.com/dotnet/csharpstandard/blob/09d5f56455cab8868ee9798de8807a2e91fb431f/standard/conversions.md#1061-nullable-conversions) section explicitly lists conversions that can be used as underlying. Current specification doesn't propose any adjustments to that list. This result in an error for the following scenario: ``` c# struct S1 : System.Runtime.CompilerServices.IUnion { public S1(int x) => throw null; public S1(string x) => throw null; object System.Runtime.CompilerServices.IUnion.Value => throw null; } class Program { static S1? Test1(int x) { return x; // error CS0029: Cannot implicitly convert type 'int' to 'S1?' } } ``` **Proposal:** Adjust the specification to support an implicit nullable conversion from `S` to `T?` backed by a union conversion. Specifically, assuming `T` is a union type there's an implicit conversion to a type `T?` from a type or expression `E` if there's a union conversion from `E` to a type `C` and `C` is a case type of `T`. Note, there is no requirement for type of `E` to be a non-nullable value type. The conversion is evaluated as the underlying union conversion from `S` to `T` followed by a wrapping from `T` to `T?` **Resolution:** Approved. #### [Resolved] Lifted conversions Do we want to adjust [Lifted conversions](https://github.com/dotnet/csharpstandard/blob/09d5f56455cab8868ee9798de8807a2e91fb431f/standard/conversions.md#1062-lifted-conversions) section to support lifted union conversions? Currently they are not allowed: ``` c# struct S1 : System.Runtime.CompilerServices.IUnion { public S1(int x) => throw null; public S1(string x) => throw null; object System.Runtime.CompilerServices.IUnion.Value => throw null; } class Program { static S1 Test1(int? x) { return x; // error CS0029: Cannot implicitly convert type 'int?' to 'S1' } static S1? Test2(int? y) { return y; // error CS0029: Cannot implicitly convert type 'int?' to 'S1?' } } ``` **Resolution:** No lifted union conversions for now. Some notes from the discussion: > The analogy to the user defined conversions breaks down a little here. > In general unions are able to contain a null value that comes in. > It is not clear whether lifting should create an instance of a union type with `null` > value stored in it, or whether it should create a `null` value of `Nullable`. #### [Resolved] Block union conversion from an instance of a base type? One might find the current behavior confusing: ``` c# struct S1 : System.Runtime.CompilerServices.IUnion { public S1(System.ValueType x) { } public S1(string x) => throw null; object System.Runtime.CompilerServices.IUnion.Value => throw null; } class Program { static S1 Test1(System.ValueType x) { return x; // Union conversion } static S1 Test2(System.ValueType y) { return (S1)y; // Unboxing conversion } } ``` Note, language explicitly disallows declaring user-defined conversions from a base type. Therefore, it might make sence to not allow union conversions like that. **Resolution:** Do nothing special for now. Generic scenarios cannot be fully protected anyway. #### [Resolved] Block union conversion from an instance of an interface type? One might find the current behavior confusing: ``` c# struct S1 : I1, System.Runtime.CompilerServices.IUnion { public S1(I1 x) => throw null; public S1(string x) => throw null; object System.Runtime.CompilerServices.IUnion.Value => throw null; } interface I1 { } struct S2 : System.Runtime.CompilerServices.IUnion { public S2(I1 x) => throw null; public S2(string x) => throw null; object System.Runtime.CompilerServices.IUnion.Value => throw null; } class C3 : System.Runtime.CompilerServices.IUnion { public C3(I1 x) => throw null; public C3(string x) => throw null; object System.Runtime.CompilerServices.IUnion.Value => throw null; } class Program { static S1 Test1(I1 x) { return x; // Union conversion } static S1 Test2(I1 x) { return (S1)x; // Unboxing } static S2 Test3(I1 x) { return x; // Union conversion } static S2 Test4(I1 x) { return (S2)x; // Union conversion } static C3 Test3(I1 x) { return x; // Union conversion } static C3 Test4(I1 x) { return (C3)x; // Reference conversion } } ``` Note, language explicitly disallows declaring user-defined conversions from a base type. Therefore, it might make sence to not allow union conversions like that. **Resolution:** Do nothing special for now. Generic scenarios cannot be fully protected anyway. ### Namespace of IUnion interface Containing namespace for `IUnion` interface remains unspecified. If the intent is to keep it in a `global` namespace, Let’s state that explicitly. **Proposal**: If this is something simply overlooked, we could use `System.Runtime.CompilerServices` namespace. ### Classes as `Union` types #### [Resolved] Checking instance itself for `null` If a union type is a class type, it's value might itself be null. What about null checks then? The `null` pattern has been co-opted to check the `Value` property, so how do you check that the union itself isn't null? For example: - When `S` is a `Union` struct, ```s is null``` for a value of `S?`is `true`only when `s` itself is `null`. When `C` is a `Union` class, ```c is null``` for a value of `C?`is `false`when `c` itself is `null`, but it is `true` when `c` itself is not `null`and `c.Value` is `null`. Another example: ``` c# class C1 : IUnion { private readonly object? _value; public C1(){} public C1(int x) { _value = x; } public C1(string x) { _value = x; } object? IUnion.Value => _value; } class Program { static int Test1(C1? u) { // warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). // For example, the pattern 'null' is not covered. // This is very confusing, the switch expression is indeed not exhaustive (u itself is not // checked for null), but there is a case 'null => 3' in the switch expression. // It looks like the only way to shut off the warning is to use 'case _'. Adding it removes // all benefits of exhaustiveness checking, any union case could be missing and there would // be no diagnostic about that. return u switch { int => 1, string => 2, null => 3 }; } } ``` This part of the design is clearly optimized around the expectation that a union type is a struct. Some options: - Too bad. Use `==` for your null check instead of a pattern match. - Let the `null` pattern (and implicit null check in other patterns) apply to both the union value and its `Value` property: `u is null ==> u == null || u.Value == null`. - Disallow classes from being union types! #### [Resolved] Deriving from a `Union` class When a class uses a `Union`class as its base class, according to the current specification, it becomes a `Union`class itself. This happens because it automatically “inherits” implementation of `IUnion` interface, it is not required to re-implement it. At the same time, constructors of the derived type define the set of types in this new `Union`. It is very easy to get to very strange language behavior around the two classes: ``` c# class C1 : IUnion { private readonly object _value; public C1(long x) { _value = x; } public C1(string x) { _value = x; } object IUnion.Value => _value; } class C2(int x) : C1(x); class Program { static int Test1(C1 u) { // Good return u switch { long => 1, string => 2, null => 3 }; } static int Test2(C2 u) { // error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'long'. // error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'string'. return u switch { long => 1, string => 2, null => 3 }; } } ``` Some options: - Change when a class type is a `Union` type. For example, a class is a `Union` type when all true: * It is `sealed` because derived types won't be considered as `Union`types, allowing which is confusing. * None of its bases implement `IUnion` This is still not perfect. The rules are too subtle. It is easy to make a mistake. There is no diagnostic on the declaration, but `Union` matching doesn’t work. - Disallow classes from being union types. ### [Resolved] The is-type operator [The is-type operator](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#1214121-the-is-type-operator) is specified as a runtime type check. Syntactically it looks very much like a type pattern, but it isn’t. Therefore, the special `Union`matching won’t be used, which could lead to a user confusion. ``` c# struct S1 : IUnion { private readonly object _value; public S1(int x) { _value = x; } public S1(string x) { _value = x; } object IUnion.Value => _value; } class Program { static bool Test1(S1 u) { return u is int; // warning CS0184: The given expression is never of the provided ('int') type } static bool Test2(S1 u) { return u is string and ['1', .., '2']; // Good } } ``` In case of a recursive union, the type pattern might give no warning, but it still won’t do what user might think it would do. **Resolution:** Should work as a type pattern. ### List pattern List pattern always fails with `Union` matching: ``` c# struct S1 : IUnion { private readonly object _value; public S1(int[] x) { _value = x; } public S1(string[] x) { _value = x; } object IUnion.Value => _value; } class Program { static bool Test1(S1 u) { // error CS8985: List patterns may not be used for a value of type 'object'. No suitable 'Length' or 'Count' property was found. // error CS0021: Cannot apply indexing with [] to an expression of type 'object' return u is [10]; } } static class Extensions { extension(object o) { public int Length => 0; } } ``` ### Other questions * Both the use of constructors in union conversions and the use of `TryGetValue(...)` in union pattern matching are specified to be lenient when multiple ones apply: They'll just pick one. This should not matter per the well-formedness rules, but are we comfortable with it? * The specification subtly relies on the implementation of the `IUnion.Value` property rather than any `Value` property found on the union type itself. This is meant to give greater flexibility for existing types (which may have their own `Value` property for other uses) to implement the pattern. But it is awkward, and inconsistent with how other members are found and used directly on the union type. Should we make a change? Some other options: * Require union types to expose a public `Value` property. * Prefer a public `Value` property if it exists, but fall back to the `IUnion.Value` implementation if not (similar to `GetEnumerator` rules). * The proposed union declaration syntax isn't universally loved, particularly when it comes to expressing the case types. Alternatives so far also meet with criticism, but it's possible we will end up making a change. Some top concerns voiced about the current one: * Commas as separators between case types may seem to imply that order matters. * Parenthesized lists look too much like primary constructors (despite not having parameter names). * Too different from enums, which have their "cases" in curly braces. * While union declarations generate structs with a single reference field, they are still somewhat susceptible to unexpected behavior when used in a concurrent context. For instance, if a user-defined function member dereferences `this` more than once, the containing variable may have been reassigned as a whole by another thread in between the two accesses. The compiler could generate code to copy `this` to a local when necessary. Should it? In general, what degree of concurrency resiliency is desirable and reasonably attainable? ================================================ FILE: proposals/unsafe-evolution.md ================================================ # Unsafe Evolution Champion issue: https://github.com/dotnet/csharplang/issues/9704 ## Summary We update the definition of `unsafe` in C# from referring to locations where pointer types are used, to be locations where memory unmanaged by the runtime is dereferenced. These locations are where memory unsafety occurs, and are responsible for the bulk of CVEs (Common Vulnerabilities and Exposures) categorized as memory safety issues. ```cs void M() { int i = 1; int* ptr = &i; // Not unsafe unsafe { Console.WriteLine(*ptr); // Dereference of memory not managed by the runtime. This is unsafe. ref int intRef = Unsafe.AsRef(ptr); // Conversion of memory not managed by the runtime to a `ref`. This is unsafe. } } namespace System.Runtime.CompilerServices { public static class Unsafe { [RequiresUnsafe] // APIs annotated with this attribute need an unsafe context. public static ref T AsRef(void* source) { /* ... */ } } } ``` ## Motivation Background for this feature can also be found in https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/caller-unsafe.md, which tracks the broader ecosystem changes that will be needed as part of this proposal. These include BCL updates to properly annotate methods as being unsafe, as well as tooling updates for better understanding of where memory unsafety occurs. For C# specifically, we want to make sure that memory unsafety is properly tracked by the language; today, it can be difficult to look at a program holistically and understand all locations where memory unsafety occurs. This is because various helpers such as the `System.Runtime.CompilerServices.Unsafe`, `System.Runtime.InteropServices.Marshal`, and others do not express that they violate memory safety and need special consideration. Methods that then use these helpers aren't immediately obvious, and when auditing code for memory safety issues (either ahead of time when doing review, or when trying to determine the cause of a vulnerability that is being reported) it can be difficult to pinpoint the locations that could be contributing to issues. Historically, `unsafe` in C# has referred to a specific memory-safety hole: the existence of pointer types. The moment that a pointer type is no longer involved, C# is perfectly happy to let memory unsafety lie latent in code. It is this issue that we are looking to address with this evolution of `unsafe` in C# and the .NET ecosystem, labeling areas where memory unsafety could potentially occur, making it easier for reviewers and auditors to understand the boundaries of potential memory unsafety in a program. Importantly, this means that we will be _changing_ the meaning of `unsafe`, not just augmenting it. The existence of a pointer is not itself unsafe; the unsafe action is dereferencing the pointer. This extends further to types themselves; types cannot be inherently unsafe. It is only the action of using a type that could be unsafe, not the existence of that type. In order for this information to flow through the system, we therefore need to have a way to mark methods themselves as unsafe. Applying an attribute (`RequiresUnsafe`) to a member will indicate that the member has memory safety concerns and any usages must be manually validated by the programmer using the member (the error will go away if the member is used inside an `unsafe` context). We are not going to use the `unsafe` modifier in signature to denote *requires-unsafe* members to avoid a breaking change (it won't even be required to allow pointers in signature as pointers are now safe; it will merely introduce an `unsafe` context). Nevertheless, this is still a breaking change for particular segments of the C# user base. Our hope is that, for many of our users, this is effectively transparent, and updating to the new rules will be seamless. However, given that some large API surfaces like large parts of reflection may need to be marked `unsafe`, we do think it likely that there will need to be a decent on-ramp to the new rules to avoid entirely bifurcating the ecosystem. ## Breaking changes The following breaking changes can be observed when updating to a compiler implementing this language feature. - Specifying `[RequiresUnsafe]` attribute on [unsupported symbol kinds](#attributes) is a compile-time error. - If the [updated memory safety rules](#attributes) are enabled (which might be the default or even the only option in a future .NET version): - APIs marked with `[RequiresUnsafe]` or `extern` require an `unsafe` context when used. - `stackalloc` under [certain conditions](#stack-allocation) requires an `unsafe` context. - Under a new warnlevel: - Applying `[RequiresUnsafe]` warns if the updated memory safety rules are not enabled. ## Detailed Design Terminology: we call members *requires-unsafe* (previously known as *caller-unsafe*) if - under [the updated memory safety rules](#attributes) they [have the `RequiresUnsafe` attribute](#attributes) or [are `extern`](#extern), - under [the legacy memory safety rules](#attributes) they [contain pointers in signature](#compat-mode). ### Existing `unsafe` rules The existing C# specification has a large section devoted to `unsafe`: [§24 Unsafe code][unsafe-code]. It is defined as conditionally normative, as it is not required for a valid C# compiler to support the `unsafe` feature. Much of what is currently considered conditionally normative will no longer be so after this change, as most of the definition of pointers is no longer considered unsafe in itself. [Pointer types][pointer-types-spec], [Fixed and moveable variables][fixed-and-moveable-variables], all [pointer expressions][pointer-expressions] (except for [pointer indirection][pointer-indirection], [pointer member access][pointer-member-access], and [pointer element access][pointer-element-access]), and [the `fixed` statement][fixed-statement] are all no longer considered `unsafe`, and exist in normal C# with no requirement to be used in an `unsafe` context. Similarly, declaring a [fixed size buffer][fixed-size-buffer-declarations] or an initialized [`stackalloc`][stack-allocation-spec] are also perfectly legal in safe C#. For all of these cases, it is only _accessing_ the memory that is unsafe. Given the extensive rewrite of both the `unsafe` code section and other parts C# specification inherent in this change, it would be unwieldy and likely not useful to provide a line-by-line diff of the existing rules of the specification. Instead, we will provide an overview of the change to make in a given section, as well as specific new rules for what is allowed in `unsafe` contexts. #### Redefining expressions that require unsafe contexts The following expressions require an `unsafe` context when used: * [Pointer indirections][pointer-indirection] * [Pointer member access][pointer-member-access] * [Pointer element access][pointer-element-access] * Function pointer invocation * Element access on a fixed-size buffer * `stackalloc` under the conditions defined [below](#stack-allocation) In addition to these expressions, expressions and statements can also conditionally require an `unsafe` context if they depend on any symbol that is marked as `unsafe`. For example, calling a method that is *requires-unsafe* will cause the _invocation_expression_ to require an `unsafe` context. Statements with invocations embedded (such as `using`s, `foreach`, and similar) can also require an `unsafe` context when they use a *requires-unsafe* member. When we say "requires an unsafe context" or similar in this document, it means emitting an error that the construct requires an `unsafe` context to be used. > [!NOTE] > This section probably needs expansion to formally declare what each expression and statement must consider to require an `unsafe` context. #### Pointer types As mentioned, pointers become no longer inherently unsafe. Any references to unsafe contexts in [§24.3][pointer-types-spec] are deleted. Pointer types exist in normal C# and do not require `unsafe` to bring them into existence. The type definitions should be worked into [§8.1](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#81-general) and its following sections, as other types. Similarly, [pointer conversions][pointer-conversions] should be worked into [§10](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/conversions.md#10-conversions), with references to `unsafe` contexts removed. Similarly, [pointer expressions][pointer-expressions], except for [pointer indirection][pointer-indirection], [pointer member access][pointer-member-access], and [pointer element access][pointer-element-access], should be worked into [§12](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md), with references to `unsafe` contexts removed. No semantics change about the meaning of these expressions; the only change is that they no longer require an `unsafe` context to use. For [pointer indirection][pointer-indirection], [pointer member access][pointer-member-access], and [pointer element access][pointer-element-access], these operators remain unsafe, as these access memory that is not managed the runtime. They remain in [§24][unsafe-code], and continue to require an `unsafe` context to be used. Any use outside of an `unsafe` context is an error. No semantics about these operators change; they still continue to mean exactly the same thing that they do today. These expressions must always occur in an `unsafe` context. The [fixed statement][fixed-statement] moves to [§13](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md), with references to `unsafe` contexts removed. Function pointers are not yet incorporated into the main C# specification, but they are similarly affected; everything but function pointer invocation is moved into the standard specification. A function pointer invocation expression must always occur in an `unsafe` context. #### Fixed-size buffers The story for [fixed-size buffers][fixed-size-buffer-declarations] is similar to [pointers](#pointer-types). The definition of a fixed-size buffer is not itself dangerous, and moves to [§16.3](https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#163-struct-members). Accessing a fixed-size buffer in an expression is similarly safe, unless the expression occurs as the _primary_expression_ of an `element_access`; these are evaluated as a _pointer_element_access_, which is unsafe, as per the rules above. #### Stack allocation Again, the story for [stack allocation][stack-allocation-spec] is very similar to [pointers](#pointer-types). Converting a `stackalloc` to a pointer is no longer unsafe; it is the deference of that pointer that is unsafe. We do add one new rule, however: A _stackalloc_expression_ is unsafe if all of the following statements are true: * The _stackalloc_expression_ is being converted to a `Span` or a `ReadOnlySpan`. * The _stackalloc_expression_ does not have a _stackalloc_initializer_. * The _stackalloc_expression_ is used within a member that has `SkipLocalsInitAttribute` applied. In these contexts, the resulting stack space could have unknown memory contents, and it is being converted to a type that provides a safe wrapper around unmanaged memory access. This violates the contract of `Span` and `ReadOnlySpan`, and so must be subject to extra scrutiny by the author and reviewers of such code. > [!NOTE] > This means that assigning a `stackalloc` to a pointer is _always_ safe, regardless of context. #### `sizeof` For certain predefined types, `sizeof` has always been constant and safe ([§12.8.19][sizeof-const]) and that remains unchanged under the new rules. For other types, `sizeof` used to require unsafe context ([§24.6.9][sizeof-unsafe]) but it is now safe under the new memory safety rules. ### Overriding, inheritance, and implementation It is a memory safety error to add `RequiresUnsafe` at the member level in any override or implementation of a member that is not *requires-unsafe* originally, because callers may be using the base definition and not see any addition of `RequiresUnsafe` by a derived implementation. ### Delegates and lambdas It is a memory safety error to convert a *requires-unsafe* member to a delegate type outside the `unsafe` context. Delegate types and [_function types_](csharp-10.0/lambda-improvements.md#natural-function-type) cannot be *requires-unsafe*. It is a compile-time error to apply `RequiresUnsafe` on a lambda symbol. ### `extern` Because `extern` methods are to native locations that cannot be guaranteed by the runtime, any `extern` method is automatically considered *requires-unsafe* if compiled under the updated memory safety rules (i.e., it gets the `RequiresUnsafeAttribute`). Even methods that only take `unmanaged` parameters by value cannot be safely called by C#, as the calling convention used for the method could be incorrectly specified by the user and must be manually verified by review. `extern` methods from assemblies using the legacy memory safety rules are not considered implicitly `unsafe` because `extern` is considered implementation detail that is not part of public surface. `extern` is not guaranteed to be preserved in reference assemblies. Note that this is different from the [compat mode](#compat-mode) which applies to legacy-rules assemblies too because methods with pointers in signature would always need an unsafe context at the call site. ### Unsafe modifiers and contexts Today (and unchanged in this proposal), as covered by the [unsafe context specification][unsafe-context-spec], `unsafe` behaves in a lexical manner, marking the entire textual body contained by the `unsafe` block as an `unsafe` context (except for iterator bodies), and also some surrounding contexts in case of declarations: ```cs class A : Attribute { [RequiresUnsafe] public A() { } } class C { [A] void M1() { } // error: cannot use `A..ctor` in safe context [A] unsafe void M1() { } // ok: the `unsafe` context applies to the `A..ctor` usage } ``` Since pointer types are now safe, an `unsafe` modifier on declarations without bodies does not have a meaning anymore. Hence `unsafe` on the following declarations will produce a warning: - `delegate`. `RequiresUnsafe` on a member is _not_ applied to any nested anonymous or local functions inside the member. To mark an anonymous or local function as *requires-unsafe*, it must manually be marked as `RequiresUnsafe`. The same goes for anonymous and local functions declared inside of an `unsafe` block. When a member is `partial`, both parts must agree on the `unsafe` modifier, but only one can specify the `RequiresUnsafe` attribute, unchanged from C# rules today. For properties, `get` and `set/init` accessors can be independently declared as `RequiresUnsafe`; marking the entire property as `RequiresUnsafe` means that both the `get` and `set/init` accessors are *requires-unsafe*. For events, `add` and `remove` accessors can be independently declared as `RequiresUnsafe`; marking the entire event as `RequiresUnsafe` means that both the `add` and `remove` accessors are *requires-unsafe*. #### Attributes When an assembly is compiled with the new memory safety rules, it gets marked with `MemorySafetyRulesAttribute` (detailed below), filled in with `15` as the language version. This is a signal to any downstream consumers that any members defined in the assembly will be properly attributed with `RequiresUnsafeAttribute` (detailed below) if an `unsafe` context is required to call them. Any member in such an assembly that is not marked with `RequiresUnsafeAttribute` does not require an `unsafe` context to be called, regardless of the types in the signature of the member. It is an error to apply the `MemorySafetyRulesAttribute` to any symbol explicitly in source. The compiler ignores `RequiresUnsafeAttribute`-marked members from assemblies that are using the legacy memory safety rules (instead, the [compat mode](#compat-mode) is used there). The compiler will emit a warning if `RequiresUnsafe` is used under the legacy memory safety rules and an error if it is applied to unsupported symbol kinds (note that this excludes symbol kinds that should be banned already by `AttributeUsageAttribute` on the attribute's definition): - destructors, - static constructors, - lambdas. When a member under the new memory safety rules is `extern`, the compiler will implicitly apply the `RequiresUnsafeAttribute` to the member in metadata. When a user-facing *requires-unsafe* member generates hidden members, such as an auto-property's get/set methods, both the user-facing member and any hidden members generated by that user-facing member are all *requires-unsafe*, and `RequiresUnsafeAttribute` is applied to all of them. The `MemorySafetyRulesAttribute` definition is synthesized by the compiler if necessary per standard well-known member rules. The `RequiresUnsafeAttribute` definition is _not_ synthesized by the compiler, and a compilation error is reported if the expected attribute constructor cannot be resolved per standard well-known member rules. ```cs namespace System.Runtime.CompilerServices { /// Indicates the language version of the memory safety rules used when the module was compiled. [AttributeUsage(AttributeTargets.Module, Inherited = false)] public sealed class MemorySafetyRulesAttribute : Attribute { /// Initializes a new instance of the class. /// The language version of the memory safety rules used when the module was compiled. public MemorySafetyRulesAttribute(int version) => Version = version; /// Gets the language version of the memory safety rules used when the module was compiled. public int Version { get; } } [AttributeUsage(AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor, AllowMultiple = false, Inherited = true)] public sealed class RequiresUnsafeAttribute : Attribute { } } ``` #### Compat mode For compat purposes, and to reduce the number of false negatives that occur when enabling the new rules, we have a fallback rule for modules that have not been updated to the new rules. For such modules, a member is considered *requires-unsafe* if it contains a pointer or function pointer type somewhere among its parameter types or return type (can be nested in a non-pointer type, e.g., `int*[]`). Note that this doesn't apply to pointers in constraint types (e.g., `where T : I`) as those wouldn't need unsafe context at the call sites previously either. This does not include substituted generic parameters (e.g., method `I.M(T)` when substituted `T` for `int*[]`) as there is no type-safe way for the target member to use that pointer type for anything anyway. ### VB We do not need to add support to Visual Basic for `[RequiresUnsafe]` members since there are no `unsafe` contexts in VB today and no way to work with pointers there either. ## Alternatives ### Use `unsafe` to denote *requires-unsafe* members Instead of using `RequiresUnsafeAttribute` to denote *requires-unsafe* members, we could use the `unsafe` keyword on the member (and only use the attribute for metadata representation of *requires-unsafe* members). See [a previous version of this speclet](https://github.com/dotnet/csharplang/blob/61f06216967ed264a8f83c71bff482f3eb6ac113/proposals/unsafe-evolution.md) before [the alternative](https://github.com/dotnet/csharplang/blob/61f06216967ed264a8f83c71bff482f3eb6ac113/meetings/working-groups/unsafe-evolution/unsafe-alternative-syntax.md) was incorporated into it. Advantages of `unsafe`: - similar to other languages and hence easier to understand, - more discoverable than an attribute. Advantages of an attribute (or another keyword): - avoids breaking existing members marked as `unsafe`, - incremental adoption possible (member-by-member), - doesn't force marking the whole body as `unsafe` (even with `unsafe` keyword we could [change](https://github.com/dotnet/csharplang/blob/61f06216967ed264a8f83c71bff482f3eb6ac113/proposals/unsafe-evolution.md#unsafe-context-defaults-in-members) `unsafe` to not have an effect on bodies though). ## Open questions ### Local functions/lambda safe contexts Right now `unsafe` on a method body is lexically scoped. Any nested local functions or lambdas inherit this, and their bodies are in a memory unsafe context. Is this behavior that we want to keep in the language? ### Delegate type `unsafe`ty We could allow marking delegate types and lambdas (and function types) as *requires-unsafe*. This would require several additional rules (outside `unsafe` context): - disallow using *requires-unsafe* delegates as type arguments, - disallow converting those delegates to anything that's not *requires-unsafe* (`Delegate`,`Expression`, and `object`), Without this, there is a risk of forcing `unsafe` annotations in the wrong spot and having an area where the real area of `unsafe`ty isn't properly called out. #### Lambda/method group conversion to safe delegate types If we allow `unsafe` lambdas and delegates, should conversion of a *requires-unsafe* lambda or method group to a non-*requires-unsafe* delegate type permitted without warning or error in an `unsafe` context? If we don't do this, then it could be fairly painful for various parts of the ecosystem, particularly any enumerables that are passed through LINQ queries. #### Lambda/method group natural types Today, the only real impact on semantics and codegen (besides additional metadata) is changing the *function_type* of a lambda or method group when marked as `RequiresUnsafe`. If we were to avoid doing this, then there would be no real impact to either, which could give adopters more confidence that behavior has not subtly changed under the hood. ### `stackalloc` as initialized Today, [the spec](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12822-stack-allocation) always considers `stackalloc` memory as uninitialized, and says that the contents are undefined unless manually cleared or assigned. Do we consider this a spec bug, or do we need to change what we consider `unsafe` for `stackalloc` purposes? ### `unsafe` expressions Other languages with more comprehensive `unsafe` features have added `unsafe` as an expression, to enable improved user ergonomics and allow authors to more precisely limit where `unsafe` is used. Is this something that we want to have in C#? Consider an inline call to an `unsafe` member that handles the safety directly: right now, the author would either need to wrap the entire statement in an `unsafe` block, expanding the scope of the `unsafe` context, or they would need to break out the inner function call into an intermediate variable. ```cs extern int Add(int i1, int i2); // Some fancy extern addition function // Code I want to write: Console.WriteLine(unsafe(Add(1, 2))); // Code I have to write option 1, unsafe context unnecessary includes the WriteLine call unsafe { Console.WriteLine(Add(1, 2)); } // Code I have to write option 2, very verbose and harder to read: int result; unsafe { result = Add(1, 2); } Console.WriteLine(result); ``` ### More `unsafe` contexts and relaxations Before the change to use [an attribute instead of a modifier](#use-unsafe-to-denote-requires-unsafe-members) to denote *requires-unsafe* members, we had allowed `unsafe` modifier on property accessors (we hadn't allowed it on event accessors since there were *no* modifiers allowed previously). We since reverted that. Should we still allow it even though it wouldn't denote *requires-unsafe* members anymore, just an unsafe context? If we are allowing more `unsafe` contexts, should we relax more restrictions around `unsafe` and pointer parameters in iterators and async methods too? Especially allowing `await UnsafeMethod()` would be useful because now users have to rewrite that to `Task t; unsafe { t = UnsafeMethod(); } await t;`. See [ref/unsafe in iterators/async](./csharp-13.0/ref-unsafe-in-iterators-async.md#alternatives) for more details. Should we also allow `&UnsafeMethod` in safe context? Today as the proposal stands, this requires `unsafe` context if the method is marked as `[RequiresUnsafe]`. But since we are just getting its address, which will need `unsafe` context when dereferenced/called, we could allow the address-of itself in a safe context. ### `unsafe` on types We could consider not automatically making the entire lexical scope of an `unsafe` type to be an `unsafe` context and warn for an `unsafe` on a type as it would have no meaning apart from edge cases like the following which we might not care about because they have no real-world use-cases: ```cs class A : Attribute { [RequiresUnsafe] public A() { } } [A] class C; // unavoidable error for using requires-unsafe A..ctor? [A] unsafe class C; // if unsafe still introduces an unsafe context, this makes the error go away ``` ### More meaningless `unsafe` warnings Should more declarations produce the meaningless `unsafe` warning? For example, fields without initializers (assuming we don't support [*requires-unsafe* fields](#requires-unsafe-fields)), methods with empty bodies (or `extern`), etc. We already have an IDE analyzer for unnecessary `unsafe` though. ### *Requires-unsafe* fields Today, no proposal is made around `RequiresUnsafe` on a field. We may need to add it though, such that any read from or write to a field marked as *requires-unsafe* must be in an `unsafe` context. This would enable us to better annotate the concerns around code such as: ```cs class SafeWrapper { internal byte* _p; public void DoStuff() { unsafe { // ... validate that the object state is good ... // ... perform operation with _p .... } } } // Elsewhere in safe code: void M(SafeWrapper w) { w._p = stackalloc byte[10]; } ``` Should we also mark auto-property's backing field with `[RequiresUnsafe]`? ### Taking the address of an uninitialized variable Today, taking the address of a not definitely assigned variable can consider that variable definitely assigned, exposing uninitialized member. We have a couple of options to solving that: 1. Require that variables be definitely assigned before allowing an address-of operator to be used on them. 2. Make taking the address of an uninitialized variable unsafe. Examples: ```cs static void SkipInit(out T value) { // value is considered definitely assigned after the address-of fixed (void* ptr = &value); } ``` ```cs int i; // i is considered definitely assigned after the address-of _ = &i; // Incrementing whatever was on the stack i++; ``` ### Value of `MemorySafetyRulesAttribute` What should be the "enabled"/"updated" memory safety rules version? `2`? `15`? `11`? See also https://github.com/dotnet/designs/blob/main/accepted/2025/memory-safety/sdk-memory-safety-enforcement.md. ### `extern` implicitly unsafe This is currently the only place where `RequiresUnsafeAttribute` is implicitly applied by the compiler. Are we okay with this outlier? Also, CoreLib exposes many extern methods (FCalls) as safe today. Treating extern methods as implicitly unsafe will require wrapping the implicitly unsafe extern methods with a safe wrapper. We may run into situations where adding the extra wrapper is difficult due to runtime implementation details. ### `RequiresUnsafe` on `partial` members It is required to have the `unsafe` modifier at both partial member parts by pre-existing C# rules. On the other hand, attributes may be specified only at one of those parts and even cannot be specified at both parts unless they have `AllowMultiple`, but then they are effectively present multiple times. We have [changed](#use-unsafe-to-denote-requires-unsafe-members) the way to denote *requires-unsafe* members via an attribute instead of the `unsafe` modifier but haven't discussed this aspect of the change. Should we allow the attribute to be specified multiple times (via `AllowMultiple` or via special compiler behavior for this attribute and `partial` members only), or even require it (via special compiler checks for this attribute only)? ### `new()` constraint Do we want to support `new()` with *requires-unsafe* (something we currently don't seem to support in the compiler for other features, like `Obsolete`)? ```cs M(); // should be an error outside `unsafe` context since `M` calls the requires-unsafe `C..ctor`? void M() where T : new() { _ = new T(); } class C { [RequiresUnsafe] public C() { } } ``` #### `new()` constraint and `using`s How should it behave in aliases and static usings? - Should it be an error at the `using` declaration, suppressable via the `unsafe` keyword we already support there, or - should it be an error normally at the use site like it would be if used directly without an alias or static using? > [!NOTE] > In the second case, we would need to add "meaningless `unsafe`" warning for using aliases and static usings. ```cs class C { [RequiresUnsafe] public C() { } } class D where T : new() { public static void M() { _ = new T(); } } ``` ```cs using X = D; using unsafe X = D; X.M(); ``` ```cs using static D; using static unsafe D; M(); ``` Note that other constraints behave like the former option today: ```cs using X = D; // error here _ = new X(); // ok _ = new D(); // error here class C { public C(int x) { } } class D where T : new(); ``` ### Should more constructs be `unsafe`? - `dynamic` (probably should match what BCL decides for reflection APIs) ## Answered questions ### How breaking do we want to skew
Question text The initial proposal is a maximally-breaking approach, mainly as a litmus test for how aggressive we want to be. It proposes no ability to opt in/out sections of the code, changes the meaning of `unsafe` on methods, prohibits the usage of `unsafe` on types, uses errors instead of warnings, and generally forces migration to occur all at once, at the time the compiler is upgraded (and then potentially repeatedly as dependencies update and add `unsafe` to members that were already in use). However, we have a wealth of experience in making changes like this that we can draw on to scope the size of the breaks down and allow incremental adoption. These options are covered below. #### Opt in/out for code regions This is not the first time that C# has redefined the "base" case of unannotated code. C# 8.0 introduced the nullable reference type feature, which in many ways can be seen as a blueprint for how the `unsafe` feature is shaping up. It had similar goals (prevent bugs that cost billions of dollars by redefining the way default C# is interpreted) and a similar general featureset (add new info to types to propagate states and avoid bugs). It was also heavily breaking, and needed a strong set of opt in and opt out functionality to allow the feature to be adopted over time by codebases. That functionality is the "nullable reference type context". This is a lexical scope that informs the compiler, for a given region in code, both how to interpret unannotated type references and what types of warnings to give to the user. We could use this as a model for `unsafe` as well, adding an "safety rules context" or similar to allow controlling whether these new rules are being applied or not. One advantage that we have with the new `unsafe` features is that they are much less prevalent. While there are a decent number of `unsafe` calls in top libraries, our guesstimates on the percentage of top libraries that use `unsafe` is much lower than "every single line of C# code ever written". Hopefully this means that, while some ability to opt in/out is possibly needed, we don't need as complicated a mechanism as nullable has, with dedicated preprocessor switches and the like. #### Warnings vs errors The proposal currently states that memory safety requirements are currently enforced via a warning, rather than error. This is drawing from our experience working with the nullable feature, where warnings allowed code bases to incrementally adopt the new feature and not need to convert large swathes of code all at once. We expect a similar process will be needed for unsafe warnings: many codebases will simply be able to turn on the new rules globally and move on with their lives. But we expect the codebases we most care about adopting the new rules will have large amounts of code to annotate, and we want them to be able to move forward with the feature, rather than seeing a wall of errors and giving up immediately. By making the requirements warnings, we allow these codebases to fix warnings file-by-file or method-by-method as required, disabling the warnings everywhere else. #### Method signature breaks Right now, we propose that `unsafe` as a keyword on the method move from something that is lexically scoped without a semantic impact to something that has semantic impact, and isn't lexically scoped. We could limit this break by introducing a new keyword for when the caller of a method or member must be in an `unsafe` context; for example, `callerunsafe` as a modifier. #### Defaults for source generators For nullable, we force generator authors to explicitly opt-in to nullable regardless of whether the entire project has opted into the feature by default, so that generator output isn't broken by the user turning on nullable and warn as error. Should we do the same for source generators?
#### Conclusion Answered in https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-11-05.md#unsafe-evolution. We will report errors for memory safety issues when the new rules are turned on, and no exceptions for source generators will be made. [unsafe-code]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128-primary-expressions [sizeof-const]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12819-the-sizeof-operator [unsafe-context-spec]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#242-unsafe-contexts [pointer-types-spec]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#243-pointer-types [fixed-and-moveable-variables]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#244-fixed-and-moveable-variables [pointer-conversions]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#245-pointer-conversions [pointer-expressions]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#246-pointers-in-expressions [the-addressof-operator]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2465-the-address-of-operator [pointer-indirection]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2462-pointer-indirection [pointer-member-access]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2463-pointer-member-access [pointer-element-access]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2464-pointer-element-access [sizeof-unsafe]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2469-the-sizeof-operator [fixed-statement]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#247-the-fixed-statement [fixed-size-buffer-declarations]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#2482-fixed-size-buffer-declarations [stack-allocation-spec]: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#249-stack-allocation ================================================ FILE: proposals/unsigned-sizeof.md ================================================ # Unsigned `sizeof` Champion issue: ## Summary This proposal enables `sizeof` expressions without a constant value to implicitly convert to unsigned integer types the same size as `uint` or larger: ```cs uint size = sizeof(SomeStruct); ``` ## Motivation This is a minute language change which alleviates a paper cut within native interop domains. A common pattern for native Windows APIs involves assigning the size of a struct to one of its own fields, or passing the struct's size as an additional argument to a function along with a pointer to the struct. The language provides the required value easily using `sizeof(NativeStruct)`. This is only permitted in unsafe code, and it is safe so long as the struct is authored to be blittable—to have a memory layout identical to what the native API expects. These same native APIs make heavy use of unsigned integer types. The C# struct or method representing the native API will often use `uint` or other unsigned types in order to faithfully preserve the native API's distinction between signed and unsigned values. For example, `Microsoft.Windows.CsWin32` is a source generator which autogenerates such structs and methods based on Windows header files. A size is never negative. Thus, invariably, the native API is defined to take an unsigned integer, and the automated C# projection of that API requires the language user to produce a `uint` value for the size. Since the C# `sizeof` operator produces a non-constant `int` value for a user-defined struct, this results in the user having to insert explicit `uint` casts most of the time that `sizeof` is used with a struct. For example: ```cs var buffer = default(PROCESS_BASIC_INFORMATION); PInvoke.NtQueryInformationProcess( processHandle, PROCESSINFOCLASS.ProcessBasicInformation, &buffer, (uint)sizeof(PROCESS_BASIC_INFORMATION), null); ``` Or: ```cs var header = default(BITMAPINFOHEADER); header.biSize = (uint)sizeof(BITMAPINFOHEADER); // Generated from native headers: struct BITMAPINFOHEADER { public uint biSize; public int biWidth; // ... } ``` The frequent insertion of explicit uint casts is ironic: the compiler is emitting the `sizeof` CIL instruction which itself natively produces an `unsigned int32` as defined by ECMA-335, 6th edition, §III.2.45. ## Detailed design A new implicit conversion is defined, an _unsigned sizeof conversion_, from a `sizeof` expression to `uint`, `nuint`, or `ulong`. This conversion is added to the list of standard implicit conversions so that a `sizeof` expression may implicitly convert to a user-defined type which has an implicit conversion from an unsigned integer type. ## Specification The [sizeof operator](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12819-the-sizeof-operator) section is adjusted as follows (additions in **bold**): > ### 12.8.19 The sizeof operator > > The `sizeof` operator returns the number of 8-bit bytes occupied by a variable of a given type **as an `int` value**. Then, a new conversion is added to the [Implicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/conversions.md#102-implicit-conversions) section: > ### Unsigned sizeof conversions > > An implicit conversion exists from a _sizeof_expression_ ([§12.8.19](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/expressions.md#12819-the-sizeof-operator)) to `uint`. The new conversion is added to the [Standard implicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v9/standard/conversions.md#1042-standard-implicit-conversions) section: > The following implicit conversions are classified as standard implicit conversions: > > - Identity conversions ([§10.2.2](conversions.md#1022-identity-conversion)) > - Implicit numeric conversions ([§10.2.3](conversions.md#1023-implicit-numeric-conversions)) > - Implicit nullable conversions ([§10.2.6](conversions.md#1026-implicit-nullable-conversions)) > - Null literal conversions ([§10.2.7](conversions.md#1027-null-literal-conversions)) > - Implicit reference conversions ([§10.2.8](conversions.md#1028-implicit-reference-conversions)) > - Boxing conversions ([§10.2.9](conversions.md#1029-boxing-conversions)) > - Implicit constant expression conversions ([§10.2.11](conversions.md#10211-implicit-constant-expression-conversions)) > - Implicit conversions involving type parameters ([§10.2.12](conversions.md#10212-implicit-conversions-involving-type-parameters)) > - **Unsigned sizeof conversions** ## Drawbacks No drawbacks are anticipated. ## Answered questions ## Open questions 1. Is a betterness rule needed in order to prevent this change from affecting overload resolution? For example: ```cs M(sizeof(SomeStruct)); void M(long s) { } // Selected in C# 14 void M(uint s) { } ``` ================================================ FILE: spec/LICENSE.md ================================================ THE FOLLOWING NOTICE GOVERNS THE C# SPEC ===== (c) Copyright 1999-2017 Microsoft Corporation. All rights reserved. Microsoft, Windows, Visual Basic, Visual C#, and Visual C++ are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries/regions. Other product and company names mentioned herein may be the trademarks of their respective owners. ================================================ FILE: spec/README.md ================================================ This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The full table of contents is in the [standard folder](https://github.com/dotnet/csharpstandard/tree/draft-v6/standard). > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. * [Lexical structure](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md) - [§6.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#61-programs) Programs - [§6.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#62-grammars) Grammars - [§6.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#63-lexical-analysis) Lexical analysis - [§6.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#64-tokens) Tokens - [§6.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#65-pre-processing-directives) Pre-processing directives - [§7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#7-basic-concepts) Basic concepts - [§7.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#71-application-startup) Application startup - [§7.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#72-application-termination) Application termination - [§7.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#73-declarations) Declarations - [§7.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#74-members) Members - [§7.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#75-member-access) Member access - [§7.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#76-signatures-and-overloading) Signatures and overloading - [§7.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#77-scopes) Scopes - [§7.8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#78-namespace-and-type-names) Namespace and type names - [§7.9](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#79-automatic-memory-management) Automatic memory management - [§7.10](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#710-execution-order) Execution order - [§8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#8-types) Types - [§8.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#81-general) General - [§8.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#82-reference-types) Reference types - [§8.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#83-value-types) Value types - [§8.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#84-constructed-types) Constructed types - [§8.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#85-type-parameters) Type parameters - [§8.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#86-expression-tree-types) Expression tree types - [§8.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#87-the-dynamic-type) The dynamic type - [§8.8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#88-unmanaged-types) Unmanaged types - [§9](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9-variables) Variables - [§9.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#91-general) General - [§9.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#92-variable-categories) Variable categories - [§9.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#93-default-values) Default values - [§9.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94-definite-assignment) Definite assignment - [§9.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#95-variable-references) Variable references - [§9.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#96-atomicity-of-variable-references) Atomicity of variable references - [§10](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#10-conversions) Conversions - [§10.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#101-general) General - [§10.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#102-implicit-conversions) Implicit conversions - [§10.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#103-explicit-conversions) Explicit conversions - [§10.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#104-standard-conversions) Standard conversions - [§10.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#105-user-defined-conversions) User-defined conversions - [§10.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#106-conversions-involving-nullable-types) Conversions involving nullable types - [§10.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#107-anonymous-function-conversions) Anonymous function conversions - [§10.8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#108-method-group-conversions) Method group conversions - [§11](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11-expressions) Expressions - [§11.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111-general) General - [§11.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#112-expression-classifications) Expression classifications - [§11.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#113-static-and-dynamic-binding) Static and Dynamic Binding - [§11.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#114-operators) Operators - [§11.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#115-member-lookup) Member lookup - [§11.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116-function-members) Function members - [§11.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117-primary-expressions) Primary expressions - [§11.8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#118-unary-operators) Unary operators - [§11.9](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#119-arithmetic-operators) Arithmetic operators - [§11.10](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1110-shift-operators) Shift operators - [§11.11](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1111-relational-and-type-testing-operators) Relational and type-testing operators - [§11.12](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1112-logical-operators) Logical operators - [§11.13](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1113-conditional-logical-operators) Conditional logical operators - [§11.14](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1114-the-null-coalescing-operator) The null coalescing operator - [§11.15](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1115-conditional-operator) Conditional operator - [§11.16](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1116-anonymous-function-expressions) Anonymous function expressions - [§11.17](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1117-query-expressions) Query expressions - [§11.18](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1118-assignment-operators) Assignment operators - [§11.19](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1119-expression) Expression - [§11.20](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1120-constant-expressions) Constant expressions - [§11.21](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1121-boolean-expressions) Boolean expressions - [§12](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#12-statements) Statements - [§12.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#121-general) General - [§12.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#122-end-points-and-reachability) End points and reachability - [§12.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#123-blocks) Blocks - [§12.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#124-the-empty-statement) The empty statement - [§12.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#125-labeled-statements) Labeled statements - [§12.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#126-declaration-statements) Declaration statements - [§12.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#127-expression-statements) Expression statements - [§12.8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#128-selection-statements) Selection statements - [§12.9](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#129-iteration-statements) Iteration statements - [§12.10](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1210-jump-statements) Jump statements - [§12.11](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1211-the-try-statement) The try statement - [§12.12](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1212-the-checked-and-unchecked-statements) The checked and unchecked statements - [§12.13](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1213-the-lock-statement) The lock statement - [§12.14](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1214-the-using-statement) The using statement - [§12.15](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1215-the-yield-statement) The yield statement - [§13](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#13-namespaces) Namespaces - [§13.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#131-general) General - [§13.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#132-compilation-units) Compilation units - [§13.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#133-namespace-declarations) Namespace declarations - [§13.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#134-extern-alias-directives) Extern alias directives - [§13.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#135-using-directives) Using directives - [§13.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#136-namespace-member-declarations) Namespace member declarations - [§13.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#137-type-declarations) Type declarations - [§13.8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#138-qualified-alias-member) Qualified alias member - [§14](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14-classes) Classes - [§14.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#141-general) General - [§14.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#142-class-declarations) Class declarations - [§14.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#143-class-members) Class members - [§14.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#144-constants) Constants - [§14.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#145-fields) Fields - [§14.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#146-methods) Methods - [§14.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#147-properties) Properties - [§14.8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#148-events) Events - [§14.9](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#149-indexers) Indexers - [§14.10](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1410-operators) Operators - [§14.11](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1411-instance-constructors) Instance constructors - [§14.12](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1412-static-constructors) Static constructors - [§14.13](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1413-finalizers) Finalizers - [§14.14](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1414-iterators) Iterators - [§14.15](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1415-async-functions) Async Functions - [§15](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#15-structs) Structs - [§15.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#151-general) General - [§15.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#152-struct-declarations) Struct declarations - [§15.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#153-struct-members) Struct members - [§15.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#154-class-and-struct-differences) Class and struct differences - [§16](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#16-arrays) Arrays - [§16.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#161-general) General - [§16.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#162-array-types) Array types - [§16.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#163-array-creation) Array creation - [§16.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#164-array-element-access) Array element access - [§16.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#165-array-members) Array members - [§16.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#166-array-covariance) Array covariance - [§16.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#167-array-initializers) Array initializers - [§17](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#17-interfaces) Interfaces - [§17.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#171-general) General - [§17.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#172-interface-declarations) Interface declarations - [§17.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#173-interface-body) Interface body - [§17.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#174-interface-members) Interface members - [§17.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#175-qualified-interface-member-names) Qualified interface member names - [§17.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#176-interface-implementations) Interface implementations - [§18](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#18-enums) Enums - [§18.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#181-general) General - [§18.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#182-enum-declarations) Enum declarations - [§18.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#183-enum-modifiers) Enum modifiers - [§18.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#184-enum-members) Enum members - [§18.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#185-the-systemenum-type) The System.Enum type - [§18.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#186-enum-values-and-operations) Enum values and operations - [§19](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#19-delegates) Delegates - [§19.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#191-general) General - [§19.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#192-delegate-declarations) Delegate declarations - [§19.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#193-delegate-members) Delegate members - [§19.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#194-delegate-compatibility) Delegate compatibility - [§19.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#195-delegate-instantiation) Delegate instantiation - [§19.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#196-delegate-invocation) Delegate invocation - [§20](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#20-exceptions) Exceptions - [§20.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#201-general) General - [§20.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#202-causes-of-exceptions) Causes of exceptions - [§20.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#203-the-systemexception-class) The System.Exception class - [§20.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#204-how-exceptions-are-handled) How exceptions are handled - [§20.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#205-common-exception-classes) Common exception classes - [§21](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#21-attributes) Attributes - [§21.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#211-general) General - [§21.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#212-attribute-classes) Attribute classes - [§21.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#213-attribute-specification) Attribute specification - [§21.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#214-attribute-instances) Attribute instances - [§21.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#215-reserved-attributes) Reserved attributes - [§21.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#216-attributes-for-interoperation) Attributes for interoperation - [§22](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#22-unsafe-code) Unsafe code - [§22.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#221-general) General - [§22.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#222-unsafe-contexts) Unsafe contexts - [§22.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#223-pointer-types) Pointer types - [§22.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#224-fixed-and-moveable-variables) Fixed and moveable variables - [§22.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#225-pointer-conversions) Pointer conversions - [§22.6](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#226-pointers-in-expressions) Pointers in expressions - [§22.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#227-the-fixed-statement) The fixed statement - [§22.8](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#228-fixed-size-buffers) Fixed-size buffers - [§22.9](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#229-stack-allocation) Stack allocation - [§D](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/documentation-comments.md#annex-d-documentation-comments) Documentation comments - [§D.1](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/documentation-comments.md#d1-general) General - [§D.2](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/documentation-comments.md#d2-introduction) Introduction - [§D.3](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/documentation-comments.md#d3-recommended-tags) Recommended tags - [§D.4](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/documentation-comments.md#d4-processing-the-documentation-file) Processing the documentation file - [§D.5](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/documentation-comments.md#d5-an-example) An example ================================================ FILE: spec/arrays.md ================================================ # Arrays This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Arrays](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#16-arrays) - [Array types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#162-array-types) - [The System.Array type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#1622-the-systemarray-type) - [Arrays and the generic IList interface](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#1623-arrays-and-the-generic-collection-interfaces) - [Array creation](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#163-array-creation) - [Array element access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#164-array-element-access) - [Array members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#165-array-members) - [Array covariance](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#166-array-covariance) - [Array initializers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/arrays.md#167-array-initializers) ================================================ FILE: spec/attributes.md ================================================ # Attributes This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Attributes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#21-attributes) - [ Attribute classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#212-attribute-classes) - [Attribute usage](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#2122-attribute-usage) - [Positional and named parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#2123-positional-and-named-parameters) - [Attribute parameter types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#2124-attribute-parameter-types) - [Attribute specification](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#213-attribute-specification) - [Attribute instances](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#214-attribute-instances) - [Compilation of an attribute](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#2142-compilation-of-an-attribute) - [Run-time retrieval of an attribute instance](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#2143-run-time-retrieval-of-an-attribute-instance) - [Reserved attributes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#215-reserved-attributes) - [The AttributeUsage attribute](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#2152-the-attributeusage-attribute) - [The Conditional attribute](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#2153-the-conditional-attribute) - [Conditional methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#21532-conditional-methods) - [Conditional attribute classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#21533-conditional-attribute-classes) - [The Obsolete attribute](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#2154-the-obsolete-attribute) - [Caller info attributes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#2155-caller-info-attributes) - [The CallerLineNumber attribute](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#21552-the-callerlinenumber-attribute) - [The CallerFilePath attribute](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#21553-the-callerfilepath-attribute) - [The CallerMemberName attribute](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#21554-the-callermembername-attribute) - [Attributes for Interoperation](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/attributes.md#216-attributes-for-interoperation) - **Interoperation with COM and Win32 components**: This heading has been removed. - **Interoperation with other .NET languages**: This heading has been removed. - **The IndexerName attribute**: This heading has been removed. ================================================ FILE: spec/basic-concepts.md ================================================ # Basic concepts This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Basic concepts](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#7-basic-concepts) - [Application Startup](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#71-application-startup) - [Application termination](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#72-application-termination) - [Declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#73-declarations) - [Members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#74-members) - [Namespace members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#742-namespace-members) - [Struct members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#743-struct-members) - [Enumeration members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#744-enumeration-members) - [Class members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#745-class-members) - [Interface members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#746-interface-members) - [Array members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#747-array-members) - [Delegate members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#748-delegate-members) - [Member access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#75-member-access) - [Declared accessibility](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#752-declared-accessibility) - [Accessibility domains](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#753-accessibility-domains) - [Protected access for instance members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#754-protected-access) - [Accessibility constraints](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#755-accessibility-constraints) - [Signatures and overloading](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#76-signatures-and-overloading) - [Scopes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#77-scopes) - [Name hiding](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#772-name-hiding) - [Hiding through nesting](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#7722-hiding-through-nesting) - [Hiding through inheritance](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#7723-hiding-through-inheritance) - [Namespace and type names](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#78-namespace-and-type-names) - [Fully qualified names](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#783-fully-qualified-names) - [Automatic memory management](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#79-automatic-memory-management) - [Execution order](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/basic-concepts.md#710-execution-order) ================================================ FILE: spec/classes.md ================================================ # Classes This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14-classes) - [Class declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#142-class-declarations) - [Class modifiers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1422-class-modifiers) - [Abstract classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14222-abstract-classes) - [Sealed classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14223-sealed-classes) - [Static classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14224-static-classes) - [Partial modifier](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1427-partial-declarations) - [Type parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1423-type-parameters) - [Class base specification](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1424-class-base-specification) - [Base classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14242-base-classes) - [Interface implementations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14243-interface-implementations) - [Type parameter constraints](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1425-type-parameter-constraints) - [Class body](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1426-class-body) - [Partial types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1427-partial-declarations) - Note that the following headings (except partial methods) don't exist in the standard. The text is all contained in this section. - Attributes - Modifiers - Type parameters and constraints - Base class - Base interfaces - Members - [Partial methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1469-partial-methods) - Name binding - [Class members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#143-class-members) - [The instance type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1432-the-instance-type) - [Members of constructed types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1433-members-of-constructed-types) - [Inheritance](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1434-inheritance) - [The new modifier](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1435-the-new-modifier) - [Access modifiers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1436-access-modifiers) - [Constituent types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1437-constituent-types) - [Static and instance members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1438-static-and-instance-members) - [Nested types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1439-nested-types) - [Fully qualified name](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14392-fully-qualified-name) - [Declared accessibility](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14393-declared-accessibility) - [Hiding](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14394-hiding) - [this access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14395-this-access) - [Access to private and protected members of the containing type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14396-access-to-private-and-protected-members-of-the-containing-type) - [Nested types in generic classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14397-nested-types-in-generic-classes) - [Reserved member names](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14310-reserved-member-names) - [Member names reserved for properties](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#143102-member-names-reserved-for-properties) - [Member names reserved for events](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#143103-member-names-reserved-for-events) - [Member names reserved for indexers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#143104-member-names-reserved-for-indexers) - [Member names reserved for destructors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#143105-member-names-reserved-for-finalizers) - [Constants](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#144-constants) - [Fields](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#145-fields) - [Static and instance fields](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1452-static-and-instance-fields) - [Readonly fields](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1453-readonly-fields) - [Using static readonly fields for constants](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14532-using-static-readonly-fields-for-constants) - [Versioning of constants and static readonly fields](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14533-versioning-of-constants-and-static-readonly-fields) - [Volatile fields](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1454-volatile-fields) - [Field initialization](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1455-field-initialization) - [Variable initializers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1456-variable-initializers) - [Static field initialization](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14562-static-field-initialization) - [Instance field initialization](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14563-instance-field-initialization) - [Methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#146-methods) - [Method parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1462-method-parameters) - [Value parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14622-value-parameters) - [Reference parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14623-reference-parameters) - [Output parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14624-output-parameters) - [Parameter arrays](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14625-parameter-arrays) - [Static and instance methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1463-static-and-instance-methods) - [Virtual methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1464-virtual-methods) - [Override methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1465-override-methods) - [Sealed methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1466-sealed-methods) - [Abstract methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1467-abstract-methods) - [External methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1468-external-methods) - [Partial methods (recap)](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1469-partial-methods) - [Extension methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14610-extension-methods) - [Method body](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14611-method-body) - [Method overloading](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#146-methods) - [Properties](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#147-properties) - [Static and instance properties](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1472-static-and-instance-properties) - [Accessors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1473-accessors) - [Automatically implemented properties](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1474-automatically-implemented-properties) - [Accessibility](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1475-accessibility) - [Virtual, sealed, override, and abstract property accessors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1476-virtual-sealed-override-and-abstract-accessors) - [Events](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#148-events) - [Field-like events](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1482-field-like-events) - [Event accessors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1483-event-accessors) - [Static and instance events](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1484-static-and-instance-events) - [Virtual, sealed, override, and abstract event accessors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1485-virtual-sealed-override-and-abstract-accessors) - [Indexers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#149-indexers) - [Indexer overloading](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#149-indexers) - [Operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1410-operators) - [Unary operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14102-unary-operators) - [Binary operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14103-binary-operators) - [Conversion operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14104-conversion-operators) - [Instance constructors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1411-instance-constructors) - [Constructor initializers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14112-constructor-initializers) - [Instance variable initializers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14113-instance-variable-initializers) - [Constructor execution](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14114-constructor-execution) - [Default constructors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14115-default-constructors) - [Private constructors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1411-instance-constructors) - [Optional instance constructor parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1411-instance-constructors) - [Static constructors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1412-static-constructors) - [Destructors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1413-finalizers) - [Iterators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1414-iterators) - [Enumerator interfaces](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14142-enumerator-interfaces) - [Enumerable interfaces](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14143-enumerable-interfaces) - [Yield type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14144-yield-type) - [Enumerator objects](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14145-enumerator-objects) - [The MoveNext method](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#141452-the-movenext-method) - [The Current property](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#141453-the-current-property) - [The Dispose method](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#141454-the-dispose-method) - [Enumerable objects](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14146-enumerable-objects) - [The GetEnumerator method](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#141462-the-getenumerator-method) - [Implementation example](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1414-iterators) - [Async functions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1415-async-functions) - [Evaluation of a task-returning async function](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14152-evaluation-of-a-task-returning-async-function) - [Evaluation of a void-returning async function](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#14153-evaluation-of-a-void-returning-async-function) ================================================ FILE: spec/conversions.md ================================================ # Conversions This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md) - [Implicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#102-implicit-conversions) - [Identity conversion](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1022-identity-conversion) - [Implicit numeric conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1023-implicit-numeric-conversions) - [Implicit enumeration conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1024-implicit-enumeration-conversions) - [Implicit interpolated string conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1025-implicit-interpolated-string-conversions) - [Implicit nullable conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1026-implicit-nullable-conversions) - [Null literal conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1027-null-literal-conversions) - [Implicit reference conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1028-implicit-reference-conversions) - [Boxing conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1029-boxing-conversions) - [Implicit dynamic conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#10210-implicit-dynamic-conversions) - [Implicit constant expression conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#10211-implicit-constant-expression-conversions) - [Implicit conversions involving type parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#10212-implicit-conversions-involving-type-parameters) - [User-defined implicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#10213-user-defined-implicit-conversions) - [Anonymous function conversions and method group conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#10214-anonymous-function-conversions-and-method-group-conversions) - [Explicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#103-explicit-conversions) - [Explicit numeric conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1032-explicit-numeric-conversions) - [Explicit enumeration conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1033-explicit-enumeration-conversions) - [Explicit nullable conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1034-explicit-nullable-conversions) - [Explicit reference conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1035-explicit-reference-conversions) - [Unboxing conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1036-unboxing-conversions) - [Explicit dynamic conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1037-explicit-dynamic-conversions) - [Explicit conversions involving type parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1038-explicit-conversions-involving-type-parameters) - [User-defined explicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1039-user-defined-explicit-conversions) - [Standard conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#104-standard-conversions) - [Standard implicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1042-standard-implicit-conversions) - [Standard explicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1043-standard-explicit-conversions) - [User-defined conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#105-user-defined-conversions) - [Permitted user-defined conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1052-permitted-user-defined-conversions) - [Lifted conversion operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1062-lifted-conversions) - [Evaluation of user-defined conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1053-evaluation-of-user-defined-conversions) - [Processing of user-defined implicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1054-user-defined-implicit-conversions) - [Processing of user-defined explicit conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1055-user-defined-explicit-conversions) - [Anonymous function conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#107-anonymous-function-conversions) - [Evaluation of anonymous function conversions to delegate types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1072-evaluation-of-anonymous-function-conversions-to-delegate-types) - [Evaluation of anonymous function conversions to expression tree types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1073-evaluation-of-lambda-expression-conversions-to-expression-tree-types) - [Implementation example](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#1073-evaluation-of-lambda-expression-conversions-to-expression-tree-types) - [Method group conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/conversions.md#108-method-group-conversions) ================================================ FILE: spec/delegates.md ================================================ # Delegates This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Delegates](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#19-delegates) - [Delegate declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#192-delegate-declarations) - [Delegate compatibility](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#194-delegate-compatibility) - [Delegate instantiation](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#195-delegate-instantiation) - [Delegate invocation](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/delegates.md#196-delegate-invocation) ================================================ FILE: spec/documentation-comments.md ================================================ # Documentation comments This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Documentation comments](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#annex-d-documentation-comments) - [Introduction](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d2-introduction) - [Recommended tags](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d3-recommended-tags) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d32-c) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d33-code) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d34-example) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d35-exception)) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d36-include) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d37-list) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d38-para) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d39-param) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d310-paramref) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d311-permission) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d312-remarks) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d313-returns) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d314-see) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d315-seealso) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d316-summary) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d319-value) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d317-typeparam) - [``](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d318-typeparamref) - [Processing the documentation file](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d4-processing-the-documentation-file) - [ID string format](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d42-id-string-format) - [ID string examples](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d43-id-string-examples) - [An example](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d5-an-example) - [C# source code](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d51-c-source-code) - [Resulting XML](https://github.com/dotnet/csharpstandard/blob/standard-v6/standard/documentation-comments.md#d52-resulting-xml) ================================================ FILE: spec/enums.md ================================================ # Enums This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Enums](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#18-enums) - [Enum declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#182-enum-declarations) - [Enum modifiers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#183-enum-modifiers) - [Enum members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#184-enum-members) - [The System.Enum type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#185-the-systemenum-type) - [Enum values and operations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/enums.md#186-enum-values-and-operations) ================================================ FILE: spec/exceptions.md ================================================ # Exceptions This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Exceptions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#20-exceptions) - [Causes of exceptions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#202-causes-of-exceptions) - [The System.Exception class](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#203-the-systemexception-class) - [How exceptions are handled](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#204-how-exceptions-are-handled) - [Common Exception Classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/exceptions.md#205-common-exception-classes) ================================================ FILE: spec/expressions.md ================================================ # Expressions This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md) - [Expression classifications](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#112-expression-classifications) - [Values of expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1122-values-of-expressions) - [Static and Dynamic Binding](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#113-static-and-dynamic-binding) - [Binding-time](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1132-binding-time) - [Dynamic binding](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1133-dynamic-binding) - [Types of constituent expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1134-types-of-subexpressions) - [Operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#114-operators) - [Operator precedence and associativity](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1142-operator-precedence-and-associativity) - [Operator overloading](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1143-operator-overloading) - [Unary operator overload resolution](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1144-unary-operator-overload-resolution) - [Binary operator overload resolution](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1145-binary-operator-overload-resolution) - [Candidate user-defined operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1146-candidate-user-defined-operators) - [Numeric promotions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1147-numeric-promotions) - [Unary numeric promotions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11472-unary-numeric-promotions) - [Binary numeric promotions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11473-binary-numeric-promotions) - [Lifted operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1148-lifted-operators) - [Member lookup](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#115-member-lookup) - [Base types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1152-base-types) - [Function members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116-function-members) - [Argument lists](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1162-argument-lists) - [Corresponding parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11622-corresponding-parameters) - [Run-time evaluation of argument lists](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11623-run-time-evaluation-of-argument-lists) - [Type inference](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1163-type-inference) - [The first phase](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11632-the-first-phase) - [The second phase](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11633-the-second-phase) - [Input types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11634-input-types) - [Output types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11635-output-types) - [Dependence](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11636-dependence) - [Output type inferences](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11637-output-type-inferences) - [Explicit parameter type inferences](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11638-explicit-parameter-type-inferences) - [Exact inferences](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11639-exact-inferences) - [Lower-bound inferences](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116310-lower-bound-inferences) - [Upper-bound inferences](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116311-upper-bound-inferences) - [Fixing](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116312-fixing) - [Inferred return type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116313-inferred-return-type) - [Type inference for conversion of method groups](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116314-type-inference-for-conversion-of-method-groups) - [Finding the best common type of a set of expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#116315-finding-the-best-common-type-of-a-set-of-expressions) - [Overload resolution](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1164-overload-resolution) - [Applicable function member](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11642-applicable-function-member) - [Better function member](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11643-better-function-member) - [Better conversion from expression](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11644-better-conversion-from-expression) - [Exactly matching Expression](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11645-better-conversion-from-expression) - [Better conversion target](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11646-better-conversion-target) - [Overloading in generic classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11647-overloading-in-generic-classes) - [Compile-time checking of dynamic overload resolution](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1165-compile-time-checking-of-dynamic-member-invocation) - [Function member invocation](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1166-function-member-invocation) - [Invocations on boxed instances](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11662-invocations-on-boxed-instances) - [Primary expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117-primary-expressions) - [Literals](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1172-literals) - [Interpolated strings](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1173-interpolated-string-expressions) - [Simple names](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1174-simple-names) - [Parenthesized expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1175-parenthesized-expressions) - [Member access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1176-member-access) - [Identical simple names and type names](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11762-identical-simple-names-and-type-names) - [Grammar ambiguities](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#625-grammar-ambiguities) - [Invocation expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1178-invocation-expressions) - [Method invocations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11782-method-invocations) - [Extension method invocations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11783-extension-method-invocations) - [Delegate invocations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11784-delegate-invocations) - [Element access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11710-element-access) - [Array access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117102-array-access) - [Indexer access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117103-indexer-access) - [This access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11712-this-access) - [Base access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11713-base-access) - [Postfix increment and decrement operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11714-postfix-increment-and-decrement-operators) - [The new operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11715-the-new-operator) - [Object creation expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117152-object-creation-expressions) - [Object initializers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117153-object-initializers) - [Collection initializers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117154-collection-initializers) - [Array creation expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117155-array-creation-expressions) - [Delegate creation expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117156-delegate-creation-expressions) - [Anonymous object creation expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#117157-anonymous-object-creation-expressions) - [The typeof operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11716-the-typeof-operator) - [The checked and unchecked operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11718-the-checked-and-unchecked-operators) - [Default value expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11719-default-value-expressions) - [Nameof expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11720-nameof-expressions) - [Anonymous method expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11721-anonymous-method-expressions) - [Unary operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#118-unary-operators) - **Null-conditional operator**: These sections have been rearranged. - **Null-conditional expressions as projection initializers**: These sections have been rearranged. - **Null-conditional expressions as statement expressions**: These sections have been rearranged. - [§11.7.7](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1177-null-conditional-member-access) Null Conditional Member Access - [§11.7.9](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1179-null-conditional-invocation-expression) Null Conditional Invocation Expression - [§11.7.11](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11711-null-conditional-element-access) Null Conditional Element Access - [Unary plus operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1182-unary-plus-operator) - [Unary minus operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1183-unary-minus-operator) - [Logical negation operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1184-logical-negation-operator) - [Bitwise complement operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1185-bitwise-complement-operator) - [Prefix increment and decrement operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1186-prefix-increment-and-decrement-operators) - [Cast expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1187-cast-expressions) - [Await expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1188-await-expressions) - [Awaitable expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11882-awaitable-expressions) - [Classification of await expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11883-classification-of-await-expressions) - [Runtime evaluation of await expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11884-run-time-evaluation-of-await-expressions) - [Arithmetic operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#119-arithmetic-operators) - [Multiplication operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1192-multiplication-operator) - [Division operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1193-division-operator) - [Remainder operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1194-remainder-operator) - [Addition operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1195-addition-operator) - [Subtraction operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1196-subtraction-operator) - [Shift operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1110-shift-operators) - [Relational and type-testing operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1111-relational-and-type-testing-operators) - [Integer comparison operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11112-integer-comparison-operators) - [Floating-point comparison operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11113-floating-point-comparison-operators) - [Decimal comparison operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11114-decimal-comparison-operators) - [Boolean equality operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11115-boolean-equality-operators) - [Enumeration comparison operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11116-enumeration-comparison-operators) - [Reference type equality operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11117-reference-type-equality-operators) - [String equality operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11118-string-equality-operators) - [Delegate equality operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11119-delegate-equality-operators) - [Equality operators and null](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111110-equality-operators-between-nullable-value-types-and-the-null-literal) - [The is operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111111-the-is-operator) - [The as operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111112-the-as-operator) - [Logical operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1112-logical-operators) - [Integer logical operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11122-integer-logical-operators) - [Enumeration logical operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11123-enumeration-logical-operators) - [Boolean logical operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11124-boolean-logical-operators) - [Nullable boolean logical operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11125-nullable-boolean--and--operators) - [Conditional logical operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1113-conditional-logical-operators) - [Boolean conditional logical operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11132-boolean-conditional-logical-operators) - [User-defined conditional logical operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11133-user-defined-conditional-logical-operators) - [The null coalescing operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1114-the-null-coalescing-operator) - [Conditional operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1115-conditional-operator) - [Anonymous function expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1116-anonymous-function-expressions) - [Anonymous function signatures](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11162-anonymous-function-signatures) - [Anonymous function bodies](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11163-anonymous-function-bodies) - [Overload resolution and anonymous functions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11164-overload-resolution) - [Anonymous functions and dynamic binding](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11165-anonymous-functions-and-dynamic-binding) - [Outer variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11166-outer-variables) - [Captured outer variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111662-captured-outer-variables) - [Instantiation of local variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111663-instantiation-of-local-variables) - [Evaluation of anonymous function expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11167-evaluation-of-anonymous-function-expressions) - [Query expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1117-query-expressions) - [Ambiguities in query expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11172-ambiguities-in-query-expressions) - [Query expression translation](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11173-query-expression-translation) - [Select and groupby clauses with continuations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111732-select-and-group--by-clauses-with-continuations) - [Explicit range variable types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111733-explicit-range-variable-types) - [Degenerate query expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111734-degenerate-query-expressions) - [From, let, where, join and orderby clauses](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111735-from-let-where-join-and-orderby-clauses) - [Select clauses](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111736-select-clauses) - [Groupby clauses](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111737-group-clauses) - [Transparent identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#111738-transparent-identifiers) - [The query expression pattern](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11174-the-query-expression-pattern) - [Assignment operators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1118-assignment-operators) - [Simple assignment](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11182-simple-assignment) - [Compound assignment](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11183-compound-assignment) - [Event assignment](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#11184-event-assignment) - [Expression](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1119-expression) - [Constant expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1120-constant-expressions) - [Boolean expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/expressions.md#1121-boolean-expressions) ================================================ FILE: spec/interfaces.md ================================================ # Interfaces This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Interfaces](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#17-interfaces) - [Interface declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#172-interface-declarations) - [Interface modifiers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1722-interface-modifiers) - [Partial modifier](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/classes.md#1427-partial-declarations) - [Variant type parameter lists](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1723-variant-type-parameter-lists) - [Variance safety](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#17232-variance-safety) - [Variance conversion](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#17233-variance-conversion) - [Base interfaces](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1724-base-interfaces) - [Interface body](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#173-interface-body) - [Interface members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#174-interface-members) - [Interface methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1742-interface-methods) - [Interface properties](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1743-interface-properties) - [Interface events](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1744-interface-events) - [Interface indexers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1745-interface-indexers) - [Interface member access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1746-interface-member-access) - [Fully qualified interface member names](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#175-qualified-interface-member-names) - [Interface implementations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#176-interface-implementations) - [Explicit interface member implementations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1762-explicit-interface-member-implementations) - [Uniqueness of implemented interfaces](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1763-uniqueness-of-implemented-interfaces) - [Implementation of generic methods](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1764-implementation-of-generic-methods) - [Interface mapping](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1765-interface-mapping) - [Interface implementation inheritance](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1766-interface-implementation-inheritance) - [Interface re-implementation](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1767-interface-re-implementation) - [Abstract classes and interfaces](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/interfaces.md#1768-abstract-classes-and-interfaces) ================================================ FILE: spec/introduction.md ================================================ # Introduction This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. This section has been removed from the standard. The [standard/README.md](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/README.md) has a detailed table of contents for the standard. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. ================================================ FILE: spec/lexical-structure.md ================================================ # Lexical structure This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Lexical structure](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md) - [Programs](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#61-programs) - [Grammars](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#62-grammars) - [Grammar notation](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#622-grammar-notation) - [Lexical grammar](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#623-lexical-grammar) - [Syntactic grammar](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#624-syntactic-grammar) - [Lexical analysis](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#63-lexical-analysis) - [Line terminators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#632-line-terminators) - [Comments](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#633-comments) - [White space](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#634-white-space) - [Tokens](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#64-tokens) - [Unicode character escape sequences](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#642-unicode-character-escape-sequences) - [Identifiers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#643-identifiers) - [Keywords](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#644-keywords) - [Literals](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#645-literals) - [Boolean literals](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#6452-boolean-literals) - [Integer literals](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#6453-integer-literals) - [Real literals](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#6454-real-literals) - [Character literals](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#6455-character-literals) - [String literals](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#6456-string-literals) - [Interpolated string literals](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#6456-string-literals) - [The null literal](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#6457-the-null-literal) - [Operators and punctuators](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#646-operators-and-punctuators)) - [Pre-processing directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#65-pre-processing-directives) - [Conditional compilation symbols](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#652-conditional-compilation-symbols) - [Pre-processing expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#653-pre-processing-expressions) - [Declaration directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#654-definition-directives) - [Conditional compilation directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#655-conditional-compilation-directives) - [Diagnostic directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#656-diagnostic-directives) - [Region directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#657-region-directives) - [Line directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#658-line-directives) - [Pragma directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#659-pragma-directives) - [Pragma warning](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/lexical-structure.md#659-pragma-directives) ================================================ FILE: spec/namespaces.md ================================================ # Namespaces This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Namespaces](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#13-namespaces) - [Compilation units](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#132-compilation-units) - [Namespace declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#133-namespace-declarations) - [Extern aliases](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#134-extern-alias-directives) - [Using directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#135-using-directives) - [Using alias directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#1352-using-alias-directives) - [Using namespace directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#1353-using-namespace-directives) - [Using static directives](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#1354-using-static-directives) - [Namespace members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#136-namespace-member-declarations) - [Type declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#137-type-declarations) - [Namespace alias qualifiers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#138-qualified-alias-member) - [Uniqueness of aliases](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/namespaces.md#1382-uniqueness-of-aliases) ================================================ FILE: spec/statements.md ================================================ # Statements This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#12-statements) - [End points and reachability](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#122-end-points-and-reachability) - [Blocks](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#123-blocks) - [Statement lists](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1232-statement-lists) - [The empty statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#124-the-empty-statement) - [Labeled statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#125-labeled-statements) - [Declaration statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#126-declaration-statements) - [Local variable declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1262-local-variable-declarations) - [Local constant declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1263-local-constant-declarations) - [Expression statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#127-expression-statements) - [Selection statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#128-selection-statements) - [The if statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1282-the-if-statement) - [The switch statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1283-the-switch-statement) - [Iteration statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#129-iteration-statements) - [The while statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1292-the-while-statement) - [The do statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1293-the-do-statement) - [The for statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1294-the-for-statement) - [The foreach statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1295-the-foreach-statement) - [Jump statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1210-jump-statements) - [The break statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#12102-the-break-statement) - [The continue statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#12103-the-continue-statement) - [The goto statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#12104-the-goto-statement) - [The return statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#12105-the-return-statement) - [The throw statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#12106-the-throw-statement) - [The try statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1211-the-try-statement) - [The checked and unchecked statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1212-the-checked-and-unchecked-statements) - [The lock statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1213-the-lock-statement) - [The using statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1214-the-using-statement) - [The yield statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/statements.md#1215-the-yield-statement) ================================================ FILE: spec/structs.md ================================================ # Structs This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Structs](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#15-structs) - [Struct declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#152-struct-declarations) - [Struct modifiers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1522-struct-modifiers) - [Partial modifier](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1523-partial-modifier) - [Struct interfaces](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1524-struct-interfaces) - [Struct body](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1525-struct-body) - [Struct members](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#153-struct-members) - [Class and struct differences](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#154-class-and-struct-differences) - [Value semantics](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1542-value-semantics) - [Inheritance](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1543-inheritance) - [Assignment](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1544-assignment) - [Default values](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1545-default-values) - [Boxing and unboxing](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1546-boxing-and-unboxing) - [Meaning of this](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1547-meaning-of-this) - [Field initializers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1548-field-initializers) - [Constructors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#1549-constructors) - **Destructors**: This section has been removed - [Static constructors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/structs.md#15410-static-constructors) - **Struct examples** These sections have been removed. - Database integer type - Database boolean type ================================================ FILE: spec/types.md ================================================ # Types This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md) - [Value types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#83-value-types) - [The System.ValueType type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#832-the-systemvaluetype-type) - [Default constructors](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#833-default-constructors) - [Struct types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#834-struct-types) - [Simple types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#835-simple-types) - [Integral types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#836-integral-types) - [Floating point types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#837-floating-point-types) - [The decimal type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#838-the-decimal-type) - [The bool type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#839-the-bool-type) - [Enumeration types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#8310-enumeration-types) - [Nullable types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#8311-nullable-value-types) - [Reference types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#82-reference-types) - [Class types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#822-class-types) - [The object type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#823-the-object-type) - [The dynamic type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#824-the-dynamic-type) - [The string type](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#825-the-string-type) - [Interface types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#826-interface-types) - [Array types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#827-array-types) - [Delegate types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#828-delegate-types) - [Boxing and unboxing](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#8312-boxing-and-unboxing) - [Boxing conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#8312-boxing-and-unboxing) - [Unboxing conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#8312-boxing-and-unboxing) - [Constructed types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#84-constructed-types) - [Type arguments](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#842-type-arguments) - [Open and closed types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#843-open-and-closed-types) - [Bound and unbound types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#844-bound-and-unbound-types) - [Satisfying constraints](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#845-satisfying-constraints) - [Type parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#85-type-parameters) - [Expression tree types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/types.md#86-expression-tree-types) ================================================ FILE: spec/unsafe-code.md ================================================ # Unsafe code This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Unsafe code](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#22-unsafe-code) - [Unsafe contexts](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#222-unsafe-contexts) - [Pointer types](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#223-pointer-types) - [Fixed and moveable variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#224-fixed-and-moveable-variables) - [Pointer conversions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#225-pointer-conversions) - [Pointer arrays](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2252-pointer-arrays) - [Pointers in expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#226-pointers-in-expressions) - [Pointer indirection](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2262-pointer-indirection) - [Pointer member access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2263-pointer-member-access) - [Pointer element access](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2264-pointer-element-access) - [The address-of operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2265-the-address-of-operator) - [Pointer increment and decrement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2266-pointer-increment-and-decrement) - [Pointer arithmetic](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2267-pointer-arithmetic) - [Pointer comparison](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2268-pointer-comparison) - [The sizeof operator](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2269-the-sizeof-operator) - [The fixed statement](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#227-the-fixed-statement) - [Fixed size buffers](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#228-fixed-size-buffers) - [Fixed size buffer declarations](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2282-fixed-size-buffer-declarations) - [Fixed size buffers in expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2283-fixed-size-buffers-in-expressions) - [Definite assignment checking](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#2284-definite-assignment-checking) - [Stack allocation](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/unsafe-code.md#229-stack-allocation) - **Dynamic memory allocation**: Not included in the standard. ================================================ FILE: spec/variables.md ================================================ # Variables This content has moved to the [`dotnet/csharpstandard`](https://github.com/dotnet/csharpstandard) repository. The list below provides links to each heading in this section. The links specify the C# 6 branch, which is version when the specifications merged. > To view the text of the Microsoft spec before merging with the ECMA text, checkout the [ms-spec-text](https://github.com/dotnet/csharplang/releases/tag/ms-spec-text) tag in this repository. - [Variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md) - [Variable categories](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#92-variable-categories) - [Static variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#922-static-variables) - [Instance variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#923-instance-variables) - [Instance variables in classes](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9232-instance-variables-in-classes) - [Instance variables in structs](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9233-instance-variables-in-structs) - [Array elements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#924-array-elements) - [Value parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#925-value-parameters) - [Reference parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#926-reference-parameters) - [Output parameters](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#927-output-parameters) - [Local variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#928-local-variables) - [Default values](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#93-default-values) - [Definite assignment](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94-definite-assignment) - [Initially assigned variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#942-initially-assigned-variables) - [Initially unassigned variables](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#943-initially-unassigned-variables) - [Precise rules for determining definite assignment](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#944-precise-rules-for-determining-definite-assignment) - [General rules for statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9442-general-rules-for-statements) - [Block statements, checked, and unchecked statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9443-block-statements-checked-and-unchecked-statements) - [Expression statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9444-expression-statements) - [Declaration statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9445-declaration-statements) - [If statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9446-if-statements) - [Switch statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9447-switch-statements) - [While statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9448-while-statements) - [Do statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#9449-do-statements) - [For statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94410-for-statements) - [Break, continue, and goto statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94411-break-continue-and-goto-statements) - [Throw statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94412-throw-statements) - [Return statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94413-return-statements) - [Try-catch statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94414-try-catch-statements) - [Try-finally statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94415-try-finally-statements) - [Try-catch-finally statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94416-try-catch-finally-statements) - [Foreach statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94417-foreach-statements) - [Using statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94418-using-statements) - [Lock statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94419-lock-statements) - [Yield statements](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94420-yield-statements) - [General rules for simple expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94422-general-rules-for-simple-expressions) - [General rules for expressions with embedded expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94423-general-rules-for-expressions-with-embedded-expressions) - [Invocation expressions and object creation expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94424-invocation-expressions-and-object-creation-expressions) - [Simple assignment expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94425-simple-assignment-expressions) - [&& (conditional AND) expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94426--expressions) - [|| (conditional OR) expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94427--expressions) - [! (logical negation) expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94428--expressions) - [?? (null coalescing) expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94429--expressions) - [?: (conditional) expressions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94430--expressions) - [Anonymous functions](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#94431-anonymous-functions) - [Variable references](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#95-variable-references) - [Atomicity of variable references](https://github.com/dotnet/csharpstandard/blob/draft-v6/standard/variables.md#96-atomicity-of-variable-references)