├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── CSharpNine-Finished ├── CSharpNine.csproj ├── CSharpNine.sln └── Program.cs ├── CSharpNine ├── CSharpNine.csproj ├── CSharpNine.sln └── Program.cs ├── CSharpTen-Finished ├── CSharpTen.csproj ├── CSharpTen.sln ├── OtherStuff.cs ├── Person.cs ├── Program.cs └── Usings.cs ├── CSharpTen ├── CSharpTen.csproj ├── CSharpTen.sln ├── OtherStuff.cs ├── Program.cs └── Usings.cs ├── DemoScript-CSharp10.md ├── DemoScript-CSharp9.md ├── LICENSE ├── README.md └── WhatsNewInCSharp.pptx /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | project: 15 | - CSharpNine/CSharpNine 16 | - CSharpNine-Finished/CSharpNine 17 | - CSharpTen/CSharpTen 18 | - CSharpTen-Finished/CSharpTen 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Setup .NET 22 | uses: actions/setup-dotnet@v2 23 | with: 24 | dotnet-version: 6.0.x 25 | - name: Restore dependencies for ${{ matrix.project }} 26 | run: dotnet restore ./${{ matrix.project }}.csproj 27 | - name: Build for ${{ matrix.project }} 28 | run: dotnet build ./${{ matrix.project }}.csproj --no-restore 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /CSharpNine-Finished/CSharpNine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CSharpNine-Finished/CSharpNine.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharp9Workshop", "CSharp9Workshop.csproj", "{178BD9E4-CEBA-4F7E-89DE-640C75D7E450}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {178BD9E4-CEBA-4F7E-89DE-640C75D7E450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {178BD9E4-CEBA-4F7E-89DE-640C75D7E450}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {178BD9E4-CEBA-4F7E-89DE-640C75D7E450}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {178BD9E4-CEBA-4F7E-89DE-640C75D7E450}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3CDB78E0-A0D4-40AB-845A-D6D2BA297220} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /CSharpNine-Finished/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static System.Console; 3 | 4 | Person person = new Student("Scott", "Hunter") 5 | { 6 | Gpa = 3.8, 7 | }; 8 | 9 | var otherPerson = person with 10 | { 11 | LastName = "Hanselman" 12 | }; 13 | 14 | WriteLine(person); 15 | WriteLine(otherPerson); 16 | 17 | var originalPerson = otherPerson with 18 | { 19 | LastName = "Hunter" 20 | }; 21 | 22 | WriteLine($"Equals: {Equals(person, originalPerson)}"); 23 | WriteLine($"Reference Equals: {ReferenceEquals(person, originalPerson)}"); 24 | var p = new Person("Scott", "Hunter"); 25 | WriteLine($"Person and Student: Equals: {Equals(person, p)}"); 26 | 27 | var (first, last) = person; 28 | WriteLine($"{first}, {last}"); 29 | 30 | WriteLine($"Person status: {PrintStudentHonorarium(p)}"); 31 | WriteLine($"Student status: {PrintStudentHonorarium(otherPerson)}"); 32 | 33 | 34 | static string PrintStudentHonorarium(Person p) 35 | { 36 | return p switch 37 | { 38 | null => throw new ArgumentNullException(nameof(p), "Person can't be null"), 39 | Student s => s.Gpa switch 40 | { 41 | < 3.0 and > 1.0 => "Satisfactory", 42 | 4.0 => "Distinguished honors", 43 | >= 3.5 => "High honors", 44 | >= 3.0 => "Honors", 45 | _ => "pass" 46 | }, 47 | Person _ => "graduate", 48 | }; 49 | } 50 | 51 | record Person(string FirstName, string LastName); 52 | 53 | record Student(string FirstName, string LastName) : Person(FirstName, LastName) 54 | { 55 | public double Gpa { get; set; } = 4.0; 56 | } 57 | -------------------------------------------------------------------------------- /CSharpNine/CSharpNine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CSharpNine/CSharpNine.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpNine", "CSharpNine.csproj", "{5D153A05-318B-47D0-A4CE-2E5B1515AA2D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5D153A05-318B-47D0-A4CE-2E5B1515AA2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5D153A05-318B-47D0-A4CE-2E5B1515AA2D}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5D153A05-318B-47D0-A4CE-2E5B1515AA2D}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5D153A05-318B-47D0-A4CE-2E5B1515AA2D}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {7FC69436-666B-4F35-8144-56EEFCAE7511} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /CSharpNine/Program.cs: -------------------------------------------------------------------------------- 1 | using static System.Console; 2 | 3 | class Program 4 | { 5 | static void Main(string[] args) 6 | { 7 | var person = new Person 8 | { 9 | FirstName = "Scott", 10 | LastName = "Hunter" 11 | }; 12 | 13 | DisplayPerson(person); 14 | 15 | static void DisplayPerson(Person person) 16 | { 17 | WriteLine($"{person.FirstName} {person.LastName}"); 18 | } 19 | } 20 | } 21 | 22 | class Person 23 | { 24 | public string FirstName { get; set; } 25 | public string LastName { get; set; } 26 | } 27 | 28 | class Student : Person 29 | { 30 | public double Gpa { get; set; } = 4.0; 31 | } 32 | -------------------------------------------------------------------------------- /CSharpTen-Finished/CSharpTen.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /CSharpTen-Finished/CSharpTen.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.31904.369 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpTen", "CSharpTen.csproj", "{52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {0F2E2DE9-4EB4-494E-933E-B88FA761AEAE} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /CSharpTen-Finished/OtherStuff.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Globalization; 3 | using System.Linq.Expressions; 4 | using System.Text; 5 | 6 | internal class Lambdas 7 | { 8 | public Lambdas() 9 | { 10 | LambdaExpression parse = (string s) => int.Parse(s); 11 | 12 | var choose = object (bool b) => b ? 1 : "two"; 13 | } 14 | } 15 | 16 | internal class MethodGroups 17 | { 18 | public MethodGroups() 19 | { 20 | var read = Console.Read; 21 | Action write = Console.Write; 22 | } 23 | } 24 | 25 | internal class InterpolatedStringHandlers 26 | { 27 | public string BuildString(object[] args) 28 | { 29 | var sb = new StringBuilder(); 30 | sb.Append($"Hello {args[0]}, how are you?"); 31 | 32 | return sb.ToString(); 33 | } 34 | 35 | public void DebugAssert(bool condition) 36 | { 37 | Debug.Assert(condition, $"{DateTime.Now} - {ExpensiveCalculation()}"); 38 | } 39 | 40 | public string CreateInvariantString(int result) 41 | => string.Create(CultureInfo.InvariantCulture, $"The result is {result}"); 42 | 43 | private static object ExpensiveCalculation() 44 | { 45 | Thread.Sleep(1000); 46 | return 0; 47 | } 48 | } -------------------------------------------------------------------------------- /CSharpTen-Finished/Person.cs: -------------------------------------------------------------------------------- 1 | namespace Model; 2 | 3 | struct Person 4 | { 5 | public Person(string firstName, string lastName) 6 | { 7 | FirstName = firstName; 8 | LastName = lastName; 9 | } 10 | public string FirstName { get; init; } = "John"; 11 | public string LastName { get; init; } = "Doe"; 12 | 13 | public void WriteToFile(string filePath) 14 | => File.WriteAllText(filePath, ToString()); 15 | } 16 | -------------------------------------------------------------------------------- /CSharpTen-Finished/Program.cs: -------------------------------------------------------------------------------- 1 | using Model; 2 | using static System.Console; 3 | 4 | var person = new 5 | { 6 | FirstName = "Scott", 7 | LastName = "Hunter" 8 | }; 9 | 10 | var otherPerson = person with { LastName = "Hanselman" }; 11 | 12 | WriteLine(person); 13 | WriteLine(otherPerson); 14 | 15 | var originalPerson = otherPerson with { LastName = "Hunter" }; 16 | 17 | WriteLine(originalPerson); 18 | WriteLine($"Equals: {Equals(person, originalPerson)}"); 19 | WriteLine($"== operator: {person == originalPerson}"); 20 | 21 | Person p1 = default; 22 | Person p2 = new(); 23 | -------------------------------------------------------------------------------- /CSharpTen-Finished/Usings.cs: -------------------------------------------------------------------------------- 1 | global using static System.Console; -------------------------------------------------------------------------------- /CSharpTen/CSharpTen.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CSharpTen/CSharpTen.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.31904.369 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpTen", "CSharpTen.csproj", "{52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {52FAE2DE-231B-4DF0-BE59-681CEA6B3FE3}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {0F2E2DE9-4EB4-494E-933E-B88FA761AEAE} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /CSharpTen/OtherStuff.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Globalization; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | internal class Lambdas 8 | { 9 | public Lambdas() 10 | { 11 | Func parse = (string s) => int.Parse(s); 12 | 13 | Func choose = (bool b) => b ? 1 : "two"; 14 | } 15 | } 16 | 17 | internal class MethodGroups 18 | { 19 | public MethodGroups() 20 | { 21 | Func read = Console.Read; 22 | Action write = Console.Write; 23 | } 24 | } 25 | 26 | internal class InterpolatedStringHandlers 27 | { 28 | public string BuildString(object[] args) 29 | { 30 | var sb = new StringBuilder(); 31 | sb.Append($"Hello {args[0]}, how are you?"); 32 | 33 | return sb.ToString(); 34 | } 35 | 36 | public void DebugAssert(bool condition) 37 | { 38 | Debug.Assert(condition, $"{DateTime.Now} - {ExpensiveCalculation()}"); 39 | } 40 | 41 | public string CreateInvariantString(int result) 42 | => string.Create(CultureInfo.InvariantCulture, $"The result is {result}"); 43 | 44 | private static object ExpensiveCalculation() 45 | { 46 | Thread.Sleep(1000); 47 | return 0; 48 | } 49 | } -------------------------------------------------------------------------------- /CSharpTen/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using static System.Console; 3 | 4 | var person = new Person 5 | { 6 | FirstName = "Scott", 7 | LastName = "Hunter" 8 | }; 9 | 10 | var otherPerson = person with { LastName = "Hanselman" }; 11 | 12 | WriteLine(person); 13 | WriteLine(otherPerson); 14 | 15 | var originalPerson = otherPerson with { LastName = "Hunter" }; 16 | 17 | WriteLine(originalPerson); 18 | WriteLine($"Equals: {Equals(person, originalPerson)}"); 19 | WriteLine($"== operator: {person == originalPerson}"); 20 | 21 | record Person 22 | { 23 | public string FirstName { get; init; } 24 | public string LastName { get; init; } 25 | 26 | public void WriteToFile(string filePath) 27 | => File.WriteAllText(filePath, ToString()); 28 | } 29 | -------------------------------------------------------------------------------- /CSharpTen/Usings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using static System.Console; -------------------------------------------------------------------------------- /DemoScript-CSharp10.md: -------------------------------------------------------------------------------- 1 | # C# 10 Demo Script 2 | 3 | # Preamble 4 | 5 | 1. Open `Program.cs`. 6 | 2. Build and run (`Ctrl+F5`) to show the program output. 7 | 8 | # Record Improvements 9 | 10 | ## Record Structs 11 | 12 | Record structs have the same features and very similar semantics as record classes. They provide the full machinery for value equality, including an implementation of `IEquatable` and `==`/`!=` operators. They also support `with` expressions. 13 | 14 | 1. Add the `class` keyword after `record` for `Person`. It should now read as `record class Person`. 15 | 2. Change `class` to `struct` for `Person`. It should now read as `record struct Person`. 16 | 3. Build and run (`Ctrl+F5`) to show that the program output is unchanged. 17 | 18 | ## With Expressions for Non-Record Structs 19 | 20 | `with` expression support has been added for vanilla non-record structs. This works seamlessly because structs are copy-by-value. 21 | 22 | 1. Remove the `record` keyword from `Person`. It should now read as `struct Person`. 23 | 24 | ## With Expressions for Anonymous Types 25 | 26 | Semantically, anonymous types are really "anonymous records". So, we've made `with` expressions work with them as well. 27 | 28 | 1. Remove `Person` from `var person = new Person` on line 4. It should now read as `var person = new`. 29 | 2. Hover over `var` to show that `person` is now an anonymous type. 30 | 31 | ## Other Struct Improvements 32 | 33 | We've added several other features to vanilla non-record structs. First, it is now possible to declare public, parameterless constructor. 34 | 35 | 1. Add a public, parameterless constructor to `Person` that initializes both the `FirstName` and `LastName` properties. 36 | 37 | ```csharp 38 | struct Person 39 | { 40 | public Person() 41 | { 42 | FirstName = "John"; 43 | LastName = "Doe"; 44 | } 45 | 46 | public string FirstName { get; init; } 47 | public string LastName { get; init; } 48 | 49 | public void WriteToFile(string filePath) 50 | => File.WriteAllText(filePath, ToString()); 51 | } 52 | ``` 53 | 54 | 55 | This represents a change in philosophy for C# with regard to structs. Previously, we chose not allow public, parameterless constructors because it means that `default(SomeStruct)` may result in a different value than `new SomeString()`. So, the following code results in different `Person` values. 56 | 57 | 1. Add the following lines above the declaration of `Person` struct 58 | 59 | ```csharp 60 | Person p1 = default; 61 | Person p2 = new(); 62 | ``` 63 | 64 | 65 | In addition, it is now possible to use field and property initializers in structs, which weren't previously allowed for the same reason. 66 | 67 | 1. Remove the constructor that was just added and use initializers for the properties. 68 | 69 | ```csharp 70 | struct Person 71 | { 72 | public string FirstName { get; init; } = "John"; 73 | public string LastName { get; init; } = "Doe"; 74 | } 75 | ``` 76 | 77 | 78 | # Removing Clutter 79 | 80 | ## File-Scoped Namespaces 81 | 82 | Our project is starting to grow, so let's move `Person` into a separate file. Fortunately, the IDE has several refactorings that can help with that. 83 | 84 | 1. Move editor caret to `Person` and press `Ctrl+.` to bring up the list of available refactorings. 85 | 2. Choose "Move type to Person.cs" to move the `Person` struct to a new file. 86 | 3. Click on a reference to `Person` and press `F12` to go to the definition of `Person` in `Person.cs`. 87 | 4. Move the editor caret to `Person` and press `Ctrl+.` to bring up the list of available refactorings. 88 | 5. Choose "Move to namespace..." and type "Model" to move `Person` into a namespace named `Model`. 89 | 90 | It's unfortunate that namespaces have forced nearly every C# class to be indented. With file-scoped namespaces, that's no longer a concern. 91 | 92 | 1. Remove the curly braces for the `Model` namespace and add a `;` after `Model`. 93 | 94 | ```csharp 95 | using System.IO; 96 | 97 | namespace Model; 98 | 99 | struct Person 100 | { 101 | public Person(string firstName, string lastName) 102 | { 103 | FirstName = firstName; 104 | LastName = lastName; 105 | } 106 | 107 | public string FirstName { get; init; } = "John"; 108 | public string LastName { get; init; } = "Doe"; 109 | 110 | public void WriteToFile(string filePath) 111 | => File.WriteAllText(filePath, ToString()); 112 | } 113 | ``` 114 | 115 | ## Global Usings 116 | 117 | Another bit of C# clutter that has "infected" every C# file is the block of using directives. In C# 10, this can be cleaned up with global using directives, which are essentially using directives that apply throughout the project. 118 | 119 | 1. Open `Usings.cs`. 120 | 2. Move the editor caret before the first `using` and press `Shift+Alt+Down` until there is a vertical selection in front of all the using directives. 121 | 3. Type `global` before the using directives to transform all of them into global usings. 122 | 123 | We should go ahead and add a global using directive for `Model`, since we expect to use that namespace throughout our project. 124 | 125 | 1. Immediately before the last global using, and one one more: `global using Model;`. 126 | 127 | ```csharp 128 | global using System; 129 | global using System.Collections.Generic; 130 | global using System.IO; 131 | global using System.Linq; 132 | global using System.Text; 133 | global using System.Threading.Tasks; 134 | global using Model; 135 | global using static System.Console; 136 | ``` 137 | 138 | 139 | And now we can clean up the using directives in our other files, since they're declared globally here. 140 | 141 | 1. Use the "Remove Unnecessary Usings" code fix at the top of both `Person.cs` and `Program.cs`. 142 | 143 | ## Implicit Usings 144 | 145 | For .NET 6, we've built a tooling feature that allows global using directives to be generated from information in the project file. We call this feature "implicit usings". This feature is enabled by default for new projects created with .NET 6 SDK, and it's easy to enable it for existing projects. 146 | 147 | 1. Open the project file (`CSharpTen.csproj`) and add `enable`. 148 | 149 | ```xml 150 | 151 | 152 | 153 | Exe 154 | net6.0 155 | enable 156 | 157 | 158 | 159 | ``` 160 | 161 | 162 | We can easily add our `Model` namespace here as well. 163 | 164 | 1. Add a new `` containing ``. 165 | 166 | ```xml 167 | 168 | 169 | 170 | Exe 171 | net6.0 172 | enable 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | ``` 181 | 182 | 2. Open `Using.cs` and delete all of the using directives except for `global using static System.Console;`. 183 | 184 | # Lambda and Method Group Improvements 185 | 186 | Let's take a look at a few other features of C# 10, such as improvements to lambda expressions and method groups. 187 | 188 | 1. Open `OtherStuff.cs`. 189 | 190 | ## Natural Types for Lambdas 191 | 192 | At long last, we've defined natural types for lambda expressions, which are the `Func` and `Action` delegates that were first introduced in C# 3.0. 193 | 194 | Now, we can can correctly infer a delegate type for a lambda expression if there is enough type information. 195 | 196 | 1. On line 9, change `Func` to `var`. 197 | 198 | ```csharp 199 | var parse = (string s) => int.Parse(s); 200 | ``` 201 | 202 | 203 | Of course, if there isn't enough information to infer a delegate type, the C# compiler will produce an error. 204 | 205 | 1. Change `(string s) =>` to `s`. 206 | 207 | ```csharp 208 | var parse = s => int.Parse(s); 209 | ``` 210 | 211 | 212 | It's also legal to assign a lambda to a type that is convertible from the inferred delegate type, such as `object` or `Delegate`. 213 | 214 | 1. Change `var` to `object`. 215 | 216 | ```csharp 217 | object parse = (string s) => int.Parse(s); 218 | ``` 219 | 220 | 2. Change `object` to `Delegate`. 221 | 222 | ```csharp 223 | Delegate parse = (string s) => int.Parse(s); 224 | ``` 225 | 226 | 227 | Similarly, it is possible to assign a lambda to a legal `Expression` type. 228 | 229 | 1. Change `Delegate` to `Expression` and press `Ctrl+.` to add a using directive for `System.Linq.Expressions`. 230 | 231 | ```csharp 232 | Expression parse = (string s) => int.Parse(s); 233 | ``` 234 | 235 | 2. Change `Expression` to `LambdaExpression`. 236 | 237 | ```csharp 238 | LambdaExpression parse = (string s) => int.Parse(s); 239 | ``` 240 | 241 | 242 | ## Return Types for Lambdas 243 | 244 | In this case, not enough information is provided by the lambda expression to infer a return type. So, the compiler produces an error. 245 | 246 | 1. On line 11, change `Func` to `var`. 247 | 248 | ```csharp 249 | var choose = (bool b) => b ? 1 : "two"; 250 | ``` 251 | 252 | 253 | In C# 10, it's possible to add return types for lambda expressions, which allows `Func` to be inferred. 254 | 255 | 1. Add `object` after `=` to give the lambda expression a return type. 256 | 257 | ```csharp 258 | var choose = object (bool b) => b ? 1 : "two"; 259 | ``` 260 | 261 | 262 | ## Natural Types for Method Groups 263 | 264 | Finally, C# 10 will infer delegate types for method groups if possible. 265 | 266 | 1. On line 20, change `Func` to `var`. 267 | 268 | ```csharp 269 | var read = Console.Read; 270 | ``` 271 | 272 | 273 | In this case, there are multiple overloads, so a delegate type can't be inferred. 274 | 275 | 1. On line 21, change `Action` to `var`. 276 | 277 | ```csharp 278 | var write = Console.Write; 279 | ``` 280 | 281 | Notice that this will cause an error. 282 | 283 | 284 | # Interpolated String Handlers 285 | 286 | 1. Open `OtherStuff.cs`. 287 | 288 | C# 6 introduced an incredibly powerful and useful feature: interpolated strings. At a high level, interpolated strings are highly readable `string.Format(...)` calls. Unfortunately, they bring a lot of costs in the form of hidden allocations. In addition, interpolated strings are built eagerly, 289 | 290 | In C# 10, we've introduced a new library pattern that allows APIs to be written that work directly with interpolated strings and can make the code that we're already writing far more efficient. Several of APIs that take advantage of this pattern have been added in .NET 6. 291 | 292 | 1. Show the interpolated string usage on line 32. 293 | 294 | ```csharp 295 | public string BuildString(object[] args) 296 | { 297 | var sb = new StringBuilder(); 298 | sb.Append($"Hello {args[0]}, how are you?"); 299 | 300 | return sb.ToString(); 301 | } 302 | ``` 303 | 304 | In C# 6, this interpolated string would result a `string.Format(...)` call which would use a different `StringBuilder` to produce a string. Then the string would be added to `sb`. In C# 10, the interpolated string is handled specially and is added directly to `sb` without requiring another `StringBuilder`. So, the code that we were already writing is just faster. 305 | 306 | Hovering over the `StringBuilder.Append` call on line 32 reveals that the overload of `Append` that is being called takes a `StringBuilder.AppendInterpolatedStringHandler` rather than a `string`. This allows the API to perform custom processing of the interpolated string arguments. 307 | 308 | 1. Show the interpolated string usage on line 39. 309 | 310 | ```csharp 311 | public void DebugAssert(bool condition) 312 | { 313 | Debug.Assert(condition, $"{DateTime.Now} - {ExpensiveCalculation()}"); 314 | } 315 | ``` 316 | 317 | In C# 6, the interpolated string passed to `Debug.Assert` is *always* created, even if `condition` is `true`. In C# 10, the arguments to the interpolated string won't be evaluated unless `condition` is `false`. -------------------------------------------------------------------------------- /DemoScript-CSharp9.md: -------------------------------------------------------------------------------- 1 | # C# 9 Demo Script 2 | 3 | Starter code (in the attached project): 4 | 5 | ```csharp 6 | using static System.Console; 7 | 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | var person = new Person 13 | { 14 | FirstName = "Scott", 15 | LastName = "Hunter" 16 | }; 17 | 18 | DisplayPerson(person); 19 | 20 | static void DisplayPerson(Person person) 21 | { 22 | WriteLine($"{person.FirstName} {person.LastName}"); 23 | } 24 | } 25 | } 26 | 27 | class Person 28 | { 29 | public string FirstName { get; set; } 30 | public string LastName { get; set; } 31 | } 32 | 33 | class Student : Person 34 | { 35 | public double Gpa { get; set; } = 4.0; 36 | } 37 | ``` 38 | 39 | ## Introduce top level statements 40 | 41 | Show the initial program. It's a nice starting point. But, for a simple program, it dosn't fit on one page. That's because C# has some ceremony. You can't do anything until you learn `class Program` and `static void Main()`. That's just cruft. Let's remove it, and fix the extra indentation: 42 | 43 | ```csharp 44 | using static System.Console; 45 | 46 | var person = new Person 47 | { 48 | FirstName = "Scott", 49 | LastName = "Hunter" 50 | }; 51 | 52 | DisplayPerson(person); 53 | 54 | static void DisplayPerson(Person person) 55 | { 56 | WriteLine($"{person.FirstName} {person.LastName}"); 57 | } 58 | 59 | class Person 60 | { 61 | public string FirstName { get; set; } 62 | public string LastName { get; set; } 63 | } 64 | 65 | class Student : Person 66 | { 67 | public double Gpa { get; set; } = 4.0; 68 | } 69 | ``` 70 | 71 | Currently, the Visual Studio templates default to targeting .NET Core 3.1. That means C# 8. You get compiler errors. Tell folks that will get updated for the next Visual Studio release. In the meantime, all the .NET 5.0 SDK is there, so you can target .NET 5. Changing the target (either in Project properties, or editing the CSPROJ file) to "net5" fixes the compiler errors. 72 | 73 | Introduce some code to show some of the restrictions: 74 | 75 | 1. Add a `WriteLine` after the declaration of `Student`. That causes a compiler error. All top level statements *must* appear before any type declarations (like Person) or namespace declarations. 76 | 1. Point out that `DisplayPerson` is still a *static local function*. It (and any functions) may appear along with your top level statements. You can still declare local functions among your top level statements. 77 | 78 | Finishing this section, discuss some real world use cases. Azure Functions are one example where these will likely be used in production. In fact, many console applications may benefit from this. 79 | 80 | ## Init only properties 81 | 82 | Change the name of the person. Discuss that your design wants to be *immutable*. Make the `LastName` property readonly, point out the compiler error. Complain that you like the object initializer syntax, but want immutability. Change the `set` accessors to `init` accessors: 83 | 84 | ```csharp 85 | class Person 86 | { 87 | public string FirstName { get; init; } 88 | public string LastName { get; init; } 89 | } 90 | ``` 91 | 92 | Now, you can still use the initializer syntax, but you can't change these properties after you've created the object. This is already better. Let's continue. 93 | 94 | ## Records and nondestructive mutation 95 | 96 | In almost all programs, you probably want to change state on these objects at some point. One practice to do that is called *nondestructive mutation*. A *nondestructive mutation* means making a copy and making one or more modifications to that copy as part of its initialization. Instead of an exact copy, it's a similar copy. You can use "with" methods to make this similar, but not exact copy. This "with" methods are often referred to as "withers". 97 | 98 | You can write with methods youself, but that is painful. Instead, leverage *records* a new type that creates a number of members for you. There are a few steps to make these changes. 99 | 100 | 1. Change `Person` to a record: 101 | ```csharp 102 | record Person 103 | { 104 | public string FirstName { get; init; } 105 | public string LastName { get; init; } 106 | } 107 | ``` 108 | Whoops! That's a compiler error. Records can only inherit from other records. Once you step into this world, you must embrace it throughout a hierarchy. 109 | 1. Change `Student` to a record: 110 | ```csharp 111 | record Student : Person 112 | { 113 | public double Gpa { get; set; } = 4.0; 114 | } 115 | ``` 116 | Now, everything compiles. Both `Person` and `Student` are records. 117 | 1. Next, let's use a `with` method to create a changed copy of a person: 118 | ```csharp 119 | var person = new Person 120 | { 121 | FirstName = "Scott", 122 | LastName = "Hunter" 123 | }; 124 | 125 | var otherPerson = person with 126 | { 127 | LastName = "Hanselman" 128 | }; 129 | 130 | DisplayPerson(person); 131 | DisplayPerson(otherPerson); 132 | ``` 133 | 134 | The `otherPerson` was created by copying and modifying the original `person`. 135 | 136 | ## Records have other *synthesized mebmbers* 137 | 138 | Remove the `DisplayPerson()` method. Change the `DisplayPerson` calls to call `WriteLine`. Records create their own `ToString()` output. This method prints a more reasonable output for any record type. That means less boilerplate code for you. Let's add some more cases. 139 | 140 | Records also create methods to test for equality based on the *values* of a record's properties. Let's demonstrate this. Create a copy of the original by changing the last name again: 141 | 142 | ```csharp 143 | var originalPerson = otherPerson with 144 | { 145 | LastName = "Hunter" 146 | }; 147 | 148 | WriteLine($"Equals: {Equals(person, originalPerson)}"); 149 | WriteLine($"Reference Equals: {ReferenceEquals(person, originalPerson)}"); 150 | ``` 151 | 152 | ## Record hierarchies 153 | 154 | You've got a hierarchy of records: `Student` derives from `Person`. Let's explore how the compiler handles those hierarchies. First, change "Person" to "Student" in the first declaration. Also, change the static type of the declaration to `Person` (instead of `var`): 155 | 156 | ```csharp 157 | Person person = new Student 158 | { 159 | FirstName = "Scott", 160 | LastName = "Hunter" 161 | }; 162 | ``` 163 | 164 | Run the application. Notice that all the records are now `Student`, not `Person` objects. 165 | Add a GPA, and see that the `Gpa` property is copied on each record you create from the first record: 166 | 167 | ```csharp 168 | Person person = new Student 169 | { 170 | FirstName = "Scott", 171 | LastName = "Hunter", 172 | Gpa = 3.8, 173 | }; 174 | ``` 175 | 176 | Gpa flows through. Let's make one more change to test equality between these record types. Add the following two lines after the current equality tests: 177 | 178 | ```csharp 179 | var p = new Person("Scott", "Hunter"); 180 | WriteLine($"Person and Student: Equals: {Equals(person, p)}"); 181 | ``` 182 | 183 | Note that `p`, which is a `Person` is not equal to `person`, which is a student. The equality tests do compare the types of records 184 | 185 | ## Positional records 186 | 187 | Records are classes. But, we make a few assumptions about how records will be used. We assume that records are primarily defined by their public properties, not by their behavior. In addition, because of that, we assume you'll likely want records to be immutable. These aren't enforced, but when those assuptions are true you can use a more concise syntax called *Positional records*. Update the Person record as follows: 188 | 189 | ```csharp 190 | record Person(string FirstName, string LastName); 191 | ``` 192 | 193 | Positional records create a constructor called a *primary constructor*. That's a constructor that takes parameter matching each of the public properties declared in the record declaration. That means derived records must call that primary constructor. Update the `Student` to match: 194 | 195 | ```csharp 196 | record Student(string FirstName, string LastName) : Person(FirstName, LastName) 197 | { 198 | public double Gpa { get; set; } = 4.0; 199 | } 200 | ``` 201 | 202 | Now, both the `Student` and `Person` records are positional records. That means you'll need to craeate 203 | To compile this, you'll need to change the construction of `person`: 204 | 205 | ```csharp 206 | Person person = new Student("Scott", "Hunter") 207 | { 208 | Gpa = 3.8, 209 | }; 210 | ``` 211 | 212 | Point out that you can mix construction and object initializers for positional records. Note that GPA doesn't have to be immutable. 213 | 214 | Positional records also add deconstruction methods, because the compiler *assumes* the order you supplied the properties for the record. Add the following to test it: 215 | 216 | ```csharp 217 | var (first, last) = person; 218 | WriteLine($"{first}, {last}"); 219 | ``` 220 | 221 | ## On to patterns 222 | 223 | Let's add a local function that prints a person's honors status. You can use some of the new pattern matching features for *relational patterns* to return the correct descriptions: 224 | 225 | ```csharp 226 | WriteLine($"Person status: {PrintStudentHonorarium(p)}"); 227 | WriteLine($"Student status: {PrintStudentHonorarium(otherPerson)}"); 228 | 229 | static string PrintStudentHonorarium(Person p) 230 | { 231 | if (p is Student s) 232 | { 233 | return s.Gpa switch 234 | { 235 | 4.0 => "Distinguished honors", 236 | >= 3.5 => "High honors", 237 | >= 3.0 => "honors", 238 | > 1.0 => "Satisfactory", 239 | _ => "pass" 240 | }; 241 | } 242 | else 243 | { 244 | return "graduate"; 245 | } 246 | } 247 | ``` 248 | 249 | This shows some of our newer patterns, and some of the earlier patterns as well. This `is` pattern match checks if the person is a student. That is from C# 7. We continue to invest in patterns, and the new syntax in the switch is from C# 9. These *relational patterns* provide a richer syntax for testing numeric values. In this case, the switch variable is a number, so the switch arms start with the relation operator. The variable isn't needed. 250 | 251 | This can use some refactoring. There isn't a good `null` check. There's also a type check followed by a pattern testing the value of the `Gpa` property. These comparisons can be combined into a nested switch statement. 252 | 253 | Let's try this next: 254 | 255 | ```csharp 256 | static string PrintStudentHonorarium(Person p) 257 | { 258 | return p switch 259 | { 260 | null => throw new ArgumentNullException(nameof(p), "Person can't be null"), 261 | Person _ => "graduate", 262 | Student s => s.Gpa switch 263 | { 264 | 4.0 => "Distinguished honors", 265 | >= 3.5 => "High honors", 266 | >= 3.0 => "honors", 267 | > 1.0 => "Satisfactory", 268 | _ => "pass" 269 | }, 270 | }; 271 | } 272 | ``` 273 | 274 | You'll have errors on the `Student` line. That's because that case is already handled by the `Person` case arm. You'll need to move the student above the `Person` arm. 275 | The important point about this is that the compiler will warn you if you have switch arms in an order that prevents any from being reachable. You must arrange them so that each can be reached. 276 | 277 | You could introduce an *and* pattern in the Student switch expression to process all students without honors first: 278 | 279 | ```csharp 280 | static string PrintStudentHonorarium(Person p) 281 | { 282 | return p switch 283 | { 284 | null => throw new ArgumentNullException(nameof(p), "Person can't be null"), 285 | Student s => s.Gpa switch 286 | { 287 | < 3.0 and > 1.0 => "Satisfactory", 288 | 4.0 => "Distinguished honors", 289 | >= 3.5 => "High honors", 290 | >= 3.0 => "Honors", 291 | _ => "pass" 292 | }, 293 | Person _ => "graduate", 294 | }; 295 | } 296 | ``` 297 | 298 | For other uses, you can aslo use `or` and `not` patterns. The `not null` patterns is a handy way to perform a null check on any variable. 299 | 300 | ## Summary 301 | 302 | Go to the next slide. 303 | 304 | Summarize the list of features. You saw all the features in the left column: 305 | 306 | - Top level statements 307 | - Init only setters 308 | - Records 309 | - Positional records 310 | - Pattern matching enhancements 311 | 312 | We didn't have time to cover all these other features: 313 | 314 | - static anonymous functions: Like local functions, you can prevent anonymous functions (lambdas) from capturing variables 315 | - Native sized integers: In some scenarios, you want an integral value to match the machine's natural CPU size. Use `nint` and `nuint`. 316 | - Function pointers: Function pointers provide an easy syntax to access the IL opcodes `ldftn` and `calli`. You can declare function pointers using new `delegate*` syntax. These are useful for interop scenarios. 317 | - Supress `localsinit`. This disables the standard .NET behavior to initialize memory used for local variables to all 0s. Disabling it in hot paths can improve performance. 318 | - Partial method features. Partial methods no longer must not have any access modifiers and must return `void`. This supports code generators. However, to avoid any breaking change, any partial method that doesn't follow the existing rules must have an implementation. 319 | 320 | Final slide. Download and use it. 321 | 322 | 323 | 324 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 .NET Presentations: Events in a Box! 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C# 9 & 10 Workshop 2 | 3 | [![.NET](https://github.com/dotnet-presentations/csharp-workshop/actions/workflows/dotnet.yml/badge.svg)](https://github.com/dotnet-presentations/csharp-workshop/actions/workflows/dotnet.yml) 4 | 5 | Welcome to the C# workshop focused on learning new features in C# 9 & 10. This is roughly a 2 hour workshop and includes [slides](WhatsNewInCSharp.pptx), a full [demo script for C# 9](DemoScript-CSharp9.md) and [demo script for C# 10](DemoScript-CSharp10.md), and a starting & finish project to follow. 6 | 7 | 8 | -------------------------------------------------------------------------------- /WhatsNewInCSharp.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnet-presentations/csharp-workshop/1996a93ef12ea01018929b61be4cd83e4b326c5e/WhatsNewInCSharp.pptx --------------------------------------------------------------------------------