├── .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 | [](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
--------------------------------------------------------------------------------