├── 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 | ![C# Mind map](csharp-mindmap.png) 18 | 19 | ## Support my work 20 | 21 | If you find this repository helpful, consider supporting me on Patreon: 22 | 23 | [![Patreon](patreon.png)](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 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](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 --------------------------------------------------------------------------------