├── LICENSE
├── README.md
├── csharp-mindmap.png
└── patreon.png
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # C# Programming Cheat Sheet (2025 Edition)
2 |
3 | ## Introduction
4 |
5 | This comprehensive C# cheat sheet serves as a quick reference guide for C# developers at all skill levels. It covers the core language features, modern patterns, and best practices as of 2025.
6 |
7 | The cheat sheet organizes **fundamental concepts into more advanced topics**,** making it useful for learning and reference.
8 |
9 | C# has evolved significantly since its inception, with regular updates introducing powerful new features while maintaining backward compatibility.
10 |
11 | This guide incorporates the latest language enhancements **through C# 13 and beyond**, as well as code organization, and development approaches that have become standard in the .NET ecosystem.
12 |
13 | 🔖 Feel free to **bookmark this page** and refer to it whenever you need to refresh your knowledge of C# language features or modern development techniques.
14 |
15 | The image below show an **overview of the C# language features and concepts** covered in this cheat sheet:
16 |
17 | 
18 |
19 | ## Support my work
20 |
21 | If you find this repository helpful, consider supporting me on Patreon:
22 |
23 | [](https://www.patreon.com/techworld_with_milan)
24 |
25 | ## Give a Star! :star:
26 |
27 | If you like or are using this project to learn or start your solution, please give it a star. Thanks!
28 |
29 | ## Table of contents
30 |
31 | - [Comments](#comments)
32 | - [Strings](#strings)
33 | - [Basic types and literals](#basic-types-and-literals)
34 | - [Statements](#statements)
35 | - [Control flow](#control-flow)
36 | - [Loops](#loops)
37 | - [Lock statement](#lock-statement)
38 | - [Using statement](#using-statement)
39 | - [Unsafe code](#unsafe-code)
40 | - [Yield statement](#yield-statement)
41 | - [Methods and functions](#methods-and-functions)
42 | - [Basic method syntax](#basic-method-syntax)
43 | - [Expression-bodied members](#expression-bodied-members-c-60)
44 | - [Method parameters](#method-parameters)
45 | - [Local functions](#local-functions-c-70)
46 | - [Extension methods](#extension-methods)
47 | - [Lambda expressions](#lambda-expressions)
48 | - [Method overloading](#method-overloading)
49 | - [Delegates and events](#delegates-and-events)
50 | - [Data types](#data-types)
51 | - [Classes](#classes)
52 | - [Structs](#structs)
53 | - [Records](#records-c-90)
54 | - [Record structs](#record-structs-c-100)
55 | - [Interfaces](#interfaces)
56 | - [Enums](#enums)
57 | - [Tuples](#tuples)
58 | - [Nullable types](#nullable-types)
59 | - [Generics](#generics)
60 | - [Generic classes](#generic-classes)
61 | - [Generic methods](#generic-methods)
62 | - [Constraints](#constraints)
63 | - [Classes](#classes-and-inheritance)
64 | - [Constructors and initialization](#constructors-and-initialization)
65 | - [Primary constructors](#primary-constructors-c-12)
66 | - [Inheritance](#inheritance)
67 | - [Abstract classes](#abstract-classes)
68 | - [Sealed classes and members](#sealed-classes-and-members)
69 | - [Polymorphism](#polymorphism)
70 | - [Collections](#collections)
71 | - [Collection expressions](#collection-expressions-c-12)
72 | - [Arrays](#arrays)
73 | - [Lists](#lists)
74 | - [Dictionary](#dictionary)
75 | - [HashSet](#hashset)
76 | - [Queue and Stack](#queue-and-stack)
77 | - [LINQ](#linq-language-integrated-query)
78 | - [Pattern matching](#pattern-matching)
79 | - [Type patterns](#type-patterns)
80 | - [Property patterns](#property-patterns)
81 | - [Tuple patterns](#tuple-patterns)
82 | - [Logical patterns](#logical-patterns)
83 | - [List patterns](#list-patterns-c-110)
84 | - [Discard patterns](#discard-pattern)
85 | - [Exceptions](#exceptions)
86 | - [Try-Catch-Finally](#try-catch-finally)
87 | - [Throwing exceptions](#throwing-exceptions)
88 | - [Custom exceptions](#custom-exceptions)
89 | - [Asynchronous programming](#asynchronous-programming)
90 | - [Async and await basics](#async-and-await-basics)
91 | - [Task-based asynchronous pattern](#task-based-asynchronous-pattern)
92 | - [Exception handling in async code](#exception-handling-in-async-code)
93 | - [Cancellation in async operations](#cancellation-in-async-operations)
94 | - [ValueTask and async streams](#valuetask-and-async-streams-c-80)
95 | - [Code organization](#code-organization)
96 | - [Namespaces](#namespaces)
97 | - [Using directives](#using-directives)
98 | - [File-scoped types](#file-scoped-types-c-11)
99 | - [Partial classes](#partial-classes)
100 | - [Access modifiers](#access-modifiers)
101 | - [Properties and indexers](#properties-and-indexers)
102 |
103 |
104 |
105 | # Comments
106 |
107 | Comments in C# provide ways to document your code, explain complex logic, and temporarily disable code during development. C# supports three types of comments: single-line, multi-line, and XML documentation comments. Good commenting practices are essential for code maintainability, especially in team environments.
108 |
109 | ```csharp
110 | // This is a single-line comment
111 |
112 | /* This is a
113 | multi-line comment */
114 |
115 | ///
116 | /// XML documentation comment used to generate documentation
117 | ///
118 | public void DocumentedMethod() { }
119 | ```
120 |
121 | XML documentation comments can include various tags to document parameters, return values, exceptions, etc.
122 |
123 | ```csharp
124 | ///
125 | /// Adds two integers and returns the result
126 | ///
127 | /// First integer
128 | /// Second integer
129 | /// The sum of the two integers
130 | /// Thrown when the sum is too large
131 | public int Add(int a, int b) => a + b;
132 | ```
133 |
134 | **Additional resources:**
135 |
136 | - [Microsoft Docs: XML Documentation Comments](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/)
137 | - [C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
138 |
139 |
140 |
141 | # Strings
142 |
143 | Strings in C# are immutable sequences of Unicode characters represented by the `string` type (an alias for `System.String`). C# offers a rich set of string manipulation features, from basic concatenation to advanced interpolation and raw string literals. The language has evolved significantly to make string handling more intuitive and efficient.
144 |
145 | ```csharp
146 | // Basic string creation
147 | string greeting = "Hello";
148 | string name = "World";
149 | string message = greeting + " " + name; // "Hello World"
150 |
151 | // String interpolation (C# 6.0+)
152 | string interpolated = $"{greeting} {name}!"; // "Hello World!"
153 |
154 | // Verbatim strings (preserves formatting and ignores escape sequences except "")
155 | string path = @"C:\Users\UserName\Documents";
156 | string multiline = @"This is a
157 | multi-line string
158 | that preserves formatting";
159 |
160 | // Verbatim string interpolation
161 | string verbatimInterpolated = $@"User {name}
162 | Path: {path}";
163 |
164 | // Raw string literals (C# 11+) - no escape sequences, preserves formatting
165 | string json = """
166 | {
167 | "name": "John Doe",
168 | "age": 30,
169 | "isActive": true
170 | }
171 | """;
172 |
173 | // Raw string interpolation (C# 11+)
174 | string name = "Jane";
175 | string rawInterpolated = $"""
176 | {
177 | "name": "{{name}}",
178 | "created": "{{DateTime.Now}}"
179 | }
180 | """;
181 |
182 | // Common string methods
183 | string text = "Hello, World!";
184 | bool contains = text.Contains("World"); // true
185 | string upper = text.ToUpper(); // "HELLO, WORLD!"
186 | string lower = text.ToLower(); // "hello, world!"
187 | string replaced = text.Replace("Hello", "Hi"); // "Hi, World!"
188 | string trimmed = " text ".Trim(); // "text"
189 | string[] split = text.Split(','); // ["Hello", " World!"]
190 | int length = text.Length; // 13
191 |
192 | // String comparison
193 | bool equals = string.Equals("abc", "ABC", StringComparison.OrdinalIgnoreCase); // true
194 | int comparison = string.Compare("abc", "ABC", StringComparison.Ordinal); // not equal
195 | ```
196 |
197 | For better performance with repeated string concatenation (as String is immutable type), use `StringBuilder`:
198 |
199 | ```csharp
200 | using System.Text;
201 |
202 | StringBuilder sb = new StringBuilder();
203 | for (int i = 0; i < 100; i++)
204 | {
205 | sb.Append($"Item {i}, ");
206 | }
207 | string result = sb.ToString();
208 | ```
209 |
210 | **Additional resources:**
211 |
212 | - [String class (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/api/system.string)
213 | - [String interpolation (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated)
214 | - [Raw String literals (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/raw-string-literal)
215 | - [String performance best practices](https://learn.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings)
216 |
217 |
218 |
219 | # Basic types and literals
220 |
221 | C# is a strongly-typed language with a comprehensive type system that forms the foundation of all C# programs. Understanding these basic types is essential for writing efficient and type-safe code.
222 |
223 | C# types are categorized as **value types (stored on the stack)** and **reference types (stored on the heap)**, each with different memory and performance characteristics.
224 |
225 | ```csharp
226 | // Integer types
227 | byte byteValue = 255; // 8-bit unsigned integer (0 to 255)
228 | sbyte sbyteValue = -128; // 8-bit signed integer (-128 to 127)
229 | short shortValue = -32768; // 16-bit signed integer (-32,768 to 32,767)
230 | ushort ushortValue = 65535; // 16-bit unsigned integer (0 to 65,535)
231 | int intValue = -2147483648; // 32-bit signed integer (-2,147,483,648 to 2,147,483,647)
232 | uint uintValue = 4294967295; // 32-bit unsigned integer (0 to 4,294,967,295)
233 | long longValue = -9223372036854775808; // 64-bit signed integer (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807)
234 | ulong ulongValue = 18446744073709551615; // 64-bit unsigned integer (0 to 18,446,744,073,709,551,615)
235 |
236 | // Integer literals
237 | int decimalLiteral = 42; // Decimal (base 10)
238 | int hexLiteral = 0x2A; // Hexadecimal (base 16)
239 | int binaryLiteral = 0b101010; // Binary (base 2)
240 | int withSeparator = 1_000_000; // Digit separator for readability
241 |
242 | // Floating point types
243 | float floatValue = 3.14f; // 32-bit floating-point (7 significant digits precision)
244 | double doubleValue = 3.14159265359; // 64-bit floating-point (15-16 significant digits precision)
245 | decimal decimalValue = 3.14159265359m; // 128-bit high-precision decimal (28-29 significant digits)
246 |
247 | // Boolean type
248 | bool trueValue = true;
249 | bool falseValue = false;
250 |
251 | // Character type
252 | char charValue = 'A'; // Unicode character
253 | char unicodeChar = '\u0041'; // Unicode escape sequence for 'A'
254 | char escapeChar = '\n'; // Newline escape sequence
255 |
256 | // DateTime and TimeSpan
257 | DateTime now = DateTime.Now;
258 | DateTime utcNow = DateTime.UtcNow;
259 | DateOnly today = DateOnly.FromDateTime(DateTime.Today); // Date without time (C# 10+)
260 | TimeOnly noon = new TimeOnly(12, 0, 0); // Time without date (C# 10+)
261 | DateTime specific = new DateTime(2023, 1, 1);
262 | TimeSpan oneHour = TimeSpan.FromHours(1);
263 | TimeSpan duration = TimeSpan.FromMinutes(90);
264 |
265 | // Nullable types (can be null)
266 | int? nullableInt = null;
267 | bool? nullableBool = null;
268 |
269 | // Default values
270 | int defaultInt = default; // 0
271 | bool defaultBool = default; // false
272 | string defaultString = default; // null
273 | DateTime defaultDateTime = default; // 0001-01-01 00:00:00
274 |
275 | // Numeric type aliases (C# 12+)
276 | using intptr = nint; // Native-sized integer
277 | using uintptr = nuint; // Unsigned native-sized integer
278 | using index = System.Index; // Type alias for Index
279 | ```
280 |
281 | Type inference with `var` (compile-time determined):
282 |
283 | ```csharp
284 | var inferredInt = 42; // Compiler infers int
285 | var inferredString = "Hello"; // Compiler infers string
286 | var inferredList = new List(); // Compiler infers List
287 | ```
288 |
289 | Constants and readonly:
290 |
291 | ```csharp
292 | // Compile-time constants (must be primitive types or string)
293 | const double Pi = 3.14159;
294 | const string AppName = "MyApp";
295 |
296 | // Runtime constants
297 | readonly DateTime StartTime = DateTime.Now;
298 |
299 | // Static readonly - initialized only once at runtime
300 | public static readonly HttpClient SharedClient = new HttpClient();
301 |
302 | // Init-only setter - can only be set during initialization
303 | public string Id { get; init; } = Guid.NewGuid().ToString();
304 |
305 | // Read-only fields/properties with field/property initializers
306 | public required string Name { get; init; }
307 | ```
308 |
309 | **Additional resources:**
310 |
311 | - [Built-in types (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/built-in-types)
312 | - [Value types (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types)
313 | - [Reference types (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/reference-types)
314 | - [Constants (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/const)
315 | - [DateOnly and timeOnly types (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-6#dateonly-and-timeonly)
316 |
317 |
318 |
319 | # Statements
320 |
321 | Statements are the building blocks of C# code execution, controlling the flow of your program and dictating how operations are carried out. Understanding these core language constructs is essential for effective C# programming.
322 |
323 | ## Control flow
324 |
325 | Control flow statements allow you to make decisions and execute different code paths based on conditions.
326 |
327 | ### If-else statements
328 |
329 | If-else statements execute different code blocks based on boolean conditions. They form the foundation of decision-making in C#.
330 |
331 | ```csharp
332 | // Basic if statement
333 | if (condition)
334 | {
335 | // Code executed if condition is true
336 | }
337 |
338 | // If-else
339 | if (temperature > 30)
340 | {
341 | Console.WriteLine("It's hot outside");
342 | }
343 | else
344 | {
345 | Console.WriteLine("It's not too hot");
346 | }
347 |
348 | // If-else if-else chain
349 | if (temperature > 30)
350 | {
351 | Console.WriteLine("It's hot outside");
352 | }
353 | else if (temperature > 20)
354 | {
355 | Console.WriteLine("It's warm outside");
356 | }
357 | else if (temperature > 10)
358 | {
359 | Console.WriteLine("It's cool outside");
360 | }
361 | else
362 | {
363 | Console.WriteLine("It's cold outside");
364 | }
365 |
366 | // Conditional (ternary) operator - shorthand for simple if-else
367 | string message = age >= 18 ? "Adult" : "Minor";
368 |
369 | // Null-coalescing operator (??) - returns the left operand if it's not null, otherwise the right
370 | string name = userName ?? "Anonymous";
371 |
372 | // Null-conditional operator (?.) - safely accesses members of potentially null objects
373 | int? length = customer?.Name?.Length;
374 |
375 | // Null-coalescing assignment (??=) - C# 8.0+
376 | // Assigns the right operand only if the left operand is null
377 | userName ??= "Anonymous";
378 | ```
379 |
380 | ### Switch statements and expressions
381 |
382 | Switch statements provide a way to handle multiple possible conditions for a single value. Modern C# also offers powerful switch expressions.
383 |
384 | ```csharp
385 | // Traditional switch statement
386 | switch (dayOfWeek)
387 | {
388 | case DayOfWeek.Monday:
389 | Console.WriteLine("Start of work week");
390 | break;
391 | case DayOfWeek.Friday:
392 | Console.WriteLine("End of work week");
393 | break;
394 | case DayOfWeek.Saturday:
395 | case DayOfWeek.Sunday:
396 | Console.WriteLine("Weekend");
397 | break;
398 | default:
399 | Console.WriteLine("Midweek");
400 | break;
401 | }
402 |
403 | // Switch expression (C# 8.0+)
404 | string GetDayType(DayOfWeek day) => day switch
405 | {
406 | DayOfWeek.Monday => "Start of work week",
407 | DayOfWeek.Friday => "End of work week",
408 | DayOfWeek.Saturday or DayOfWeek.Sunday => "Weekend",
409 | _ => "Midweek" // Default case
410 | };
411 |
412 | // Switch expression with pattern matching
413 | string GetShapeDescription(object shape) => shape switch
414 | {
415 | Circle c when c.Radius > 10 => "Large circle",
416 | Circle _ => "Circle",
417 | Rectangle { Width: 0 } => "Line",
418 | Rectangle { Length: var l, Width: var w } when l == w => "Square",
419 | Rectangle _ => "Rectangle",
420 | null => "No shape",
421 | _ => "Unknown shape"
422 | };
423 | ```
424 |
425 | ## Loops
426 |
427 | Loops allow you to execute a block of code repeatedly until a condition is met.
428 |
429 | ### For loops
430 |
431 | For loops are ideal when you know the number of iterations in advance.
432 |
433 | ```csharp
434 | // Basic for loop
435 | for (int i = 0; i < 10; i++)
436 | {
437 | Console.WriteLine($"Iteration {i}");
438 | }
439 |
440 | // Multiple loop variables
441 | for (int i = 0, j = 10; i < j; i++, j--)
442 | {
443 | Console.WriteLine($"i = {i}, j = {j}");
444 | }
445 |
446 | // Nested for loops
447 | for (int i = 0; i < 3; i++)
448 | {
449 | for (int j = 0; j < 3; j++)
450 | {
451 | Console.WriteLine($"Position [{i},{j}]");
452 | }
453 | }
454 |
455 | // Breaking out of a loop
456 | for (int i = 0; i < 100; i++)
457 | {
458 | if (i > 10)
459 | break; // Exits the loop
460 |
461 | Console.WriteLine(i);
462 | }
463 |
464 | // Skipping an iteration
465 | for (int i = 0; i < 10; i++)
466 | {
467 | if (i % 2 == 0)
468 | continue; // Skips to the next iteration
469 |
470 | Console.WriteLine($"Odd number: {i}");
471 | }
472 | ```
473 |
474 | ### Foreach loops
475 |
476 | Foreach loops are designed for iterating through collections and are simpler to use than for loops when the iteration count isn't important.
477 |
478 | ```csharp
479 | // Basic foreach loop
480 | foreach (string name in names)
481 | {
482 | Console.WriteLine(name);
483 | }
484 |
485 | // Using index with foreach (C# 9.0+)
486 | foreach (string name in names.Select((value, index) => new { value, index }))
487 | {
488 | Console.WriteLine($"{name.index}: {name.value}");
489 | }
490 |
491 | // Iterating through key-value pairs
492 | foreach (KeyValuePair pair in dictionary)
493 | {
494 | Console.WriteLine($"{pair.Key}: {pair.Value}");
495 | }
496 |
497 | // Using deconstruction with foreach (C# 7.0+)
498 | foreach (var (key, value) in dictionary)
499 | {
500 | Console.WriteLine($"{key}: {value}");
501 | }
502 |
503 | // Breaking and continuing also work in foreach
504 | foreach (var item in collection)
505 | {
506 | if (ShouldSkip(item))
507 | continue;
508 |
509 | if (ShouldStop(item))
510 | break;
511 |
512 | Process(item);
513 | }
514 | ```
515 |
516 | ### While and do-while loops
517 |
518 | While loops check the condition before executing the loop body, while do-while loops execute the body at least once before checking the condition.
519 |
520 | ```csharp
521 | // While loop - may execute zero times
522 | while (condition)
523 | {
524 | // Loop body
525 | }
526 |
527 | // Example: reading until a condition is met
528 | while (!Console.KeyAvailable)
529 | {
530 | // Process until a key is pressed
531 | ProcessData();
532 | }
533 |
534 | // Do-while loop - always executes at least once
535 | do
536 | {
537 | // Loop body
538 | } while (condition);
539 |
540 | // Example: menu system
541 | string choice;
542 | do
543 | {
544 | DisplayMenu();
545 | choice = Console.ReadLine();
546 | ProcessChoice(choice);
547 | } while (choice != "exit");
548 | ```
549 |
550 | ## Lock statement
551 |
552 | The lock statement prevents multiple threads from accessing a shared resource simultaneously, helping to avoid race conditions in multithreaded code.
553 |
554 | ```csharp
555 | // Define a lock object (private to avoid external locking)
556 | private readonly object _lockObject = new object();
557 |
558 | // Using the lock statement
559 | public void AddItem(string item)
560 | {
561 | lock (_lockObject)
562 | {
563 | // This code can only be executed by one thread at a time
564 | _items.Add(item);
565 | _count++;
566 | }
567 | }
568 |
569 | ```
570 |
571 | **Best practices for locks:**
572 | 1. Use a private object for locking
573 | 2. Keep the locked section as small as possible
574 | 3. Avoid locking on 'this' or public objects
575 | 4. Don't execute long-running or blocking operations inside a lock
576 | 5. Consider using higher-level synchronization primitives for complex scenarios
577 |
578 | ## Using statement
579 |
580 | The `using` statement ensures that disposable resources are properly cleaned up, even if exceptions occur. It's an essential pattern for working with resources like files, network connections, and database connections that need to be explicitly released.
581 |
582 | ```csharp
583 | // Traditional using statement
584 | using (StreamReader reader = new StreamReader("file.txt"))
585 | {
586 | string content = reader.ReadToEnd();
587 | // reader is automatically disposed here, even if an exception occurs
588 | }
589 |
590 | // Using declaration (C# 8.0+)
591 | using StreamWriter writer = new StreamWriter("output.txt");
592 | writer.WriteLine("Hello, World!");
593 | // writer is disposed at the end of the scope
594 |
595 | // Multiple resources in one using statement
596 | using (var connection = new SqlConnection(connectionString))
597 | using (var command = new SqlCommand(queryString, connection))
598 | {
599 | connection.Open();
600 | using (var reader = command.ExecuteReader())
601 | {
602 | // Process data
603 | }
604 | }
605 |
606 | // Using declaration with multiple resources (C# 8.0+)
607 | using var fileStream = new FileStream("data.bin", FileMode.Create);
608 | using var binaryWriter = new BinaryWriter(fileStream);
609 | binaryWriter.Write(42);
610 | // Both binaryWriter and fileStream are disposed at end of scope
611 | ```
612 |
613 | ## Unsafe code
614 |
615 | The unsafe keyword allows you to write code that directly manipulates memory. This is primarily used for performance-critical operations or interop with native code.
616 |
617 | ```csharp
618 | // Must enable unsafe code in project settings or compiler options
619 | // true in .csproj
620 |
621 | // Unsafe method
622 | public unsafe void ProcessBuffer(byte[] buffer)
623 | {
624 | fixed (byte* ptr = buffer)
625 | {
626 | // Direct memory manipulation
627 | for (int i = 0; i < buffer.Length; i++)
628 | {
629 | *(ptr + i) = (byte)(*(ptr + i) * 2);
630 | }
631 | }
632 | }
633 |
634 | // Unsafe context with pointer operations
635 | unsafe
636 | {
637 | int value = 10;
638 | int* pointer = &value;
639 |
640 | Console.WriteLine($"Value: {*pointer}");
641 |
642 | // Increment the value through the pointer
643 | *pointer = 20;
644 | Console.WriteLine($"Updated value: {value}");
645 | }
646 |
647 | // sizeof operator (only allowed in unsafe context)
648 | unsafe
649 | {
650 | Console.WriteLine($"Size of int: {sizeof(int)} bytes");
651 | Console.WriteLine($"Size of double: {sizeof(double)} bytes");
652 | }
653 | ```
654 |
655 | ## Yield statement
656 |
657 | The yield statement is used in iterator methods to provide values one at a time, enabling deferred execution and efficient handling of sequences.
658 |
659 | ```csharp
660 | // Simple iterator method
661 | public IEnumerable GetNumbers(int count)
662 | {
663 | for (int i = 0; i < count; i++)
664 | {
665 | yield return i;
666 | }
667 | }
668 |
669 | // Iterator method with conditional logic
670 | public IEnumerable GetEvenNumbers(int max)
671 | {
672 | for (int i = 0; i <= max; i++)
673 | {
674 | if (i % 2 == 0)
675 | {
676 | yield return i;
677 | }
678 | }
679 | }
680 |
681 | // Yield break to exit early
682 | public IEnumerable GetNumbersUntil(int max, int stopAt)
683 | {
684 | for (int i = 0; i <= max; i++)
685 | {
686 | if (i == stopAt)
687 | {
688 | yield break; // Exit the iterator
689 | }
690 |
691 | yield return i;
692 | }
693 | }
694 |
695 | // Using yield to implement filtering
696 | public IEnumerable Where(IEnumerable source, Func predicate)
697 | {
698 | foreach (var item in source)
699 | {
700 | if (predicate(item))
701 | {
702 | yield return item;
703 | }
704 | }
705 | }
706 | ```
707 |
708 | ### Benefits of yield:
709 |
710 | 1. **Lazy evaluation**: Results are computed only when needed
711 | 2. **Memory efficiency**: No need to build the entire collection at once
712 | 3. **Composability**: Iterator methods can be chained together
713 | 4. **Simplicity**: Easier to write than manually implementing IEnumerator
714 |
715 | **Additional resources:**
716 | - [C# Statements (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/statements)
717 | - [Selection statements - if, if-else, and switch (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/selection-statements)
718 | - [Iteration statements (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/iteration-statements)
719 | - [Lock statement (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/lock)
720 | - [Using statement (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using)
721 | - [Unsafe keyword (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/unsafe)
722 | - [Yield (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/yield)
723 |
724 |
725 |
726 | # Methods and functions
727 |
728 | Methods are the fundamental building blocks of C# programs that encapsulate behavior and logic. They provide a way to organize code into reusable units, improving maintainability and readability.
729 |
730 | C# offers various ways to define methods with different parameter types, return values, and syntax options to accommodate different programming styles and needs.
731 |
732 | ## Basic method syntax
733 |
734 | ```csharp
735 | // Instance method
736 | public int Add(int a, int b)
737 | {
738 | return a + b;
739 | }
740 |
741 | // Static method
742 | public static double CalculateArea(double radius)
743 | {
744 | return Math.PI * radius * radius;
745 | }
746 |
747 | // Void method (no return value)
748 | public void PrintMessage(string message)
749 | {
750 | Console.WriteLine(message);
751 | }
752 | ```
753 |
754 | ## Expression-bodied members (C# 6.0+)
755 |
756 | Expression-bodied members provide a concise syntax for methods, properties, and other members that can be represented by a single expression.
757 |
758 | ```csharp
759 | // Expression-bodied method (one-line methods)
760 | public int Multiply(int a, int b) => a * b;
761 |
762 | // Expression-bodied property
763 | public string FullName => $"{FirstName} {LastName}";
764 | ```
765 |
766 | ## Method parameters
767 |
768 | C# provides flexible parameter passing options to handle different programming scenarios.
769 |
770 | ```csharp
771 | // Optional parameters
772 | public void Greet(string name, string greeting = "Hello")
773 | {
774 | Console.WriteLine($"{greeting}, {name}!");
775 | }
776 |
777 | // Named arguments
778 | Greet(greeting: "Hi", name: "Alice");
779 |
780 | // Ref parameters (pass by reference)
781 | public void Swap(ref int a, ref int b)
782 | {
783 | int temp = a;
784 | a = b;
785 | b = temp;
786 | }
787 | // Usage: Swap(ref x, ref y);
788 |
789 | // Out parameters (for returning multiple values)
790 | public bool TryParse(string input, out int result)
791 | {
792 | return int.TryParse(input, out result);
793 | }
794 | // Usage: bool success = TryParse("123", out int number);
795 |
796 | // In parameters (read-only reference - C# 7.2+)
797 | public double CalculateDistance(in Point p1, in Point p2)
798 | {
799 | // p1 and p2 cannot be modified
800 | return Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
801 | }
802 |
803 | // Params array (variable number of arguments)
804 | public int Sum(params int[] numbers)
805 | {
806 | int total = 0;
807 | foreach (int number in numbers)
808 | {
809 | total += number;
810 | }
811 | return total;
812 | }
813 | // Usage: Sum(1, 2, 3, 4, 5);
814 | ```
815 |
816 | ## Local functions (C# 7.0+)
817 |
818 | Local functions allow you to define methods inside other methods, encapsulating helper logic that is only relevant to the containing method.
819 |
820 | ```csharp
821 | public int Factorial(int n)
822 | {
823 | // Local function defined inside another method
824 | int CalculateFactorial(int number)
825 | {
826 | if (number <= 1) return 1;
827 | return number * CalculateFactorial(number - 1);
828 | }
829 |
830 | return CalculateFactorial(n);
831 | }
832 | ```
833 |
834 | ## Extension methods
835 |
836 | Extension methods allow you to add methods to existing types without modifying the original type, making them particularly useful for extending types you don't control.
837 |
838 | ```csharp
839 | // Must be defined in a non-nested, non-generic static class
840 | public static class StringExtensions
841 | {
842 | // Extension method for string type
843 | public static bool IsNullOrEmpty(this string value)
844 | {
845 | return string.IsNullOrEmpty(value);
846 | }
847 |
848 | // Extension method with parameters
849 | public static string Truncate(this string value, int maxLength)
850 | {
851 | if (string.IsNullOrEmpty(value)) return value;
852 | return value.Length <= maxLength ? value : value.Substring(0, maxLength);
853 | }
854 | }
855 |
856 | // Usage
857 | string text = "Hello, World!";
858 | bool isEmpty = text.IsNullOrEmpty(); // false
859 | string truncated = text.Truncate(5); // "Hello"
860 | ```
861 |
862 | ## Lambda expressions
863 |
864 | Lambda expressions provide a concise way to create anonymous functions, especially useful for LINQ queries, event handlers, and functional programming patterns.
865 |
866 | ```csharp
867 | // Func delegate (takes parameters, returns a value)
868 | Func add = (a, b) => a + b;
869 | int sum = add(2, 3); // 5
870 |
871 | // Action delegate (takes parameters, returns void)
872 | Action print = message => Console.WriteLine(message);
873 | print("Hello!"); // Prints "Hello!"
874 |
875 | // Predicate delegate (takes parameters, returns bool)
876 | Predicate isEven = number => number % 2 == 0;
877 | bool result = isEven(4); // true
878 |
879 | // Multi-line lambda
880 | Func factorial = n =>
881 | {
882 | int result = 1;
883 | for (int i = 1; i <= n; i++)
884 | {
885 | result *= i;
886 | }
887 | return result;
888 | };
889 | ```
890 |
891 | ### Default parameters in lambdas (C# 12)
892 |
893 | Lambdas can now have default values for parameters:
894 |
895 | ```csharp
896 | Func add = (x, y = 10) => x + y;
897 | int result = add(5); // 15
898 | ```
899 |
900 | ## Method overloading
901 |
902 | Method overloading allows multiple methods with the same name but different parameter lists, providing flexibility in how a method can be called.
903 |
904 | ```csharp
905 | // Method overloading (same name, different parameters)
906 | public void Display(int value)
907 | {
908 | Console.WriteLine($"Integer: {value}");
909 | }
910 |
911 | public void Display(string value)
912 | {
913 | Console.WriteLine($"String: {value}");
914 | }
915 |
916 | public void Display(int value, string format)
917 | {
918 | Console.WriteLine($"Formatted: {value.ToString(format)}");
919 | }
920 | ```
921 |
922 | **Additional resources:**
923 |
924 | - [Methods (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/methods)
925 | - [Expression-bodied members (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members)
926 | - [Method parameters (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/method-parameters)
927 | - [Extension methods (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods)
928 | - [Lambda expressions (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions)
929 |
930 |
931 |
932 | # Delegates and events
933 |
934 | **Delegates** are type-safe pointers to methods, enabling flexible method invocation. They are often used for callbacks, event handling, and implementing the observer pattern. Delegates can point to static or instance methods and can be multicast (pointing to multiple methods).
935 |
936 | ```csharp
937 | public delegate int Operation(int x, int y);
938 |
939 | public int Add(int a, int b) => a + b;
940 | public int Multiply(int a, int b) => a * b;
941 |
942 | // Usage
943 | Operation op = Add;
944 | int result = op(3, 4); // 7
945 | op += Multiply; // Multicast delegate
946 | ```
947 |
948 | **Events** are built on delegates and provide a way for classes to notify subscribers when something happens. Events are typically used in GUI applications and other scenarios where you want to decouple the event source from the event handler.
949 |
950 | ```csharp:
951 | public class Button
952 | {
953 | public event EventHandler Clicked;
954 |
955 | protected virtual void OnClicked() =>
956 | Clicked?.Invoke(this, EventArgs.Empty);
957 | }
958 |
959 | // Usage
960 | var button = new Button();
961 | button.Clicked += (sender, args) => Console.WriteLine("Clicked!");
962 | ```
963 |
964 | **Additional resources**:
965 |
966 | - [Delegates (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/)
967 | - [Events (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/events-overview)
968 |
969 |
970 |
971 | # Data types
972 |
973 | C# supports a variety of composite data types to organize and represent data.
974 |
975 | ## Classes
976 |
977 | Classes are reference types that encapsulate data and behavior.
978 |
979 | ```csharp
980 | // Basic class definition
981 | public class Person
982 | {
983 | // Fields
984 | private string name;
985 | private int age;
986 |
987 | // Properties
988 | public string Name
989 | {
990 | get { return name; }
991 | set { name = value; }
992 | }
993 |
994 | // Auto-implemented property
995 | public int Age { get; set; }
996 |
997 | // Read-only property
998 | public bool IsAdult => Age >= 18;
999 |
1000 | // Constructors
1001 | public Person()
1002 | {
1003 | // Default constructor
1004 | }
1005 |
1006 | public Person(string name, int age)
1007 | {
1008 | Name = name;
1009 | Age = age;
1010 | }
1011 |
1012 | // Methods
1013 | public void Introduce()
1014 | {
1015 | Console.WriteLine($"Hello, my name is {Name} and I'm {Age} years old.");
1016 | }
1017 |
1018 | public string GetDescription() => $"{Name}, {Age} years old";
1019 |
1020 | // Static members
1021 | public static int MinimumAge { get; } = 0;
1022 |
1023 | public static bool IsValidAge(int age)
1024 | {
1025 | return age >= MinimumAge;
1026 | }
1027 | }
1028 |
1029 | // Usage
1030 | Person person = new Person();
1031 | person.Name = "Alice";
1032 | person.Age = 30;
1033 | person.Introduce();
1034 |
1035 | Person bob = new Person("Bob", 25);
1036 | string description = bob.GetDescription();
1037 | bool isAdult = bob.IsAdult;
1038 |
1039 | bool isValid = Person.IsValidAge(20);
1040 | ```
1041 |
1042 | ## Structs
1043 |
1044 | Structs are value types and are suitable for small, immutable data structures.
1045 |
1046 | Note that you cannot give initial values to a struct unless you make it static or const.
1047 |
1048 | ```csharp
1049 | // Basic struct definition
1050 | public struct Point
1051 | {
1052 | // Fields
1053 | public double X { get; set; }
1054 | public double Y { get; set; }
1055 |
1056 | // Constructor
1057 | public Point(double x, double y)
1058 | {
1059 | X = x;
1060 | Y = y;
1061 | }
1062 |
1063 | // Methods
1064 | public double DistanceFromOrigin()
1065 | {
1066 | return Math.Sqrt(X * X + Y * Y);
1067 | }
1068 |
1069 | // Override ToString method
1070 | public override string ToString() => $"({X}, {Y})";
1071 | }
1072 |
1073 | // Usage
1074 | Point point = new Point(3, 4);
1075 | double distance = point.DistanceFromOrigin(); // 5
1076 | ```
1077 |
1078 | ## Records (C# 9.0+)
1079 |
1080 | Records are reference types designed for representing immutable data.
1081 |
1082 | ```csharp
1083 | // Positional record (concise syntax)
1084 | public record Person(string FirstName, string LastName, int Age);
1085 |
1086 | // Usage
1087 | var person1 = new Person("John", "Doe", 30);
1088 | var person2 = new Person("John", "Doe", 30);
1089 |
1090 | // Records have value-based equality
1091 | bool areEqual = person1 == person2; // true
1092 |
1093 | // Non-destructive mutation with 'with' expression
1094 | var person3 = person1 with { Age = 31 };
1095 |
1096 | // Records can also be defined with standard syntax for more flexibility
1097 | public record Employee
1098 | {
1099 | public string FirstName { get; init; }
1100 | public string LastName { get; init; }
1101 | public int Id { get; init; }
1102 |
1103 | // Additional members can be defined
1104 | public string FullName => $"{FirstName} {LastName}";
1105 | }
1106 | ```
1107 |
1108 | ## Record structs (C# 10.0+)
1109 |
1110 | Record structs combine the value semantics of structs with the special features of records.
1111 |
1112 | ```csharp
1113 | // Record struct
1114 | public record struct Point(double X, double Y);
1115 |
1116 | // Mutable record struct
1117 | public record struct MutablePoint
1118 | {
1119 | public double X { get; set; }
1120 | public double Y { get; set; }
1121 |
1122 | public double Distance => Math.Sqrt(X * X + Y * Y);
1123 | }
1124 | ```
1125 |
1126 | ## Interfaces
1127 |
1128 | Interfaces define a contract that classes can implement.
1129 |
1130 | ```csharp
1131 | // Interface definition
1132 | public interface IShape
1133 | {
1134 | double Area { get; }
1135 | double Perimeter { get; }
1136 | void Draw();
1137 | string GetDescription() => $"Shape with area {Area} and perimeter {Perimeter}"; // Default implementation (C# 8.0+)
1138 | }
1139 |
1140 | // Implementing an interface
1141 | public class Circle : IShape
1142 | {
1143 | public double Radius { get; }
1144 |
1145 | public Circle(double radius)
1146 | {
1147 | Radius = radius;
1148 | }
1149 |
1150 | public double Area => Math.PI * Radius * Radius;
1151 | public double Perimeter => 2 * Math.PI * Radius;
1152 |
1153 | public void Draw()
1154 | {
1155 | Console.WriteLine("Drawing a circle");
1156 | }
1157 |
1158 | // Override default implementation
1159 | public string GetDescription() => $"Circle with radius {Radius}";
1160 | }
1161 | ```
1162 |
1163 | From C# 8.0, interfaces can have **default implementations for methods and properties**, allowing you to provide a base implementation that can be overridden by implementing classes.
1164 |
1165 | ```csharp
1166 | public interface ILogger
1167 | {
1168 | void Log(string message);
1169 |
1170 | // Default implementation
1171 | void LogError(string message) => Log($"ERROR: {message}");
1172 | void LogWarning(string message) => Log($"WARNING: {message}");
1173 |
1174 | // Static method in interface
1175 | static string FormatMessage(string level, string message) => $"[{level}] {message}";
1176 | }
1177 |
1178 | // Implement only required methods
1179 | public class MinimalLogger : ILogger
1180 | {
1181 | public void Log(string message)
1182 | {
1183 | Console.WriteLine(message);
1184 | }
1185 |
1186 | // Other methods use default implementations
1187 | }
1188 | ```
1189 |
1190 | ## Enums
1191 |
1192 | Enums define a set of named constants.
1193 |
1194 | ```csharp
1195 | // Basic enum
1196 | public enum DayOfWeek
1197 | {
1198 | Sunday, // 0
1199 | Monday, // 1
1200 | Tuesday, // 2
1201 | Wednesday, // 3
1202 | Thursday, // 4
1203 | Friday, // 5
1204 | Saturday // 6
1205 | }
1206 |
1207 | // Enum with explicit values
1208 | public enum HttpStatusCode
1209 | {
1210 | OK = 200,
1211 | Created = 201,
1212 | BadRequest = 400,
1213 | Unauthorized = 401,
1214 | NotFound = 404,
1215 | InternalServerError = 500
1216 | }
1217 |
1218 | // Enum with flags attribute (for bitwise operations)
1219 | [Flags]
1220 | public enum Permissions
1221 | {
1222 | None = 0,
1223 | Read = 1,
1224 | Write = 2,
1225 | Execute = 4,
1226 | All = Read | Write | Execute
1227 | }
1228 |
1229 | // Usage
1230 | DayOfWeek today = DayOfWeek.Monday;
1231 | HttpStatusCode status = HttpStatusCode.OK;
1232 |
1233 | // Convert between enum and integer
1234 | int dayValue = (int)today;
1235 | DayOfWeek convertedDay = (DayOfWeek)3; // Wednesday
1236 |
1237 | // Parsing from string
1238 | DayOfWeek parsed = Enum.Parse("Friday");
1239 | bool success = Enum.TryParse("Sunday", out DayOfWeek result);
1240 |
1241 | // Using flags
1242 | Permissions userPermissions = Permissions.Read | Permissions.Write;
1243 | bool canRead = userPermissions.HasFlag(Permissions.Read); // true
1244 | bool canExecute = userPermissions.HasFlag(Permissions.Execute); // false
1245 | ```
1246 |
1247 | ## Tuples
1248 |
1249 | Tuples group multiple values without defining a specific type.
1250 |
1251 | ```csharp
1252 | // Creating tuples (C# 7.0+)
1253 | (int, string) pair = (1, "one");
1254 | var person = (Name: "Alice", Age: 30);
1255 |
1256 | // Accessing tuple elements
1257 | int id = pair.Item1;
1258 | string value = pair.Item2;
1259 | string name = person.Name;
1260 | int age = person.Age;
1261 |
1262 | // Tuple deconstruction
1263 | var (newId, newValue) = pair;
1264 | var (newName, newAge) = person;
1265 |
1266 | // Using tuples as method return values
1267 | (int Min, int Max) FindMinMax(int[] numbers)
1268 | {
1269 | return (numbers.Min(), numbers.Max());
1270 | }
1271 |
1272 | var result = FindMinMax(new[] { 1, 2, 3, 4, 5 });
1273 | Console.WriteLine($"Min: {result.Min}, Max: {result.Max}");
1274 |
1275 | // Tuple as method parameter
1276 | void ProcessData((string Name, int Age) person)
1277 | {
1278 | Console.WriteLine($"Processing data for {person.Name}, {person.Age}");
1279 | }
1280 | ```
1281 |
1282 | ## Nullable types
1283 |
1284 | Nullable types represent values that can be null.
1285 |
1286 | ```csharp
1287 | // Nullable value types
1288 | int? nullableInt = null;
1289 | double? nullableDouble = 3.14;
1290 |
1291 | // Checking for null
1292 | if (nullableInt.HasValue)
1293 | {
1294 | int value = nullableInt.Value;
1295 | }
1296 |
1297 | // Null-coalescing operator
1298 | int result = nullableInt ?? 0; // If nullableInt is null, use 0
1299 |
1300 | // Nullable reference types (C# 8.0+)
1301 | // Enable with: #nullable enable
1302 | string? nullableString = null;
1303 | string nonNullableString = "Hello"; // Cannot be null
1304 |
1305 | // Null-conditional operator
1306 | int? length = nullableString?.Length; // null if nullableString is null
1307 |
1308 | // Null-coalescing assignment (C# 8.0+)
1309 | nullableString ??= "Default";
1310 | ```
1311 |
1312 |
1313 |
1314 | # Generics
1315 |
1316 | Generics let you define type-safe, reusable classes, methods, and interfaces. They improve code reuse, type safety, and performance by avoiding boxing/unboxing overhead.
1317 |
1318 | **Generic class**:
1319 |
1320 | ```csharp
1321 | public class Repository
1322 | {
1323 | private readonly List _items = new();
1324 |
1325 | public void Add(T item) => _items.Add(item);
1326 | public T Get(int index) => _items[index];
1327 | }
1328 |
1329 | // Usage
1330 | var intRepo = new Repository();
1331 | intRepo.Add(42);
1332 | int number = intRepo.Get(0);
1333 | ```
1334 |
1335 | **Generic methods**:
1336 |
1337 | ```csharp
1338 | public T Echo(T input) => input;
1339 |
1340 | // Usage
1341 | string message = Echo("Hello");
1342 | int number = Echo(123);
1343 | ```
1344 |
1345 | **Constraints (limit generic types)**:
1346 |
1347 | ```csharp
1348 | public class EmployeeRepository where T : Employee, new()
1349 | {
1350 | public T Create() => new T();
1351 | }
1352 | ```
1353 |
1354 | **Additional resources**:
1355 |
1356 | - [Generics (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/generics)
1357 |
1358 |
1359 |
1360 | # Classes
1361 |
1362 | **Classes** are the foundation of object-oriented programming in C#, serving as blueprints for creating objects that encapsulate data and behavior.
1363 |
1364 | **Inheritance** is a powerful mechanism that enables code reuse and polymorphism by allowing classes to inherit attributes and methods from parent classes.
1365 |
1366 | Proper use of these features helps create maintainable, extensible code with clear hierarchies.
1367 |
1368 | ## Constructors and initialization
1369 |
1370 | Constructors are special methods that initialize objects. They set initial state, enforce invariants, and can chain to other constructors to share initialization logic. Understanding constructor patterns is essential for creating maintainable class hierarchies.
1371 |
1372 | ```csharp
1373 | public class Person
1374 | {
1375 | public string Name { get; set; }
1376 | public int Age { get; set; }
1377 |
1378 | // Default constructor
1379 | public Person()
1380 | {
1381 | Name = "Unknown";
1382 | Age = 0;
1383 | }
1384 |
1385 | // Parameterized constructor
1386 | public Person(string name, int age)
1387 | {
1388 | Name = name;
1389 | Age = age;
1390 | }
1391 |
1392 | // Static constructor (called once before type is used)
1393 | static Person()
1394 | {
1395 | Console.WriteLine("Person type initialized");
1396 | }
1397 | }
1398 |
1399 | // Constructor chaining
1400 | public class Employee : Person
1401 | {
1402 | public string Department { get; set; }
1403 |
1404 | // Call the base class constructor
1405 | public Employee(string name, int age, string department)
1406 | : base(name, age)
1407 | {
1408 | Department = department;
1409 | }
1410 |
1411 | // Chain to another constructor in the same class
1412 | public Employee(string name, int age)
1413 | : this(name, age, "General")
1414 | {
1415 | }
1416 | }
1417 | ```
1418 |
1419 | ## Primary constructors (C# 12+)
1420 |
1421 | Primary constructors are a new feature in C# 12 that simplify class initialization by allowing constructor parameters to be defined directly in the class declaration. This reduces boilerplate code and makes the relationship between constructor parameters and class members more explicit.
1422 |
1423 | ```csharp
1424 | // Class with primary constructor
1425 | public class Person(string name, int age)
1426 | {
1427 | // Properties initialized by primary constructor parameters
1428 | public string Name { get; } = name;
1429 | public int Age { get; } = age;
1430 |
1431 | // Can use constructor parameters directly in methods
1432 | public string Introduce() => $"My name is {name} and I'm {age} years old";
1433 |
1434 | // Can still have additional constructors
1435 | public Person(string name) : this(name, 0)
1436 | {
1437 | Console.WriteLine("Created a person with default age");
1438 | }
1439 | }
1440 |
1441 | // Inheritance with primary constructors
1442 | public class Employee(string name, int age, string department) : Person(name, age)
1443 | {
1444 | public string Department { get; } = department;
1445 |
1446 | // Using base constructor parameters
1447 | public override string Introduce() => $"{base.Introduce()} working in {department}";
1448 | }
1449 |
1450 | // Usage
1451 | var alice = new Person("Alice", 30);
1452 | var bob = new Employee("Bob", 25, "Engineering");
1453 | ```
1454 |
1455 | ## Inheritance
1456 |
1457 | Inheritance allows a class (derived class) to inherit properties, methods, and events from another class (base class). This promotes code reuse and establishes an "is-a" relationship between classes. In C#, a class can inherit from only one base class but can implement multiple interfaces.
1458 |
1459 | ```csharp
1460 | // Base class
1461 | public class Animal
1462 | {
1463 | public string Name { get; set; }
1464 |
1465 | public Animal(string name)
1466 | {
1467 | Name = name;
1468 | }
1469 |
1470 | public virtual void MakeSound()
1471 | {
1472 | Console.WriteLine("Some generic animal sound");
1473 | }
1474 |
1475 | // Non-overridable method
1476 | public void Sleep()
1477 | {
1478 | Console.WriteLine($"{Name} is sleeping");
1479 | }
1480 | }
1481 |
1482 | // Derived class
1483 | public class Dog : Animal
1484 | {
1485 | public string Breed { get; set; }
1486 |
1487 | public Dog(string name, string breed) : base(name)
1488 | {
1489 | Breed = breed;
1490 | }
1491 |
1492 | // Override base class method
1493 | public override void MakeSound()
1494 | {
1495 | Console.WriteLine("Woof!");
1496 | }
1497 |
1498 | // New method
1499 | public void Fetch()
1500 | {
1501 | Console.WriteLine($"{Name} is fetching the ball");
1502 | }
1503 | }
1504 | ```
1505 |
1506 | ## Abstract classes
1507 |
1508 | Abstract classes serve as incomplete templates that cannot be instantiated directly but must be inherited by concrete classes. They're useful when you want to define common functionality while forcing derived classes to implement specific methods. An abstract class can have both abstract members (without implementation) and concrete members (with implementation).
1509 |
1510 | ```csharp
1511 | // Abstract class
1512 | public abstract class Shape
1513 | {
1514 | // Abstract property (must be implemented)
1515 | public abstract double Area { get; }
1516 |
1517 | // Abstract method (must be implemented)
1518 | public abstract double Perimeter();
1519 |
1520 | // Concrete method
1521 | public void PrintInfo()
1522 | {
1523 | Console.WriteLine($"Area: {Area}, Perimeter: {Perimeter()}");
1524 | }
1525 | }
1526 |
1527 | // Concrete implementation
1528 | public class Rectangle : Shape
1529 | {
1530 | public double Width { get; set; }
1531 | public double Height { get; set; }
1532 |
1533 | public Rectangle(double width, double height)
1534 | {
1535 | Width = width;
1536 | Height = height;
1537 | }
1538 |
1539 | public override double Area => Width * Height;
1540 |
1541 | public override double Perimeter() => 2 * (Width + Height);
1542 | }
1543 | ```
1544 |
1545 | ## Sealed classes and members
1546 |
1547 | Sealed classes prevent inheritance, making them useful for security-sensitive classes or when inheritance would break functionality. Sealed methods prevent further overriding in derived classes, ensuring that specific implementations remain unchanged throughout the inheritance chain.
1548 |
1549 | ```csharp
1550 | // Sealed class (cannot be inherited)
1551 | public sealed class Utility
1552 | {
1553 | public static void DoSomething() { }
1554 | }
1555 |
1556 | public class Base
1557 | {
1558 | // Virtual method that can be overridden
1559 | public virtual void Method1() { }
1560 |
1561 | // Virtual method that can be overridden
1562 | public virtual void Method2() { }
1563 | }
1564 |
1565 | public class Derived : Base
1566 | {
1567 | public override void Method1() { }
1568 |
1569 | // Sealed method (can't be overridden further in inheritance chain)
1570 | public sealed override void Method2() { }
1571 | }
1572 |
1573 | public class Further : Derived
1574 | {
1575 | public override void Method1() { } // OK
1576 | // public override void Method2() { } // Error: cannot override sealed method
1577 | }
1578 | ```
1579 |
1580 | ## Polymorphism
1581 |
1582 | Polymorphism allows objects to be treated as instances of their parent class rather than their actual type. This enables more flexible and extensible code by letting you write methods that operate on base classes but respond differently based on the actual object type at runtime.
1583 |
1584 | ```csharp
1585 | // Base class reference can refer to derived class objects
1586 | Animal myPet = new Dog("Fido", "Beagle");
1587 | myPet.MakeSound(); // Outputs "Woof!"
1588 |
1589 | // Array of base class can hold derived objects
1590 | Animal[] animals = new Animal[]
1591 | {
1592 | new Dog("Fido", "Beagle"),
1593 | new Cat("Whiskers"),
1594 | new Rabbit("Hopper")
1595 | };
1596 |
1597 | // Polymorphic behavior
1598 | foreach (Animal animal in animals)
1599 | {
1600 | Console.WriteLine($"{animal.Name} says:");
1601 | animal.MakeSound(); // Each animal makes its own sound
1602 | }
1603 |
1604 | // Type checking and casting
1605 | if (animals[0] is Dog dog)
1606 | {
1607 | dog.Fetch(); // Access Dog-specific method
1608 | }
1609 |
1610 | // Explicit casting
1611 | Dog anotherDog = (Dog)animals[0];
1612 | ```
1613 |
1614 | When designing class hierarchies, consider these guidelines:
1615 | - Use inheritance when there is a true "is-a" relationship between classes
1616 | - Prefer composition over inheritance for "has-a" relationships
1617 | - Use abstract classes when you want to provide common behavior with forced specialization
1618 | - Use sealed classes for security-sensitive code or to prevent unintended inheritance
1619 | - Implement interfaces for defining capabilities that can be shared across unrelated classes
1620 |
1621 | **Additional resources:**
1622 |
1623 | - [Classes and objects (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/classes-and-objects)
1624 | - [Inheritance (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/inheritance)
1625 | - [Abstract classes and methods (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/abstract)
1626 | - [Primary constructors (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/primary-constructors)
1627 | - [C# object-oriented programming best practices (Microsoft Learn)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/object-oriented-programming)
1628 |
1629 |
1630 |
1631 |
1632 | # Collections
1633 |
1634 | Collections in C# provide powerful ways to store, manage, and manipulate groups of related objects. The .NET framework offers various collection types optimized for different scenarios, from simple arrays to complex specialized collections. Choosing the right collection type is essential for writing efficient and maintainable code.
1635 |
1636 | ## Collection Expressions (C# 12+)
1637 |
1638 | Collection expressions are a concise way to initialize collections, introduced in C# 12. They provide a unified syntax for creating and initializing different collection types.
1639 |
1640 | ```csharp
1641 | // Creating collections with the new collection expressions syntax
1642 | int[] numbers = [1, 2, 3, 4, 5]; // Array
1643 | List names = ["Alice", "Bob", "Charlie"]; // List
1644 | HashSet letters = ['a', 'b', 'c']; // HashSet
1645 | Dictionary ages = [ // Dictionary
1646 | "Alice" => 30,
1647 | "Bob" => 25,
1648 | "Charlie" => 35
1649 | ];
1650 |
1651 | // Spread operator - combining collections
1652 | int[] moreNumbers = [0, .. numbers, 6]; // [0, 1, 2, 3, 4, 5, 6]
1653 | string[] firstThree = [.. names[0..3]]; // ["Alice", "Bob", "Charlie"]
1654 |
1655 | // Pattern matching with collection expressions
1656 | bool IsValidPoint(int[] point) => point is [var x, var y] && x >= 0 && y >= 0;
1657 | ```
1658 |
1659 | ## Arrays
1660 |
1661 | Arrays are fixed-size collections of elements of the same type. They provide efficient random access but have a predetermined size that cannot change after creation.
1662 |
1663 | ```csharp
1664 | // Declaration and initialization
1665 | int[] numbers = new int[5]; // Array of 5 integers with default values (0)
1666 | int[] initialized = new int[] { 1, 2, 3, 4, 5 }; // Initialized array
1667 | int[] shorthand = { 1, 2, 3, 4, 5 }; // Shorthand initialization
1668 | string[] names = { "Alice", "Bob", "Charlie" };
1669 |
1670 | // Accessing elements
1671 | int firstNumber = numbers[0]; // First element (zero-based indexing)
1672 | numbers[0] = 10; // Assign value to first element
1673 |
1674 | // Multi-dimensional arrays
1675 | int[,] matrix = new int[3, 3]; // 3x3 2D array
1676 | matrix[0, 0] = 1; // Assign to specific position
1677 | int[,] initialized2D = { // Initialize 2D array
1678 | { 1, 2, 3 },
1679 | { 4, 5, 6 },
1680 | { 7, 8, 9 }
1681 | };
1682 |
1683 | // Jagged arrays (arrays of arrays)
1684 | int[][] jagged = new int[3][];
1685 | jagged[0] = new int[] { 1, 2, 3 };
1686 | jagged[1] = new int[] { 4, 5 };
1687 | jagged[2] = new int[] { 6, 7, 8, 9 };
1688 |
1689 | // Array properties and methods
1690 | int length = numbers.Length; // Number of elements
1691 | Array.Sort(numbers); // Sort array in-place
1692 | Array.Reverse(numbers); // Reverse array in-place
1693 | int index = Array.IndexOf(names, "Bob"); // Find index of element
1694 | bool exists = Array.Exists(numbers, n => n > 10); // Check if condition exists
1695 | ```
1696 |
1697 | ## Lists
1698 |
1699 | Lists are dynamic arrays that can grow or shrink in size. They provide flexibility and are generally the go-to collection type for most scenarios when you need a sequence of elements.
1700 |
1701 | ```csharp
1702 | using System.Collections.Generic;
1703 |
1704 | // Create a list
1705 | List names = new List(); // Empty list
1706 | List numbers = new List { 1, 2, 3 }; // Initialized list
1707 |
1708 | // Add elements
1709 | names.Add("Alice"); // Add single element
1710 | names.AddRange(new[] { "Bob", "Charlie" }); // Add multiple elements
1711 |
1712 | // Access elements
1713 | string first = names[0]; // Access by index
1714 | names[0] = "Alicia"; // Modify by index
1715 |
1716 | // Remove elements
1717 | names.Remove("Bob"); // Remove specific element
1718 | names.RemoveAt(0); // Remove element at index
1719 | names.RemoveAll(x => x.StartsWith("C")); // Remove all that match condition
1720 | names.Clear(); // Remove all elements
1721 |
1722 | // Search and query
1723 | bool contains = numbers.Contains(2); // Check if contains value
1724 | int index = numbers.IndexOf(3); // Find index of element
1725 | List filtered = numbers.FindAll(n => n > 1); // Find all matching elements
1726 | int found = numbers.Find(n => n > 2); // Find first matching element
1727 |
1728 | // Other operations
1729 | int count = numbers.Count; // Number of elements
1730 | numbers.Sort(); // Sort list in-place
1731 | numbers.Reverse(); // Reverse list in-place
1732 | numbers.ForEach(n => Console.WriteLine(n)); // Perform action on each element
1733 | ```
1734 |
1735 | ## Dictionary
1736 |
1737 | Dictionaries store key-value pairs for fast lookups by key. They are essential when you need to quickly access values based on unique identifiers.
1738 |
1739 | ```csharp
1740 | using System.Collections.Generic;
1741 |
1742 | // Create a dictionary
1743 | Dictionary ages = new Dictionary();
1744 | Dictionary capitals = new Dictionary
1745 | {
1746 | { "USA", "Washington D.C." },
1747 | { "UK", "London" },
1748 | ["France"] = "Paris" // Alternative initialization syntax
1749 | };
1750 |
1751 | // Add entries
1752 | ages.Add("Alice", 30);
1753 | ages["Bob"] = 25; // Add or update using indexer
1754 |
1755 | // Access values
1756 | int aliceAge = ages["Alice"]; // Access by key (throws if not found)
1757 | bool success = ages.TryGetValue("Charlie", out int charlieAge); // Safe access
1758 |
1759 | // Check existence
1760 | bool containsKey = ages.ContainsKey("Alice");
1761 | bool containsValue = ages.ContainsValue(25);
1762 |
1763 | // Remove entries
1764 | bool removed = ages.Remove("Bob");
1765 |
1766 | // Iterate through dictionary
1767 | foreach (KeyValuePair pair in ages)
1768 | {
1769 | Console.WriteLine($"{pair.Key}: {pair.Value}");
1770 | }
1771 |
1772 | // Or using deconstruction (C# 7.0+)
1773 | foreach (var (name, age) in ages)
1774 | {
1775 | Console.WriteLine($"{name}: {age}");
1776 | }
1777 | ```
1778 |
1779 | ## HashSet
1780 |
1781 | HashSets store unique elements with fast lookup, insertion, and deletion. They're ideal for maintaining collections of unique items or performing set operations.
1782 |
1783 | ```csharp
1784 | using System.Collections.Generic;
1785 |
1786 | // Create a HashSet
1787 | HashSet uniqueNumbers = new HashSet();
1788 | HashSet fruits = new HashSet { "Apple", "Banana", "Orange" };
1789 |
1790 | // Add elements
1791 | uniqueNumbers.Add(1); // Returns true if added
1792 | uniqueNumbers.Add(1); // Returns false (already exists)
1793 | uniqueNumbers.UnionWith(new[] { 2, 3, 4 }); // Add multiple elements
1794 |
1795 | // Check membership
1796 | bool contains = fruits.Contains("Apple"); // Fast lookup
1797 |
1798 | // Remove elements
1799 | bool removed = fruits.Remove("Banana");
1800 |
1801 | // Set operations
1802 | HashSet setA = new HashSet { 1, 2, 3 };
1803 | HashSet setB = new HashSet { 3, 4, 5 };
1804 |
1805 | setA.UnionWith(setB); // Union: { 1, 2, 3, 4, 5 }
1806 | setA.IntersectWith(setB); // Intersection: { 3 }
1807 | setA.ExceptWith(setB); // Difference: { 1, 2 }
1808 | setA.SymmetricExceptWith(setB); // Symmetric difference: { 1, 2, 4, 5 }
1809 |
1810 | bool isSubset = setA.IsSubsetOf(setB);
1811 | bool isSuperset = setA.IsSupersetOf(setB);
1812 | ```
1813 |
1814 | ## Queue and Stack
1815 |
1816 | Queues (FIFO - first in, first out) and Stacks (LIFO - last in, first out) are specialized collections that support specific access patterns common in many algorithms and data processing scenarios.
1817 |
1818 | ```csharp
1819 | using System.Collections.Generic;
1820 |
1821 | // Queue (First In, First Out)
1822 | Queue queue = new Queue();
1823 | queue.Enqueue("First"); // Add to end
1824 | queue.Enqueue("Second");
1825 | queue.Enqueue("Third");
1826 |
1827 | string next = queue.Peek(); // View next item without removing
1828 | string dequeued = queue.Dequeue(); // Remove and return next item
1829 | int count = queue.Count; // Number of items
1830 | bool contains = queue.Contains("Second");
1831 |
1832 | // Stack (Last In, First Out)
1833 | Stack stack = new Stack();
1834 | stack.Push(1); // Add to top
1835 | stack.Push(2);
1836 | stack.Push(3);
1837 |
1838 | int top = stack.Peek(); // View top item without removing
1839 | int popped = stack.Pop(); // Remove and return top item
1840 | int stackCount = stack.Count; // Number of items
1841 | bool stackContains = stack.Contains(2);
1842 | ```
1843 |
1844 | ## LINQ (Language Integrated Query)
1845 |
1846 | LINQ provides powerful query capabilities for collections, making it easier to filter, transform, and aggregate data. It brings database-like query operations to in-memory collections.
1847 |
1848 | ```csharp
1849 | using System.Linq;
1850 |
1851 | List numbers = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
1852 |
1853 | // Filtering
1854 | var evens = numbers.Where(n => n % 2 == 0); // [2, 4, 6, 8, 10]
1855 | var greaterThanFive = numbers.Where(n => n > 5); // [6, 7, 8, 9, 10]
1856 |
1857 | // Transformation
1858 | var doubled = numbers.Select(n => n * 2); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
1859 | var numberObjects = numbers.Select(n => new { Value = n, IsEven = n % 2 == 0 });
1860 |
1861 | // Ordering
1862 | var ascending = numbers.OrderBy(n => n); // [1, 2, 3, ...]
1863 | var descending = numbers.OrderByDescending(n => n); // [10, 9, 8, ...]
1864 | var complex = numbers.OrderBy(n => n % 3).ThenByDescending(n => n); // Multiple criteria
1865 |
1866 | // Aggregation
1867 | int sum = numbers.Sum(); // 55
1868 | int min = numbers.Min(); // 1
1869 | int max = numbers.Max(); // 10
1870 | double average = numbers.Average(); // 5.5
1871 | int product = numbers.Aggregate((a, b) => a * b); // 3628800 (factorial of 10)
1872 |
1873 | // Quantifiers
1874 | bool allEven = numbers.All(n => n % 2 == 0); // false
1875 | bool anyEven = numbers.Any(n => n % 2 == 0); // true
1876 | bool containsSeven = numbers.Contains(7); // true
1877 |
1878 | // Partitioning
1879 | var firstThree = numbers.Take(3); // [1, 2, 3]
1880 | var skipFirstThree = numbers.Skip(3); // [4, 5, 6, 7, 8, 9, 10]
1881 | var takeLast = numbers.TakeLast(2); // [9, 10]
1882 | var skipLast = numbers.SkipLast(2); // [1, 2, 3, 4, 5, 6, 7, 8]
1883 |
1884 | // Element operations
1885 | int first = numbers.First(); // 1
1886 | int firstEven = numbers.First(n => n % 2 == 0); // 2
1887 | int lastOdd = numbers.Last(n => n % 2 != 0); // 9
1888 | int single = numbers.Where(n => n == 5).Single(); // 5
1889 |
1890 | // Grouping
1891 | var groups = numbers.GroupBy(n => n % 3); // Groups by remainder when divided by 3
1892 | foreach (var group in groups)
1893 | {
1894 | Console.WriteLine($"Remainder {group.Key}: {string.Join(", ", group)}");
1895 | }
1896 |
1897 | // Query syntax (alternative to method syntax)
1898 | var queryResult = from n in numbers
1899 | where n > 5
1900 | orderby n descending
1901 | select n * 2;
1902 | ```
1903 |
1904 | **Additional resources:**
1905 |
1906 | - [Collections overview (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/standard/collections/)
1907 | - [Collection expressions (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/collection-expressions)
1908 | - [LINQ (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/)
1909 | - [IEnumerable and IQueryable](https://dotnettutorials.net/lesson/differences-between-ienumerable-and-iqueryable/)
1910 | - [Choosing a collection type (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/standard/collections/selecting-a-collection-class)
1911 | - [System.Collections.Generic Namespace (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic)
1912 |
1913 |
1914 |
1915 | # Pattern matching
1916 |
1917 | C# supports various pattern matching techniques for more expressive conditional logic.
1918 |
1919 | ## Type patterns
1920 |
1921 | **Type patterns** allow you to check the type of an object and cast it in a single operation. This is particularly useful in `is` expressions and switch statements.
1922 |
1923 | ```csharp
1924 | // Type pattern - check if object is of a specific type
1925 | object value = "Hello";
1926 |
1927 | if (value is string text)
1928 | {
1929 | // 'text' is the value cast to string, available in this scope
1930 | Console.WriteLine(text.ToUpper());
1931 | }
1932 |
1933 | // Switch expression with type patterns (C# 8.0+)
1934 | string GetDisplayName(object item) => item switch
1935 | {
1936 | Person p => $"Person: {p.Name}",
1937 | DateTime d => $"Date: {d.ToShortDateString()}",
1938 | int i => $"Number: {i}",
1939 | string s => $"Text: {s}",
1940 | null => "Null value",
1941 | _ => "Unknown type" // Default case
1942 | };
1943 | ```
1944 |
1945 | ## Property patterns
1946 |
1947 | **Property patterns** allow you to match properties of an object directly in a pattern. This is useful for filtering or extracting data from complex objects.
1948 |
1949 | ```csharp
1950 | // Property pattern to match object properties
1951 | if (person is { Name: "Alice", Age: >= 30 })
1952 | {
1953 | Console.WriteLine("Found Alice who is 30 or older");
1954 | }
1955 |
1956 | // Switch expression with property patterns
1957 | string GetAgeCategory(Person person) => person switch
1958 | {
1959 | { Age: < 13 } => "Child",
1960 | { Age: < 20 } => "Teenager",
1961 | { Age: < 65 } => "Adult",
1962 | _ => "Senior"
1963 | };
1964 |
1965 | // Nested property patterns
1966 | if (order is { Customer: { Name: "Alice" } })
1967 | {
1968 | Console.WriteLine("This is Alice's order");
1969 | }
1970 | ```
1971 |
1972 | ## Tuple patterns
1973 |
1974 | **Tuple patterns** allow you to match multiple values at once, making it easy to work with data structures that contain multiple related values.
1975 |
1976 | ```csharp
1977 | // Tuple pattern
1978 | (int x, int y) = (5, 10);
1979 |
1980 | string GetQuadrant(int x, int y) => (x, y) switch
1981 | {
1982 | (> 0, > 0) => "First quadrant",
1983 | (< 0, > 0) => "Second quadrant",
1984 | (< 0, < 0) => "Third quadrant",
1985 | (> 0, < 0) => "Fourth quadrant",
1986 | (0, 0) => "Origin",
1987 | (_, 0) => "X-axis",
1988 | (0, _) => "Y-axis"
1989 | };
1990 | ```
1991 |
1992 | ## Logical patterns
1993 |
1994 | **Logical patterns** allow you to combine multiple conditions using logical operators like `and`, `or`, and `not`. This is useful for creating complex matching conditions.
1995 |
1996 | ```csharp
1997 | // 'and', 'or', and 'not' patterns (C# 9.0+)
1998 | if (person is { Age: > 20 and < 30, Name: "Alice" or "Bob" })
1999 | {
2000 | Console.WriteLine("Person is between 20-30 and named Alice or Bob");
2001 | }
2002 |
2003 | // In switch expression
2004 | string CheckValue(int value) => value switch
2005 | {
2006 | > 0 and < 10 => "Single digit positive",
2007 | >= 10 and < 100 => "Double digit positive",
2008 | < 0 and not -1 => "Negative, but not -1",
2009 | 0 or -1 => "Zero or negative one",
2010 | _ => "Other"
2011 | };
2012 | ```
2013 |
2014 | ## List patterns (C# 11.0+)
2015 |
2016 | **List patterns** allow you to match against the structure of collections, making it easier to work with arrays and lists.
2017 |
2018 | ```csharp
2019 | // List pattern in C# 11
2020 | var numbers = new[] { 1, 2, 3, 4 };
2021 |
2022 | bool IsFirstTwoPositive(int[] numbers) => numbers is [> 0, > 0, ..];
2023 |
2024 | string DescribeArray(int[] arr) => arr switch
2025 | {
2026 | [] => "Empty array",
2027 | [var single] => $"Single item: {single}",
2028 | [var first, var second] => $"Two items: {first}, {second}",
2029 | [var first, .. var middle, var last] => $"Multiple items, starts with {first}, ends with {last}",
2030 | _ => "Unknown pattern"
2031 | };
2032 | ```
2033 |
2034 | ## Discard pattern
2035 |
2036 | **Discard patterns (underscore `_`)** allow you to ignore specific values in a pattern match. This is useful when you only care about certain values and want to ignore the rest.
2037 |
2038 | ```csharp
2039 | // Discard pattern (underscore) to ignore values
2040 | string GetSign(int number) => number switch
2041 | {
2042 | < 0 => "Negative",
2043 | > 0 => "Positive",
2044 | _ => "Zero"
2045 | };
2046 |
2047 | // Multiple discards
2048 | (string, int) person = ("Alice", 30);
2049 | var (name, _) = person; // Discard the age
2050 | ```
2051 |
2052 |
2053 |
2054 | # Exceptions
2055 |
2056 | Exception handling is a critical aspect of robust C# applications, which allows you to manage errors and unexpected conditions. Well-designed exception handling balances providing useful feedback to users, maintaining application stability, and preserving valuable diagnostic information for developers.
2057 |
2058 | ## Try-catch-finally
2059 |
2060 | The try-catch-finally pattern forms the backbone of exception handling in C#. Code that might throw exceptions is placed in the `try` block, specific exception types are caught and handled in `catch` blocks, and cleanup code that should always execute (regardless of exceptions) goes in the `finally` block.
2061 |
2062 | ```csharp
2063 | try
2064 | {
2065 | // Code that might throw an exception
2066 | int result = 10 / 0; // Will throw DivideByZeroException
2067 | File.ReadAllText("nonexistent.txt"); // Will throw FileNotFoundException
2068 | }
2069 | catch (DivideByZeroException ex)
2070 | {
2071 | // Handle specific exception
2072 | Console.WriteLine($"Math error: {ex.Message}");
2073 | // Log the exception details for developers
2074 | Logger.LogError(ex, "Division by zero occurred");
2075 | // Provide user-friendly message
2076 | ShowUserErrorMessage("A calculation error occurred. Please try different input values.");
2077 | }
2078 | catch (FileNotFoundException ex) when (ex.FileName.Contains("nonexistent"))
2079 | {
2080 | // Exception filter (C# 6.0+) allows conditionals to catch blocks
2081 | Console.WriteLine($"File not found: {ex.FileName}");
2082 | }
2083 | catch (IOException ex)
2084 | {
2085 | // Handle another specific exception
2086 | Console.WriteLine($"IO error: {ex.Message}");
2087 | }
2088 | catch (Exception ex)
2089 | {
2090 | // Catch all other exceptions
2091 | Console.WriteLine($"Unexpected error: {ex.Message}");
2092 | throw; // Re-throw the exception to preserve stack trace
2093 | }
2094 | finally
2095 | {
2096 | // Code that always executes, whether an exception occurred or not
2097 | Console.WriteLine("This always runs");
2098 | // Common cleanup operations:
2099 | // - Close file handles
2100 | // - Release database connections
2101 | // - Dispose of unmanaged resources
2102 | // - Return pooled objects
2103 | }
2104 | ```
2105 |
2106 | ## When to use different Exception approaches
2107 |
2108 | 1. **Specific vs. general Exception catching**:
2109 | - Catch specific exceptions when you can handle them in a meaningful way
2110 | - Only catch `Exception` as a last resort to log unexpected errors or provide generic fallbacks
2111 | - Avoid empty catch blocks that swallow exceptions without handling them
2112 |
2113 | 2. **Exception filters**:
2114 | - Use when you only want to catch exceptions that meet certain criteria
2115 | - Helps avoid unnecessary exception handling and maintain more precise control flow
2116 |
2117 | 3. **Re-throwing Exceptions**:
2118 | - Use `throw;` (without specified exception) to preserve the original stack trace
2119 | - Only use `throw ex;` when you want to deliberately reset the stack trace (rarely needed)
2120 |
2121 | 4. **Exception prevention**:
2122 | - Use `TryParse` patterns and null checking to prevent exceptions when possible
2123 | - Reserve exception handling for truly exceptional conditions, not for normal control flow
2124 | - Consider validation before operations that might throw exceptions
2125 |
2126 | ## Throwing Exceptions
2127 |
2128 | Throwing exceptions should be done deliberately and with consideration for the calling code. This includes choosing appropriate exception types, providing meaningful messages, and including relevant context.
2129 |
2130 | ```csharp
2131 | // Throw exceptions
2132 | void ProcessData(string data)
2133 | {
2134 | if (data == null)
2135 | {
2136 | throw new ArgumentNullException(nameof(data), "Data cannot be null.");
2137 | }
2138 |
2139 | if (data.Length == 0)
2140 | {
2141 | throw new ArgumentException("Data cannot be empty.", nameof(data));
2142 | }
2143 |
2144 | if (!IsValidFormat(data))
2145 | {
2146 | throw new FormatException($"Data '{data}' is not in the required format.");
2147 | }
2148 |
2149 | // Process valid data...
2150 | }
2151 |
2152 | // Rethrowing exceptions
2153 | try
2154 | {
2155 | ProcessData(null);
2156 | }
2157 | catch (Exception ex)
2158 | {
2159 | // Log the exception
2160 | Console.WriteLine($"Error: {ex.Message}");
2161 |
2162 | // Preserve stack trace when rethrowing
2163 | throw;
2164 |
2165 | // This would reset the stack trace (usually undesirable):
2166 | // throw ex;
2167 | }
2168 | ```
2169 |
2170 | ## Guidelines for Exception types:
2171 |
2172 | 1. **System.ArgumentException**: Use when a method argument is invalid
2173 | 2. **System.ArgumentNullException**: Use when an argument is unexpectedly null
2174 | 3. **System.InvalidOperationException**: Use when the object state doesn't allow the operation
2175 | 4. **System.NotImplementedException**: Use for methods that aren't implemented yet
2176 | 5. **System.NotSupportedException**: Use for operations that won't be implemented
2177 | 6. **System.IO.IOException**: Use for file system and I/O errors
2178 | 7. **System.FormatException**: Use when string format is incorrect for the expected type
2179 | 8. **Custom exceptions**: Create when built-in exceptions don't appropriately describe your error
2180 |
2181 | ## Custom Exceptions
2182 |
2183 | Custom exceptions allow you to create domain-specific error types that provide more meaningful context about what went wrong in your application. They should be created when built-in exception types don't adequately capture the specific error condition.
2184 |
2185 | ```csharp
2186 | // Define custom exception
2187 | public class CustomerNotFoundException : Exception
2188 | {
2189 | public int CustomerId { get; }
2190 |
2191 | public CustomerNotFoundException(int customerId)
2192 | : base($"Customer with ID {customerId} was not found")
2193 | {
2194 | CustomerId = customerId;
2195 | }
2196 |
2197 | public CustomerNotFoundException(int customerId, Exception innerException)
2198 | : base($"Customer with ID {customerId} was not found", innerException)
2199 | {
2200 | CustomerId = customerId;
2201 | }
2202 |
2203 | // For serialization support (important for distributed applications)
2204 | protected CustomerNotFoundException(System.Runtime.Serialization.SerializationInfo info,
2205 | System.Runtime.Serialization.StreamingContext context) : base(info, context)
2206 | {
2207 | CustomerId = info.GetInt32(nameof(CustomerId));
2208 | }
2209 |
2210 | // Override GetObjectData for proper serialization
2211 | public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info,
2212 | System.Runtime.Serialization.StreamingContext context)
2213 | {
2214 | base.GetObjectData(info, context);
2215 | info.AddValue(nameof(CustomerId), CustomerId);
2216 | }
2217 | }
2218 |
2219 | // Usage
2220 | void ProcessCustomer(int customerId)
2221 | {
2222 | if (!customerDatabase.Exists(customerId))
2223 | {
2224 | throw new CustomerNotFoundException(customerId);
2225 | }
2226 |
2227 | // Process customer...
2228 | }
2229 | ```
2230 | ## Performance considerations
2231 |
2232 | Exception handling has performance implications that should be considered in your design:
2233 |
2234 | 1. The `try` block itself has minimal overhead when no exceptions occur
2235 | 2. Throwing and catching exceptions is relatively expensive and should not be used for normal control flow
2236 | 3. Use patterns like `TryParse` and null checking to avoid throwing exceptions in expected scenarios
2237 | 4. Reserve exceptions for truly exceptional conditions that shouldn't happen in normal operation
2238 | 5. Consider using status return codes or `Result` pattern for expected error conditions in performance-critical code
2239 |
2240 | **Additional resources:**
2241 |
2242 | - [Exception handling (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/)
2243 | - [Best practices for Exceptions (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions)
2244 | - [Creating and throwing Exceptions (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/standard/exceptions/how-to-create-user-defined-exceptions)
2245 | - [IDisposable pattern (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose)
2246 | - [Exception handling in async code (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/exception-handling-task-asynchronous-pattern)
2247 |
2248 |
2249 |
2250 | # Asynchronous Programming
2251 |
2252 | Asynchronous programming in C# allows you to write non-blocking code that can improve responsiveness and throughput, particularly in I/O-bound and network operations. Modern C# provides elegant syntax with async/await that makes asynchronous code almost as straightforward to write as synchronous code, while maintaining the performance benefits.
2253 |
2254 | ## When to use asynchronous programming
2255 |
2256 | Understanding when to use async code is crucial. Use async code for **I/O-bound operations**:
2257 | - Network requests
2258 | - Database operations
2259 | - File system operations
2260 | - Web API calls
2261 | - User input
2262 |
2263 | Don't use it for **CPU-bound operations**. For computationally intensive work, use:
2264 | - `Task.Run` to offload work to a background thread
2265 | - Parallel processing APIs for data parallelism
2266 |
2267 | ## Async and await basics
2268 |
2269 | ```csharp
2270 | // Async method declaration
2271 | public async Task DownloadDataAsync(string url)
2272 | {
2273 | // Create HTTP client
2274 | using HttpClient client = new HttpClient();
2275 |
2276 | // Asynchronously wait for the HTTP request
2277 | string result = await client.GetStringAsync(url);
2278 |
2279 | return result;
2280 | }
2281 |
2282 | // Calling async methods
2283 | public async Task ProcessDataAsync()
2284 | {
2285 | Console.WriteLine("Starting data download...");
2286 |
2287 | // Await the asynchronous operation
2288 | string data = await DownloadDataAsync("https://example.com/api/data");
2289 |
2290 | Console.WriteLine($"Downloaded {data.Length} bytes");
2291 | }
2292 |
2293 | // Void async methods (event handlers)
2294 | public async void Button_Click(object sender, EventArgs e)
2295 | {
2296 | try
2297 | {
2298 | await ProcessDataAsync();
2299 | MessageBox.Show("Download complete!");
2300 | }
2301 | catch (Exception ex)
2302 | {
2303 | MessageBox.Show($"Error: {ex.Message}");
2304 | }
2305 | }
2306 | ```
2307 |
2308 | ## Guidelines for async methods:
2309 |
2310 | 1. **Naming convention**: Append "Async" to method names that return Task or Task
2311 | 2. **Return types**:
2312 | - Use Task for methods that return a value
2313 | - Use Task for methods that don't return a value
2314 | - Avoid async void except for event handlers
2315 | 3. **Async all the way**: Convert entire call chains to async to avoid blocking
2316 | 4. **ConfigureAwait**: Use ConfigureAwait(false) in library code to avoid forcing context
2317 |
2318 | ## Task-based asynchronous pattern
2319 |
2320 | The Task-based asynchronous pattern (TAP) is the recommended approach for asynchronous programming in C#. It uses `Task` and `Task` to represent ongoing work and provides rich composition capabilities.
2321 |
2322 | ```csharp
2323 | // Create and return a Task
2324 | public Task CalculateAsync(int a, int b)
2325 | {
2326 | return Task.Run(() =>
2327 | {
2328 | // Simulate CPU-bound work
2329 | Thread.Sleep(1000);
2330 | return a + b;
2331 | });
2332 | }
2333 |
2334 | // Task.WhenAll - run multiple tasks in parallel
2335 | public async Task ProcessMultipleAsync()
2336 | {
2337 | Task task1 = DownloadDataAsync("https://example.com/api/1");
2338 | Task task2 = DownloadDataAsync("https://example.com/api/2");
2339 | Task task3 = DownloadDataAsync("https://example.com/api/3");
2340 |
2341 | // Wait for all tasks to complete
2342 | string[] results = await Task.WhenAll(task1, task2, task3);
2343 |
2344 | // Process results
2345 | foreach (string result in results)
2346 | {
2347 | Console.WriteLine($"Result length: {result.Length}");
2348 | }
2349 | }
2350 |
2351 | // Task.WhenAny - wait for the first task to complete
2352 | public async Task GetFastestResponseAsync()
2353 | {
2354 | Task task1 = DownloadDataAsync("https://example.com/api/1");
2355 | Task task2 = DownloadDataAsync("https://example.com/api/2");
2356 |
2357 | // Wait for the first task to complete
2358 | Task completedTask = await Task.WhenAny(task1, task2);
2359 |
2360 | // Get the result from the completed task
2361 | return await completedTask;
2362 | }
2363 | ```
2364 |
2365 | When to use different task composition methods:
2366 | 1. **Task.WhenAll**: Use when you need the results of all operations and they can run concurrently
2367 | 2. **Task.WhenAny**: Use for implementing timeouts, racing operations, or taking the first available result
2368 | 3. **Task.Run**: Use for CPU-bound work that needs to be offloaded from the current thread
2369 | 4. **Task.Delay**: Use for implementing timeouts or periodic operations in async methods
2370 |
2371 | ## Exception handling in async code
2372 |
2373 | ```csharp
2374 | public async Task ExceptionHandlingExampleAsync()
2375 | {
2376 | try
2377 | {
2378 | // Multiple awaits in one try block
2379 | string data = await DownloadDataAsync("https://example.com/api/data");
2380 | int result = await ProcessDataAsync(data);
2381 | await SaveResultAsync(result);
2382 | }
2383 | catch (HttpRequestException ex)
2384 | {
2385 | // Handle network-related exceptions
2386 | Console.WriteLine($"Network error: {ex.Message}");
2387 | }
2388 | catch (JsonException ex)
2389 | {
2390 | // Handle JSON parsing exceptions
2391 | Console.WriteLine($"Invalid data format: {ex.Message}");
2392 | }
2393 | catch (Exception ex)
2394 | {
2395 | // Handle all other exceptions
2396 | Console.WriteLine($"Unexpected error: {ex.Message}");
2397 | }
2398 | }
2399 |
2400 | // Aggregate exceptions with Task.WhenAll
2401 | public async Task HandleMultipleExceptionsAsync()
2402 | {
2403 | var tasks = new List();
2404 |
2405 | for (int i = 0; i < 5; i++)
2406 | {
2407 | int taskNumber = i;
2408 | tasks.Add(Task.Run(async () =>
2409 | {
2410 | if (taskNumber % 2 == 0)
2411 | {
2412 | await Task.Delay(100);
2413 | throw new Exception($"Task {taskNumber} failed");
2414 | }
2415 | }));
2416 | }
2417 |
2418 | try
2419 | {
2420 | await Task.WhenAll(tasks);
2421 | }
2422 | catch (Exception)
2423 | {
2424 | // Check for all exceptions
2425 | foreach (var task in tasks)
2426 | {
2427 | if (task.Exception != null)
2428 | {
2429 | Console.WriteLine(task.Exception.InnerException.Message);
2430 | }
2431 | }
2432 | }
2433 | }
2434 | ```
2435 |
2436 | ## Async exception handling best practices:
2437 |
2438 | 1. Always handle exceptions in async methods, especially in async void methods
2439 | 2. Be aware that `Task.WhenAll` throws only the first exception; check all tasks for exceptions
2440 | 3. Use `AggregateException.Flatten()` to simplify handling multiple exceptions
2441 | 4. Consider using a global exception handler for unhandled exceptions in async code
2442 | 5. Remember that exceptions in async methods are captured and placed on the returned Task
2443 |
2444 | ## Cancellation in async operations
2445 |
2446 | Cancellation allows long-running operations to be stopped gracefully. The `CancellationToken` mechanism provides a standardized way to implement cancellation in async methods.
2447 |
2448 | ```csharp
2449 | public async Task DemonstrateCancellationAsync()
2450 | {
2451 | // Create cancellation token source
2452 | using CancellationTokenSource cts = new CancellationTokenSource();
2453 |
2454 | // Set timeout after 5 seconds
2455 | cts.CancelAfter(TimeSpan.FromSeconds(5));
2456 |
2457 | try
2458 | {
2459 | await LongRunningOperationAsync(cts.Token);
2460 | }
2461 | catch (OperationCanceledException)
2462 | {
2463 | Console.WriteLine("Operation was canceled");
2464 | }
2465 | }
2466 |
2467 | public async Task LongRunningOperationAsync(CancellationToken cancellationToken)
2468 | {
2469 | for (int i = 0; i < 100; i++)
2470 | {
2471 | // Check cancellation before doing work
2472 | cancellationToken.ThrowIfCancellationRequested();
2473 |
2474 | // Perform some work
2475 | Console.WriteLine($"Working on step {i}");
2476 |
2477 | // Wait with cancellation support
2478 | await Task.Delay(100, cancellationToken);
2479 | }
2480 | }
2481 |
2482 | // Example of cancelling a web request
2483 | public async Task DownloadWithTimeoutAsync(string url, TimeSpan timeout)
2484 | {
2485 | using CancellationTokenSource cts = new CancellationTokenSource(timeout);
2486 | using HttpClient client = new HttpClient();
2487 |
2488 | try
2489 | {
2490 | return await client.GetStringAsync(url, cts.Token);
2491 | }
2492 | catch (OperationCanceledException)
2493 | {
2494 | throw new TimeoutException($"The request to {url} timed out after {timeout.TotalSeconds} seconds");
2495 | }
2496 | }
2497 | ```
2498 |
2499 | ## ValueTask and async streams (C# 8.0+)
2500 |
2501 | `ValueTask` and async streams are newer features that enhance async programming in specific scenarios by improving performance and extending the asynchronous model to sequences.
2502 |
2503 | ```csharp
2504 | // ValueTask for potentially synchronous, high-performance scenarios
2505 | public ValueTask GetValueAsync(bool alreadyCached, int cachedValue)
2506 | {
2507 | if (alreadyCached)
2508 | {
2509 | // Return immediately without allocating a Task
2510 | return new ValueTask(cachedValue);
2511 | }
2512 |
2513 | // Fall back to async path
2514 | return new ValueTask(GetValueSlowlyAsync());
2515 | }
2516 |
2517 | private async Task GetValueSlowlyAsync()
2518 | {
2519 | await Task.Delay(100);
2520 | return 42;
2521 | }
2522 |
2523 | // Async streams with IAsyncEnumerable
2524 | public async IAsyncEnumerable GetDataStreamAsync(
2525 | [EnumeratorCancellation] CancellationToken cancellationToken = default)
2526 | {
2527 | for (int i = 0; i < 10; i++)
2528 | {
2529 | // Check cancellation
2530 | cancellationToken.ThrowIfCancellationRequested();
2531 |
2532 | // Simulate work
2533 | await Task.Delay(100, cancellationToken);
2534 |
2535 | // Yield a result
2536 | yield return $"Item {i}";
2537 | }
2538 | }
2539 |
2540 | // Consuming async streams
2541 | public async Task ConsumeAsyncStreamAsync()
2542 | {
2543 | await foreach (string item in GetDataStreamAsync())
2544 | {
2545 | Console.WriteLine(item);
2546 | }
2547 |
2548 | // With cancellation
2549 | using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
2550 |
2551 | try
2552 | {
2553 | await foreach (string item in GetDataStreamAsync().WithCancellation(cts.Token))
2554 | {
2555 | Console.WriteLine(item);
2556 | }
2557 | }
2558 | catch (OperationCanceledException)
2559 | {
2560 | Console.WriteLine("Stream processing was canceled");
2561 | }
2562 | }
2563 | ```
2564 |
2565 |
2566 |
2567 | # Code organization
2568 |
2569 | How you organize your C# code significantly impacts its readability, maintainability, and extensibility. Well-organized code follows consistent patterns, respects separation of concerns, and leverages language features to create clear boundaries between components.
2570 |
2571 | Modern C# includes numerous features that help enforce good code organization principles.
2572 |
2573 | ## Namespaces
2574 |
2575 | Namespaces in C# provide a way to organize code into logical groups and prevent naming conflicts. They create a hierarchical structure for your types (yes, they can be nested), making large codebases more manageable and allowing for intuitive navigation.
2576 |
2577 | ```csharp
2578 | // Namespace declaration
2579 | namespace MyApplication.DataAccess
2580 | {
2581 | public class Database
2582 | {
2583 | // Class implementation
2584 | }
2585 | }
2586 |
2587 | // File-scoped namespaces (C# 10.0+)
2588 | namespace MyApplication.Business;
2589 |
2590 | public class Customer
2591 | {
2592 | // Class implementation
2593 | }
2594 | ```
2595 |
2596 | ### Namespace organization best practices:
2597 |
2598 | 1. Structure namespaces to reflect logical organization, not folder structure
2599 | 2. Consider using a company or project name as the top-level namespace
2600 | 3. Group related functionality within namespace hierarchies
2601 | 4. Avoid deeply nested namespaces (more than 3-4 levels)
2602 | 5. Don't put different functionality in the same namespace just because they're in the same assembly
2603 |
2604 | ## Using directives
2605 |
2606 | Using directives specify which namespaces are referenced in your code, allowing you to use types from those namespaces without fully qualifying them. They improve code readability by reducing repetition.
2607 |
2608 | ```csharp
2609 | // Import namespace
2610 | using System;
2611 | using System.Collections.Generic;
2612 | using System.Linq;
2613 |
2614 | // Alias namespace
2615 | using IO = System.IO;
2616 |
2617 | // Static imports (C# 6.0+)
2618 | using static System.Math;
2619 | using static System.Console;
2620 |
2621 | // Combined with global using (C# 10.0+)
2622 | global using System;
2623 | global using System.Collections.Generic;
2624 |
2625 | // Using aliases for types (C# 12+)
2626 | using Point = (int X, int Y);
2627 | using CustomerName = string;
2628 | using RGB = (byte Red, byte Green, byte Blue);
2629 |
2630 | // Multiple global using directives in a single file (GlobalUsings.cs)
2631 | global using System.Collections.Generic;
2632 | global using System.Linq;
2633 | global using System.Threading;
2634 | global using System.Threading.Tasks;
2635 | ```
2636 |
2637 | ### Using directives best practices:
2638 |
2639 | 1. Place `using` directives at the top of the file, outside of namespace declarations
2640 | 2. Order `using` directives alphabetically, with System namespaces first
2641 | 3. Use `global using` for commonly used namespaces across many files
2642 | 4. Use static imports sparingly and only for frequently used static members
2643 | 5. Consider using aliases to improve readability or avoid ambiguity
2644 |
2645 | ## File-scoped types (C# 11+)
2646 |
2647 | File-scoped types are accessible only within the file where they're defined, allowing you to create helper classes, interfaces, or enums that are truly private to their implementation file. This reduces the public API surface and prevents accidental usage.
2648 |
2649 | ```csharp
2650 | // File: UserService.cs
2651 | namespace MyApp.Services;
2652 |
2653 | // File-scoped type - only accessible within this file
2654 | file class UserValidator
2655 | {
2656 | public bool Validate(User user) => !string.IsNullOrEmpty(user.Name);
2657 | }
2658 |
2659 | // Public class that can use the file-scoped type
2660 | public class UserService
2661 | {
2662 | private readonly UserValidator _validator = new();
2663 |
2664 | public bool RegisterUser(User user)
2665 | {
2666 | if (!_validator.Validate(user))
2667 | return false;
2668 |
2669 | // Register user logic
2670 | return true;
2671 | }
2672 | }
2673 |
2674 | // File: Utils.cs
2675 | file static class StringExtensions // Only visible in this file
2676 | {
2677 | public static bool IsValidEmail(this string email) =>
2678 | email.Contains('@') && email.Contains('.');
2679 | }
2680 | ```
2681 |
2682 | ## Partial classes
2683 |
2684 | Partial classes allow splitting a class, struct, or interface definition across multiple files. This can be useful for separating generated code from hand-written code or dividing large classes by functionality.
2685 |
2686 | ```csharp
2687 | // File: Customer.cs
2688 | public partial class Customer
2689 | {
2690 | public int Id { get; set; }
2691 | public string Name { get; set; }
2692 |
2693 | public void Save()
2694 | {
2695 | // Implementation
2696 | }
2697 | }
2698 |
2699 | // File: Customer.Orders.cs
2700 | public partial class Customer
2701 | {
2702 | public List Orders { get; } = new List();
2703 |
2704 | public void AddOrder(Order order)
2705 | {
2706 | Orders.Add(order);
2707 | }
2708 | }
2709 |
2710 | // File: Customer.Validation.cs
2711 | public partial class Customer
2712 | {
2713 | public bool Validate()
2714 | {
2715 | // Validation logic
2716 | return !string.IsNullOrEmpty(Name);
2717 | }
2718 | }
2719 | ```
2720 |
2721 | ## Access modifiers
2722 |
2723 | Access modifiers control the visibility and accessibility of types and type members. Properly applied access modifiers create clear boundaries and enforce encapsulation.
2724 |
2725 | ```csharp
2726 | // Access modifiers
2727 | public class AccessModifierDemo
2728 | {
2729 | public int PublicField; // Accessible from anywhere
2730 | private int _privateField; // Accessible only within the class
2731 | protected int ProtectedField; // Accessible within the class and derived classes
2732 | internal int InternalField; // Accessible within the same assembly
2733 | protected internal int ProtectedInternalField; // Accessible within the same assembly or derived classes
2734 | private protected int PrivateProtectedField; // Accessible within the same assembly from derived classes
2735 | }
2736 | ```
2737 |
2738 | ### Access modifier guidelines:
2739 |
2740 | 1. **public**: Use for types and members that form your public API
2741 | 2. **internal**: Use for types and members that should be available within your assembly but not externally
2742 | 3. **private**: Use for implementation details inside a class that shouldn't be accessible elsewhere
2743 | 4. **protected**: Use for members that should be accessible to derived classes for customization
2744 | 5. **protected internal**: Use when both derived classes and code within the assembly need access
2745 | 6. **private protected**: Use when derived classes within the same assembly (but not external ones) need access
2746 |
2747 | ## Properties and indexers
2748 |
2749 | Properties provide a way to expose fields while adding validation, computed values, or extra logic during access. They're a fundamental part of C# that enables proper encapsulation in object-oriented design.
2750 |
2751 | ```csharp
2752 | public class PropertyDemo
2753 | {
2754 | // Auto-implemented property
2755 | public string Name { get; set; }
2756 |
2757 | // Property with backing field
2758 | private int _age;
2759 | public int Age
2760 | {
2761 | get { return _age; }
2762 | set { _age = value < 0 ? 0 : value; }
2763 | }
2764 |
2765 | // Expression-bodied property (C# 6.0+)
2766 | public bool IsAdult => Age >= 18;
2767 |
2768 | // Property with different access levels
2769 | public string Email { get; private set; }
2770 |
2771 | // Init-only property (C# 9.0+)
2772 | public string Id { get; init; }
2773 |
2774 | // Required property (C# 11.0+)
2775 | public required string Username { get; set; }
2776 |
2777 | // Indexers
2778 | private string[] _data = new string[10];
2779 |
2780 | public string this[int index]
2781 | {
2782 | get => _data[index];
2783 | set => _data[index] = value;
2784 | }
2785 |
2786 | public string this[string key]
2787 | {
2788 | get => key switch
2789 | {
2790 | "first" => _data[0],
2791 | "last" => _data[^1], // The “Hat” Operator (^) is used as a prefix to count indexes starting from the end of a list.
2792 | _ => throw new ArgumentException("Invalid key")
2793 | };
2794 | }
2795 | }
2796 | ```
2797 |
2798 | **Additional resources:**
2799 |
2800 | - [C# coding conventions (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
2801 | - [File-scoped namespaces (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/file-scoped-namespaces)
2802 | - [Access modifiers (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/access-modifiers)
2803 | - [Properties (Microsoft Docs)](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties)
2804 |
2805 |
2806 |
2807 | ## Wrap Up
2808 |
2809 | If you think the cheatsheet can be improved, please open a PR with any updates and submit any issues. Also, I will continue to improve this, so you should star this repository, too.
2810 |
2811 | ## Contribution
2812 |
2813 | - Open a pull request with improvements
2814 | - Discuss ideas in issues
2815 | - Spread the word
2816 |
2817 | ## License
2818 |
2819 | [](https://opensource.org/licenses/Apache-2.0)
2820 |
2821 | ## Author
2822 |
2823 | [Dr. Milan Milanović](https://milan.milanovic.org) - CTO at [3MD](https://3mdinc.com) and Microsoft MVP for Developer Technologies.
--------------------------------------------------------------------------------
/csharp-mindmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/milanm/csharp-cheatsheet/0b5c131ebbcd0126bf6f770f02c4163338936daf/csharp-mindmap.png
--------------------------------------------------------------------------------
/patreon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/milanm/csharp-cheatsheet/0b5c131ebbcd0126bf6f770f02c4163338936daf/patreon.png
--------------------------------------------------------------------------------