├── .gitignore ├── C#Handbook.pdf ├── 10_miscellaneous.md ├── cSpell.json ├── configurations ├── .EditorConfig └── Settings.StyleCop ├── 7_objectLifetime.md ├── 8_managingChange.md ├── 0_overview.md ├── 5_safeProgramming.md ├── 9_documentation.md ├── 6_errorHandling.md ├── readme.md ├── 1_naming.md ├── 2_formatting.md ├── 4_design.md └── 3_usage.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /C#Handbook.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvonballmo/CSharpHandbook/HEAD/C#Handbook.pdf -------------------------------------------------------------------------------- /10_miscellaneous.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | 3 | ## Generated Code 4 | 5 | * Do not commit manual changes to generated code. Temporary changes for debugging are fine, but always be aware that your changes will be overwritten when code is re-generated. 6 | 7 | ## Configuration and File System 8 | 9 | * An assembly should not assume its location. 10 | * Do not use the registry to store application information; save user settings to a user-accessible file instead. 11 | 12 | ## Logging 13 | 14 | * Do not log directly to any output (e.g. a file or the console). 15 | * Avoid logging directly to global or static constructs. 16 | * Instead, inject an interface into the method where needed (e.g. `ILogger`). 17 | 18 | ### `ValueTask` 19 | 20 | The `ValueTask` is also a performance-improvement feature to improve task-handling when there are a _lot_ of tasks. Do not use these unless you have performance-sensitive code. 21 | 22 | * Use `ValueTask` where results will usually be returned synchronously. 23 | * Use `ValueTask` when Tasks cannot be cached and you memory-usage is a problem. 24 | * Do not expose `ValueTask` in public APIs. 25 | 26 | 27 | -------------------------------------------------------------------------------- /cSpell.json: -------------------------------------------------------------------------------- 1 | // cSpell Settings 2 | { 3 | // Version of the setting file. Always 0.1 4 | "version": "0.1", 5 | // language - current active spelling language 6 | "language": "en", 7 | // words - list of words to be always considered correct 8 | "words": [ 9 | "localizable", 10 | "Postgre", 11 | "reticle", 12 | "readonly", 13 | "Linq", 14 | "struct", 15 | "nameof", 16 | "Encodo", 17 | "Quino", 18 | "Winform", 19 | "serializable", 20 | "structs", 21 | "MVVM", 22 | "endregion", 23 | "YAGNI", 24 | "Overdesign", 25 | "Silverlight", 26 | "Lippert", 27 | "Cyclomatic", 28 | "parameterized", 29 | "unmanaged", 30 | "Commutativity", 31 | "ifdef", 32 | "Punchclock", 33 | "Prepend", 34 | "Roadmap" 35 | ], 36 | // flagWords - list of words to be always considered incorrect 37 | // This is useful for offensive words and common spelling errors. 38 | // For example "hte" should be "the" 39 | "flagWords": [ 40 | "hte" 41 | ] 42 | } -------------------------------------------------------------------------------- /configurations/.EditorConfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | insert_final_newline = false 11 | 12 | [*.cs] 13 | dotnet_sort_system_directives_first = true 14 | dotnet_separate_import_directive_groups = false 15 | 16 | # this. preferences 17 | dotnet_style_qualification_for_field = false:warning 18 | dotnet_style_qualification_for_property = false:warning 19 | dotnet_style_qualification_for_method = false:warning 20 | dotnet_style_qualification_for_event = false:warning 21 | 22 | # Language keywords vs BCL types preferences 23 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 24 | dotnet_style_predefined_type_for_member_access = true:warning 25 | 26 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning 27 | dotnet_style_readonly_field = true:suggestion 28 | 29 | # New line preferences 30 | csharp_new_line_before_open_brace = all 31 | csharp_new_line_before_else = true 32 | csharp_new_line_before_catch = true 33 | csharp_new_line_before_finally = true 34 | csharp_new_line_before_members_in_object_initializers = true 35 | csharp_new_line_before_members_in_anonymous_types = true 36 | csharp_new_line_between_query_expression_clauses = true 37 | 38 | # Indentation preferences 39 | csharp_indent_case_contents = true 40 | csharp_indent_switch_labels = true 41 | csharp_indent_labels = flush_left -------------------------------------------------------------------------------- /7_objectLifetime.md: -------------------------------------------------------------------------------- 1 | # Object Lifetime 2 | 3 | * Use the `using` statement to precisely delineate the lifetime of `IDisposable` objects. 4 | * Use `try`/`finally` blocks to manage other state (e.g. executing a matching `EndUpdate()` for a `BeginUpdate()`) 5 | * Do not set local variables to `null`. They will be automatically de-referenced and cleaned up. 6 | 7 | ## `IDisposable` 8 | 9 | * Implement `IDisposable` if your object uses disposable objects or system resources. 10 | * Be careful about making your interfaces `IDisposable`; that pattern is a leaky abstraction and can be quite invasive. 11 | * Implement `Dispose()` so that it can be safely called multiple times (see example below). 12 | * Don't hide `Dispose()` in an explicit implementation. It confuses callers unnecessarily. If there is a more appropriate domain-specific name for `Dispose`, feel free to make a synonym. 13 | 14 | ## `Finalize` 15 | 16 | * There are performance penalties for implementing `Finalize`. Implement it only if you actually have costly external resources. 17 | * Call the `GC.SuppressFinalize` method from `Dispose` to prevent `Finalize` from being executed if `Dispose()` has already been called. 18 | * `Finalize` should include only calls to `Dispose` and `base.Finalize()`. 19 | * `Finalize` should never be `public` 20 | 21 | ## Destructors 22 | 23 | * Avoid using destructors because they incur a performance penalty in the garbage collector. 24 | * Do not access other object references inside the destructor as those objects may already have been garbage-collected (there is no guaranteed order-of-destruction in the IL or .NET runtime). 25 | 26 | ## Best Practices 27 | 28 | The following class expects the caller to use a non-standard pattern to avoid holding open a file handle. 29 | 30 | ```csharp 31 | public class Weapon 32 | { 33 | public Load(string file) 34 | { 35 | _file = File.OpenRead(file); 36 | } 37 | 38 | // Aim(), Fire(), etc. 39 | 40 | public EjectShell() 41 | { 42 | _file.Dispose(); 43 | } 44 | 45 | private File _file; 46 | } 47 | ``` 48 | Instead, make `Weapon` disposable. The following implementation follows the recommended pattern. R# can help you create this pattern. 49 | 50 | > _Note that `_file` is set to `null` so that `Dispose` can be called multiple notes, not to clear the reference._ 51 | 52 | ```csharp 53 | public class Weapon : IDisposable 54 | { 55 | public Weapon(string file) 56 | { 57 | _file = File.OpenRead(file); 58 | } 59 | 60 | // Aim(), Fire(), etc. 61 | 62 | public Dispose() 63 | { 64 | Dispose(true); 65 | GC.SuppressFinalize(this); 66 | } 67 | 68 | protected virtual void Dispose(bool disposing) 69 | { 70 | if (disposing) 71 | { 72 | if (_file != null) 73 | { 74 | _file.Dispose(); 75 | _file = null; 76 | } 77 | } 78 | } 79 | 80 | private File _file; 81 | } 82 | ``` 83 | 84 | Using the standard pattern, R# and Code Analysis will detect when an `IDisposable` object is not disposed of properly. -------------------------------------------------------------------------------- /8_managingChange.md: -------------------------------------------------------------------------------- 1 | # Managing Change 2 | 3 | ## Modifying Interfaces 4 | 5 | * Avoid introducing breaking changes. Wherever possible, retain old overloads/names for one extra major version. 6 | * Document breaking changes in the release notes, including upgrade instructions. 7 | 8 | ## Marking Members as Obsolete 9 | 10 | Include the version number in the message. For example: 11 | 12 | ```csharp 13 | [Obsolete(""Since 4.0: Use IMetaElementSearchAspect instead.")] 14 | ``` 15 | 16 | ## Refactoring Names and Signatures 17 | 18 | The whole point of having an agile process and lots of automated tests is to be able to quickly improve designs and accommodate new functionality. Very often, this involves quite aggressive refactoring. Aggressive refactoring means that code that compiled with a previous version of framework may no longer compile with the latest version. 19 | In each case where such a change is to be made, the following points must be considered: 20 | 21 | * Will customer code be affected? A “customer” in this case is _anyone_ who consumes your code: both internal and external developers. 22 | * Do you have access to all uses of the code in order to be able to update it? 23 | * Is all of the code that depends on code integrated at least daily in order to catch any usages you might have forgotten? 24 | * Is the area you are changing covered by automated tests? 25 | * Just how important is the change? 26 | * Can you make the change in a non-destructive manner by introducing an optional parameter or an overload instead? 27 | * Are you willing to introduce a compile error into customer code? 28 | 29 | With an agile methodology, the answer to the question “should you?” is quite often “yes”. Cleaner, tighter and more logical code is more maintainable and self-explanatory code. 30 | 31 | ## Roadmap for Safe Obsolescence 32 | 33 | If the change is localized, you can of course make it right away. If not, you may need to go the long route described below: 34 | 35 | 1. Mark the old version of the feature as obsolete. 36 | 2. Create the new feature with a different name so that it does not collide with the existing feature. 37 | 3. Rewrite the code so that the main code path uses the new feature but also incorporates the old version as well. 38 | 4. Add an issue to complete the next stage of refactoring in the next release. 39 | 5. Make and distribute a point release. 40 | 6. In the next release, remove the obsolete feature and all handling for it. 41 | 7. Rename the new feature to the desired name but retain a copy with the temporary name as well, marking it as obsolete. 42 | 8. Update the issue to indicate that it should be completed in the next release. 43 | 9. Make and distribute a point release. 44 | 10. In the next release, remove the obsolete version with the temporary name and the refactoring is complete. 45 | 11. Close the issue. 46 | 47 | Because the steps outlined above require the patience of a saint, they should really only be used for features that absolutely _must_ be refactored but that absolutely _cannot_ break customer code. In all other cases, refactor away and make sure that repair instructions for the compile error are included in the release notes. -------------------------------------------------------------------------------- /0_overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ## Terminology 4 | 5 | | Term | Definition 6 | | --- | --- 7 | | IDE | Integrated Development Environment 8 | | VS | Microsoft Visual Studio 9 | | R# | JetBrains ReSharper 10 | | AJAX | Asynchronous JavaScript and XML 11 | | API | Application Programming Interface 12 | | DRY | Don’t Repeat Yourself 13 | | YAGNI | You Ain't Gonna Need It 14 | 15 | ## History 16 | 17 | This document started out as a Microsoft Word document and was originally published at CodePlex in 2008 by [Encodo Systems AG](http://encodo.com). It has been updated a few times over the years, but not nearly often enough to keep pace with changes. 18 | 19 | This most recent version reflects a conversion to Markdown format and storage in a Git repository to improve version-tracking, collaboration and updates. 20 | 21 | It is also a complete overhaul in both content and structure to provide maximum benefit to various readers. It is also now maintained by Marco von Ballmoos (partner and senior developer at [Encodo Systems AG](http://encodo.com)) instead of by Encodo itself. 22 | 23 | ## Versions 24 | 25 | |Version | Date | Author | Comments 26 | | --- | --- | --- | --- 27 | | 7.0.0 | 29.04.2017 | MvB | Converted the manual from Microsoft Word to Markdown format; rewrote most chapters; removed redundancy; upgraded all styles/patterns from C# 3.5 to C# 7; matched version number to language-version number 28 | | 2.0.0 | Unreleased | MvB | Updated text throughout the “1 − General” and “2 − Design Guide” sections; added “7.17 − Using System.Linq”, “7.29 − Refactoring Names and Signatures” and “7.30 − Loose vs. Tight Coupling” best practices; 29 | | 1.5.3 | Unreleased | MvB | Added reference and notes from “The Little Manual of API Design”; added section “7.15 – Using Partial Classes”. 30 | | 1.5.2 | 19.10.2009 | MvB | Expanded “8.1 – Documentation” with examples; added more tips to the “2.3 – Interfaces vs. Abstract Classes” section; added “7.21 – Restricting Access with Interfaces”; added “5.3.7 – Extension Methods” and “7.18 – Using Extension Methods”. 31 | | 1.5.1 | 24.10.2008 | MvB | Incorporated feedback from the forums at the MSDN Code Gallery. 32 | | 1.5 | 20.05.2008 | MvB | Updated line-breaking section; added more tips for generic methods; added tips for naming delegates and delegate parameters; added rules for object and array initializers; added rules and best practices for return statements; added tips for formatting complex conditions; added section on formatting switch statements. 33 | | 1.4 | 18.04.2008 | MvB | Updated formatting for code examples; added section on using the var keyword; improved section on choosing names; added naming conventions for lambda expressions; added examples for formatting methods; re-organized error handling/exceptions section; updated formatting. 34 | | 1.3 | 31.03.2008 | MvB | Added more tips for documentation; added white-space rules for regions; expanded rules for line-breaking; updated event naming section. 35 | | 1.2 | 07.03.2008 | MvB | Change to empty methods; added conditional compilation section; updated section on comments; made some examples customer-neutral; fixed some syntax-highlighting; reorganized language elements. 36 | | 1.1 | 06.02.2008 | MvB | Updated sections on error handling and naming 37 | | 1.0 | 28.01.2008 | MvB | First Draft 38 | | 0.1 | 03.12.2007 | MvB | Document Created 39 | 40 | ## Referenced Documents 41 | 42 | The style and formatting guidelines draw mostly from in-house programming experience. Most of the following documents were more heavily used in prior versions. References have been included for completeness. 43 | 44 | | Date | Title | Authors | Version 45 | | --- | --- | --- | --- 46 | | 13.04.2017 | [Patterns and Practices in C# 7](https://www.infoq.com/articles/Patterns-Practices-CSharp-7) | Jonathan Allen | 47 | | 20.07.2015 | [Microsoft C# Coding Conventions](https://msdn.microsoft.com/en-us/library/ff926074.aspx) | Microsoft | VS2015 48 | | 01.07.2011 | [IDesign C# Coding Standards](https://www.scribd.com/document/236016479/IDesign-C-Coding-Standard-2-4) | Juval Lowy | 2.4 49 | | 19.05.2011 | [Optional argument corner cases, part four](http://blogs.msdn.com/b/ericlippert/archive/2011/05/19/optional-argument-corner-cases-part-four.aspx) | Eric Lippert | 50 | | 01.11.2008 | [Microsoft Framework Design Guidelines](https://msdn.microsoft.com/en-us/library/ms229042(v=vs.110).aspx) | Krzysztof Cwalina and Brad Abrams | 2.0 51 | | 19.06.2008 | [The Little Manual of API Design](http://www4.in.tum.de/~blanchet/api-design.pdf) (PDF) | Jasmin Blanchette | 52 | | 19.05.2005 | [Coding Standard: C#](http://www.sourceformat.com/pdf/cs-coding-standard-philips.pdf) (PDF) | Philips Medical Systems | 1.3 53 | | 26.01.2005 | [Microsoft Internal Coding Guidelines](https://blogs.msdn.microsoft.com/brada/2005/01/26/internal-coding-guidelines/) | Brad Abrams | 2.0 -------------------------------------------------------------------------------- /configurations/Settings.StyleCop: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | deserializing 5 | dotnet 6 | Encodo 7 | finalizer 8 | Hangfire 9 | json 10 | jsonp 11 | migratable 12 | mixin 13 | nullable 14 | pauser 15 | PostgreSql 16 | Punchclock 17 | Quino 18 | remoting 19 | serilog 20 | untyped 21 | Winform 22 | wrappable 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | False 31 | 32 | 33 | 34 | 35 | False 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | False 46 | 47 | 48 | 49 | 50 | False 51 | 52 | 53 | 54 | 55 | False 56 | 57 | 58 | 59 | 60 | False 61 | 62 | 63 | 64 | 65 | False 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | False 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | False 86 | 87 | 88 | 89 | 90 | False 91 | 92 | 93 | 94 | 95 | False 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | False 106 | 107 | 108 | 109 | 110 | False 111 | 112 | 113 | 114 | 115 | False 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | False 126 | 127 | 128 | 129 | 130 | True 131 | True 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /5_safeProgramming.md: -------------------------------------------------------------------------------- 1 | # Safe Programming 2 | 3 | * Use static typing wherever possible. 4 | 5 | ## Be Functional 6 | 7 | * Make data immutable wherever possible. 8 | * Make methods pure wherever possible. 9 | 10 | ## Avoid `null` references 11 | 12 | * Make references non-nullable wherever possible. 13 | * Use the `[NotNull]` attribute for parameters, fields and results. Enforce it with a runtime check. 14 | * Always test parameters, local variables and fields that can be `null`. 15 | 16 | Instead of allowing `null` for a parameter, avoid null-checks with a null implementation. 17 | 18 | ```csharp 19 | interface ILogger 20 | { 21 | bool Log(string message); 22 | } 23 | 24 | class NullLogger : ILogger 25 | { 26 | void Log(string message) 27 | { 28 | // NOP 29 | } 30 | } 31 | ``` 32 | 33 | ## Local variables 34 | 35 | * Do not re-use local variable names, even though the scoping rules are well-defined and allow it. This prevents surprising effects when the variable in the inner scope is removed and the code continues to compile because the variable in the outer scope is still valid. 36 | * Do not modify a variable with a prefix or suffix operator more than once in an expression. The following statement is not allowed: 37 | ```csharp 38 | items[propIndex++] = ++propIndex; 39 | ``` 40 | 41 | ## Side Effects 42 | 43 | A side effect is a change in an object as a result of reading a property or calling a method that causes the result of the property or method to be different when called again. 44 | 45 | * Prefer pure methods. 46 | * Void methods have side effects by definition. 47 | * Writing a property must cause a side effect. 48 | * Reading a property should not cause a side effect. An exception is lazy-initialization to cache the result. 49 | * Avoid writing methods that return results _and_ cause side effects. An exception is lazy-initialization to cache the result. 50 | 51 | ## ”Access to Modified Closure” 52 | 53 | `IEnumerable` sequences are evaluated lazily. ReSharper will warn of multiple enumeration. 54 | 55 | You can accidentally change the value of a captured variable before the sequence is evaluated. Since _ReSharper_ will complain about this behavior even when it does not cause unwanted side-effects, it is important to understand which cases are actually problematic. 56 | 57 | ```csharp 58 | var data = new[] { "foo", "bar", "bla" }; 59 | var otherData = new[] { "bla", "blu" }; 60 | var overlapData = new List(); 61 | 62 | foreach (var d in data) 63 | { 64 | if (otherData.Where(od => od == d).Any()) 65 | { 66 | overlapData.Add(d); 67 | } 68 | } 69 | 70 | Assert.That(overlapData.Count, Is.EqualTo(1)); // "bla" 71 | ``` 72 | 73 | The reference to the variable `d` will be flagged by _ReSharper_ and marked as an _“access to a modified closure”_. This indicates that a variable referenced—or “captured”—by the lambda expression—closure—will have the last value assigned to it rather than the value that was assigned to it when the lambda was created. 74 | 75 | In the example above, the lambda is created with the first value in the sequence, but since we only use the lambda once, and then always before the variable has been changed, we don’t have to worry about side-effects. _ReSharper_ can only detect that a variable referenced in a closure is being changed within its scope. 76 | 77 | Even though there isn’t a problem in this case, rewrite the `foreach`-statement above as follows to eliminate the _access to modified closure_ warning. 78 | 79 | ```csharp 80 | var data = new[] { "foo", "bar", "bla" }; 81 | var otherData = new[] { "bla", "blu" }; 82 | var overlapData = data.Where(d => otherData.Where(od => od == d).Any()).ToList(); 83 | 84 | Assert.That(overlapData.Count, Is.EqualTo(1)); // "bla" 85 | ``` 86 | 87 | Finally, use library functionality wherever possible. In this case, we should use `Intersect` to calculate the overlap (intersection). 88 | 89 | ```csharp 90 | var data = new[] { "foo", "bar", "bla" }; 91 | var otherData = new[] { "bla", "blu" }; 92 | var overlapData = data.Intersect(otherData).ToList(); 93 | 94 | Assert.That(overlapData.Count, Is.EqualTo(1)); // "bla" 95 | ``` 96 | 97 | Remember to be aware of how items are compared. The `Intersects` method above compares using `Equals`, not reference-equality. 98 | 99 | The following example does not yield the expected result: 100 | 101 | ```csharp 102 | var data = new[] { "foo", "bar", "bla" }; 103 | 104 | var threshold = 2; 105 | var twoLetterWords = data.Where(d => d.Length == threshold); 106 | 107 | threshold = 3; 108 | var threeLetterWords = data.Where(d => d.Length == threshold); 109 | 110 | Assert.That(twoLetterWords.Count(), Is.EqualTo(0)); 111 | Assert.That(threeLetterWords.Count(), Is.EqualTo(3)); 112 | ``` 113 | 114 | The lambda in `twoLetterWords` _references_ `threshold`, which is then changed before the lambda is evaluated with `Count()`. There is nothing wrong with this code, but the results can be surprising. Use `ToList()` to evaluate the lambda in `twoLetterWords` _before_ the threshold is changed. 115 | 116 | ```csharp 117 | var data = new[] { "foo", "bar", "bla" }; 118 | var threshold = 2; 119 | var twoLetterWords = data.Where(d => d.Length == threshold).ToList(); 120 | 121 | threshold = 3; 122 | var threeLetterWords = data.Where(d => d.Length == threshold); 123 | 124 | Assert.That(twoLetterWords.Count(), Is.EqualTo(0)); 125 | Assert.That(threeLetterWords.Count(), Is.EqualTo(3)); 126 | ``` 127 | 128 | ## "Collection was modified; enumeration operation may not execute." 129 | 130 | Changing a sequence during enumeration causes a runtime error. 131 | 132 | The following code will fail whenever `data` contains an element for which `IsEmpty` returns `true`. 133 | 134 | ```csharp 135 | foreach (var d in data.Where(d => d.IsEmpty)) 136 | { 137 | data.Remove(d); 138 | } 139 | ``` 140 | 141 | To avoid this problem, use an in-memory copy of the sequence instead. A good practice is to use `ToList()` to create the copy and to call it in the `foreach` statement so that it's clear why it's being used. 142 | 143 | ```csharp 144 | foreach (var d in data.Where(d => d.IsEmpty).ToList()) 145 | { 146 | data.Remove(d); 147 | } 148 | ``` 149 | 150 | ## "Possible multiple enumeration of IEnumerable" 151 | 152 | Suppose, in the example above, that we also want to know how many elements were empty. Let's start by extracting `emptyElements` to a variable. 153 | 154 | ```csharp 155 | var emptyElements = data.Where(d => d.IsEmpty); 156 | foreach (var d in emptyElements.ToList()) 157 | { 158 | data.Remove(d); 159 | } 160 | 161 | return emptyElements.Count(); 162 | ``` 163 | 164 | Since `emptyElements` is evaluated lazily, the call to `Count()` to return the result will evaluate the iterator again, producing a sequence that is now empty—because the `foreach`-statement removed them all from data. The code above will always return zero. 165 | 166 | A more critical look at the code above would discover that the `emptyElements` iterator is triggered twice: by the call to `ToList()` and `Count()` (ReSharper will helpfully indicate this with an inspection). Both `ToList()` and `Count()` logically iterate the entire sequence. 167 | 168 | To fix the problem, we lift the call to `ToList()` out of the `foreach` statement and into the variable. 169 | 170 | ```csharp 171 | var emptyElements = data.Where(d => d.IsEmpty).ToList(); 172 | foreach (var d in emptyElements) 173 | { 174 | data.Remove(d); 175 | } 176 | 177 | return emptyElements.Count; 178 | ``` 179 | 180 | We can eliminate the `foreach` by directly re-assigning `data`, as shown below. 181 | 182 | ```csharp 183 | var dataCount = data.Count; 184 | var data = data.Where(d => !d.IsEmpty).ToList(); 185 | 186 | return dataCount – data.Count; 187 | ``` 188 | 189 | The first algorithm is more efficient when the majority of item in `data` are empty. The second algorithm is more efficient when the majority is non-empty. -------------------------------------------------------------------------------- /9_documentation.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## Files 4 | 5 | * Include a `README.md` file at the root of the project that includes the following information: 6 | * Dependencies 7 | * Basic configuration 8 | * Basic command line 9 | * Links to other documentation 10 | * Include a `LICENSE` file at the root of the project that describes licensing restrictions. 11 | 12 | ## Language 13 | 14 | * Use U.S. English spelling and grammar. 15 | * Use full sentences or clauses; do not use lists of keywords or short phrases. 16 | * Use the prepositional possessive for code elements (i.e. write "the value of ``" instead of "``’s value"). 17 | 18 | ## Style 19 | 20 | * An API should document itself. Code documentation can sometimes be very obvious and simple. This indicates to the caller that it really _is_ that simple. 21 | * Document similar members consistently; it’s better to repeat yourself or to use the same structure for all members as long as the documentation is useful for each member. 22 | * Include conceptual documentation for each concept/component to provide an overview and examples of how to use the product. 23 | * Move longer documentation out of the code and into higher-level conceptual documentation or examples. 24 | 25 | ## XML Documentation 26 | 27 | * Include XML documentation to enhance code-completion. 28 | * Document `public` and `protected` elements. 29 | * Do not document `private` or `internal` members. 30 | * Include references to important members from class documentation. 31 | 32 | ### Dependencies 33 | 34 | * Do not introduce dependencies for documentation. 35 | * Do not add `using` statements for documentation; if necessary, include the required namespace in the documentation reference itself. 36 | 37 | ### Tags 38 | 39 | * Format block tags onto separate lines. E.g. ``, ``, `` and `` 40 | * Use `` tags for the keywords `null`, `false` and `true`. 41 | * Use `` tags to refer to properties, methods and classes. 42 | * Use `` and `` tags to refer to method parameters. 43 | * Use the `` tag for method overrides or interface implementations. 44 | * Use a `` section to indicate usage and to link to related members. It’s sometimes good to include references to other types or methods in descriptive sentences in addition to listing them in the `` section. 45 | 46 | ## Examples 47 | 48 | ### Classes 49 | 50 | * An abstract implementation of an interface should use the following form: 51 | ```csharp 52 | /// 53 | /// A base implementation of the interface. 54 | /// 55 | public abstract class MakerBase : IMaker { } 56 | ``` 57 | * The standard (or only) implementation of an interface should use the following form: 58 | ```csharp 59 | /// 60 | /// The standard implementation of the interface. 61 | /// 62 | public class Maker : IMaker { } 63 | ``` 64 | * For one of several implementations, use the following form: 65 | ```csharp 66 | /// 67 | /// An implementation of the interface that works with a 68 | /// Windows service. 69 | /// 70 | public class WindowsServerBasedMaker : IMaker { } 71 | ``` 72 | 73 | ### Methods 74 | 75 | * Document parameters in declaration order. 76 | * Do not document exceptions that are bugs (e.g. `ArgumentNullException`). 77 | * Refer to the first parameter as "given"; subsequent parameter references do not need to be qualified. For example, 78 | ```csharp 79 | /// Gets the value of the given in .` 80 | ``` 81 | * The documentation should indicate which values are acceptable inputs for a parameter (e.g. whether or not it can be `null` or empty (for strings) or the range of acceptable values (for numbers). The example below demonstrates all of these principles: 82 | ```csharp 83 | /// 84 | /// Fills the with random text using the given 85 | /// and . 86 | /// 87 | /// 88 | /// The generator to use to create the random text; cannot be null. 89 | /// 90 | /// 91 | /// The values with which to seed the random generator; cannot be null. 92 | /// 93 | void FillWithRandomText(IRandomGenerator generator, string seedValues); 94 | * For methods that return a `bool`, use the following form: 95 | ```csharp 96 | /// 97 | /// Gets a value indicating whether the value of the given 98 | /// in has changed since it was loaded from or last stored. 99 | /// 100 | /// 101 | /// The object to test; cannot be null. 102 | /// 103 | /// 104 | /// The property to test; cannot be null. 105 | /// 106 | /// 107 | /// true if the value has been modified; otherwise false. 108 | /// 109 | bool ValueModified(object obj, IMetaProperty prop); 110 | ``` 111 | * Exceptions should begin with “If…” as shown in the example below: 112 | ```csharp 113 | /// 114 | /// Gets the as formatted according to its type. 115 | /// 116 | /// 117 | /// The value to format; can be null. 118 | /// 119 | /// 120 | /// Hints indicating context and formatting requirements. 121 | /// 122 | /// 123 | /// The as formatted according to its type. 124 | /// 125 | /// 126 | /// If cannot be formatted. 127 | /// 128 | string FormatValue(object value, CommandTextFormatHints hints); 129 | ``` 130 | 131 | ### Constructors 132 | 133 | * Do not use `` for constructors. 134 | * Documentation for parameters that initialize `public` or `protected` properties should reference that property instead of repeating documentation for properties in the documentation for the initializing parameter in the constructor. 135 | * Parameters assigned to read-only properties should use the form "Initializes the value of ..." 136 | * Parameters assigned to read/write properties should use the form "Sets the initial value of ..." 137 | 138 | ```csharp 139 | class SortOrderAspect 140 | { 141 | /// 142 | /// Initializes a new instance of the class. 143 | /// 144 | /// 145 | /// Initializes the value of . 146 | /// 147 | /// 148 | /// Sets the initial value of . 149 | /// 150 | public SortOrderAspect(IMetaProperty sortOrderProperty, bool enabled) 151 | { 152 | } 153 | 154 | /// 155 | /// Gets the property used to create a manual sorting for objects using the 156 | /// to which this aspect is attached. 157 | /// 158 | IMetaProperty SortOrderProperty { get; private set; } 159 | 160 | /// 161 | /// Gets or sets a value indicating whether this is 162 | /// enabled. 163 | /// 164 | IMetaProperty Enabled { get; set; } 165 | } 166 | ``` 167 | 168 | ### Properties 169 | 170 | * If a property has a non-public setter, do not include the "or sets" part to avoid confusion for public users of the property. 171 | * Include only the `` tag. The `` tag is not needed. 172 | * The documentation for read-only properties must begin with “Gets”. 173 | ```csharp 174 | /// 175 | /// Gets the environment within which the database runs. 176 | /// 177 | IDatabaseEnvironment Environment { get; private set; } 178 | ``` 179 | * The documentation for read/write properties should begin with “Gets or sets”, as follows: 180 | ```csharp 181 | /// 182 | /// Gets or sets the database type. 183 | /// 184 | DatabaseType DatabaseType { get; } 185 | ``` 186 | * Boolean properties should have the following form, formatting the value element as follows: 187 | ```csharp 188 | /// 189 | /// Gets a value indicating whether the database in exists. 190 | /// 191 | bool Exists { get; } 192 | ``` 193 | * For properties with generic names, take care to specify exactly what the property does, rather than writing vague documentation like “gets or sets a value indicating whether this object is enabled”. Tell the user what “enabled” means in the context of the property being documented: 194 | ```csharp 195 | /// 196 | /// Gets or sets a value indicating whether automatic updating of the sort-order 197 | /// is enabled. 198 | /// 199 | bool Enabled { get; } 200 | ``` 201 | 202 | ### Full Example 203 | 204 | The example below includes many of the best practices outlined in the previous sections. It includes ``, `` and several `` tags as well as clearly stating what it does with those parameters and their acceptable values. Finally, it includes extra detail in the `` section instead of the ``. 205 | 206 | ```csharp 207 | /// 208 | /// Copies the entire contents of the given stream to the 209 | /// given stream. 210 | /// 211 | /// The stream from which to copy data; cannot be null. 212 | /// 213 | /// 214 | /// The stream to which to copy data; cannot be null. 215 | /// 216 | /// 217 | /// Uses a 32KB buffer; use the overload to 218 | /// use a different buffer size. 219 | /// 220 | /// 221 | /// If the cannot be read 222 | /// or is not at the head of the stream and cannot perform a seek or if the 223 | /// cannot be written. 224 | public static void CopyTo(this Stream input, Stream output) 225 | ``` -------------------------------------------------------------------------------- /6_errorHandling.md: -------------------------------------------------------------------------------- 1 | 2 | # Error Handling 3 | 4 | ## Strategies 5 | 6 | * Prefer exceptions to return codes. 7 | * Use a return code only where all results are valid. 8 | * Use the `Try*`-pattern (illustrated below) to encapsulate methods that can fail. 9 | * If errors/warnings are expected, then use an `ILogger` or similar construct to record those warnings rather than throwing and multiple catching exceptions. 10 | 11 | ## Terms 12 | 13 | The following definitions are used below: 14 | 15 | * _errors_ are thrown when handling input. An application must _handle_ and _recover_ from these. 16 | * _bugs_ result from programming error. An application _may not_ handle or recover from these. 17 | * An exception is _caught_ with a `catch` block that matches it 18 | * An application _re-throws_ an exception with a naked `throw` statement 19 | * An application _wraps_ an exception by throwing a new exception with the original exception as its inner exception. 20 | * An exception is _handled_ if it is neither re-thrown nor wrapped 21 | * An exception is _logged_ by writing it to a logging handler 22 | 23 | ## Errors 24 | 25 | The following are examples of errors. 26 | 27 | * Timed-out calls over a network 28 | * Storage failure on a database 29 | * Invalid input-file format 30 | * Invalid user input 31 | 32 | ## Bugs 33 | 34 | The following are examples of bugs. 35 | 36 | * The system runs out of memory 37 | * Dereferencing a `null` variable 38 | * An invalid cast 39 | * Any unexpected situation for which the software is not prepared 40 | 41 | The most common exceptions in C# are bugs. 42 | 43 | * `ArgumentException` and descendants 44 | * `NullReferenceException` 45 | * `ClassCastException` 46 | * `OutOfMemoryException` 47 | * `StackOverflowException` 48 | * `InvalidOperationException` 49 | * `AccessViolationException` 50 | * `NotSupportedException` 51 | * `NotImplementedException` 52 | 53 | ## Design-by-Contract 54 | 55 | Use assertions at the beginning of a method to assert preconditions; assert post-conditions where appropriate. 56 | 57 | * Throw `ArgumentNullExceptions` for preconditions and post-conditions. 58 | * Do not use `Debug.Assert`. 59 | * Do not remove constracts in release code unless you can prove a performance issue. 60 | * Throw the exception on the same line as the check, to mirror the formatting of the assertion. 61 | ```csharp 62 | if (connection == null) { throw new ArgumentNullException("connection"); } 63 | ``` 64 | * If the assertion cannot be formulated in code, add a comment describing it instead. 65 | * All methods and properties used to test pre-conditions must have the same visibility as the method being called. 66 | 67 | ## Throwing Exceptions 68 | 69 | * If a member cannot satisfy its post-condition (or, absent a post-condition, fulfill the promise implied in its name or specified in its documentation), it should throw an exception. 70 | * Use standard exceptions. 71 | * Never throw `Exception`. Instead, use one of the standard .NET exceptions when possible. These include `InvalidOperationException`, `NotSupportedException`, `ArgumentException`, `ArgumentNullException` and `ArgumentOutOfRangeException`. 72 | * When using an `ArgumentException` or descendent thereof, make sure that the `ParamName` property is non-empty. 73 | * Your code should not explicitly or implicitly throw `NullReferenceException`, `System.AccessViolationException`, `System.InvalidCastException`, or `System.IndexOutOfRangeException` as these indicate implementation details and possible attack points in your code. These exceptions are to be avoided with pre-conditions and/or argument-checking and should never be documented or accepted as part of the contract of a method. 74 | * Do not throw `StackOverflowException` or `OutOfMemoryException`; these exceptions should only be thrown by the runtime. 75 | * Do not explicitly throw exceptions from `finally` blocks (implicit exceptions are fine). 76 | 77 | ## Catching Exceptions 78 | 79 | * Do not handle bugs. 80 | * Handle only specific, expected errors. 81 | * Always log handled errors. 82 | * Catch and re-throw an error in order to reset the internal state of an object. 83 | * Do not log exceptions to an event handler; this practice separates the point-of-failure from the logging/collection point, increasing the likelihood that an exception is ignored and making debugging very difficult. 84 | 85 | ### Buggy Third-party code 86 | 87 | The _only time_ it is appropriate to handle a bug is when third-party code has a bug _and you are sure that possibly corrupt state will not be re-used_. If the component is long-lived, you should not continue to use it after it has encountered a bug. 88 | 89 | If the component is short-lived, it will be recycled and you can more-or-less safely ignore the bug. In the case of a misbehaving third-party component, catch the specific, known exception that is causing the problem and note it. 90 | 91 | ```csharp 92 | try 93 | { 94 | return new BuggyComponent().GenerateReport(data); 95 | } 96 | catch (NullReferenceException exception) 97 | { 98 | logger.Log("Buggy Component encountered known bug when processing expression.", exception); 99 | } 100 | ``` 101 | 102 | ## Defining Exceptions 103 | 104 | * Re-use exception types wherever possible. 105 | * Do not simply create an exception type for every different error. 106 | * Create a new type only if you want to expose additional properties or catch a specific class of exception. 107 | * Don't expose additional properties unless you're actually going to use them. 108 | * Use a custom exception to hold any information that more completely describes the error (e.g. error codes or structures). 109 | ```csharp 110 | throw new DatabaseException(errorInfo); 111 | ``` 112 | * Custom exceptions should always inherit from `Exception`. 113 | * Custom exceptions should be `public` so that other assemblies can catch them and extract information. 114 | * Avoid constructing complex exception hierarchies; use your own exception base-classes only if you actually will have code that needs to catch all exceptions of a particular sub-class. 115 | * An exception should provide the two standard constructors and should use the given parameter names: 116 | ```csharp 117 | public class ConfigurationException : Exception 118 | { 119 | public ConfigurationException(string message) 120 | : base(message) 121 | { } 122 | 123 | public ConfigurationException(string message, Exception innerException) 124 | : base(message, innerException) 125 | { } 126 | } 127 | ``` 128 | * Only implement serialization for exceptions if you're going to use it. 129 | * If an exception must be able to work across network boundaries, then it must be serializable. 130 | * Do not cause exceptions during the construction of another exception (this sometimes happens when formatting custom messages) as this will subsume the original exception and cause confusion. 131 | 132 | ### Wrapping Exceptions 133 | 134 | * Only catch an exception to wrap it in another exception, log it or set an internal state 135 | * Use an empty throw statement to re-throw the original exception in order to preserve the stack-trace. 136 | * Wrapped exceptions should _always_ include the original exception in order to preserve the stack-trace. 137 | * Lower-level exceptions from an implementation-specific subsection should be caught and wrapped before being allowed to bubble up to implementation-independent code (e.g. when handling database exceptions). 138 | 139 | ## The Try* Pattern 140 | 141 | The Try\* pattern is used by the .NET framework. Generally, Try\*-methods accept an `out` parameter on which to attempt an operation, returning `true` if successful. 142 | 143 | * The parameter should be named “result”. The method should be prefixed with “Try”. 144 | * If you provide a method using the Try* pattern (), you should also provide a non-try-based, exception-throwing variant as well. The exception-throwing variant should call the Try* variant, never the other way around. 145 | ```csharp 146 | public IExpression Parse(string text) 147 | { 148 | if (TryParse(text, var out expression)) 149 | { 150 | return expression; 151 | } 152 | 153 | throw new InvalidOperationException($"The expression [{text}] contains a syntax error."); 154 | } 155 | 156 | public bool TryParse(string text, out IExpression result) 157 | { 158 | if (text == "true") 159 | { 160 | expression = BooleanExpression(true); 161 | 162 | return true; 163 | } 164 | 165 | if (text == "false") 166 | { 167 | expression = BooleanExpression(false); 168 | 169 | return true; 170 | } 171 | 172 | expression = null; 173 | 174 | return false; 175 | } 176 | ``` 177 | 178 | ## Error Messages 179 | 180 | ### Content 181 | 182 | * Use complete sentences that end in a period. 183 | * Do not use question marks or exclamation points. 184 | * Be brief. 185 | * Be specific. 186 | * Provide information on how to prevent the error in the future. 187 | 188 | The following message is too vague and wordy. 189 | 190 | ```csharp 191 | "The file that the application was looking for in order to load the configuration could not be loaded from the user folder." 192 | ``` 193 | 194 | This message leaves a lot of questions open. 195 | 196 | * Does the file exist? 197 | * Is it empty? 198 | * Is it corrupted? 199 | * Can the application read it? 200 | * Where exactly was the application looking? 201 | 202 | Instead, use something like the following. 203 | 204 | ```csharp 205 | "Permission to read file [~/.appConfig] was denied." 206 | ``` 207 | 208 | From this message the problem is clear and the user has many clues as to how to address the issue. 209 | 210 | ### Exceptions 211 | 212 | * Log all exceptions. 213 | * Include technical detail in a separate message in the exception (e.g. stored in the `Data` array with a standard key). 214 | * Lower-level, developer messages should be logged to sources that are available only to those with permission to view lower-level details. 215 | * Applications should avoid showing sensitive information to end-users. This applies especially to web applications, which must never show exception traces in production code. The exact message returned by an exception can vary depending on the permission level of the executing code. 216 | * If data included in a message _could_ be empty, consider wrapping it in braces so that the message is clear even when the data is empty. For example, the following code might produce a confusing error message: 217 | ```csharp 218 | var message = $"The following expression {data} could not be parsed."; 219 | ``` 220 | If `data` is empty, the caller (user or developer) sees only _The following expression could not be parsed._ If the message was instead defined as follows: 221 | ```csharp 222 | var message = $"The following expression [{data}] could not be parsed."; 223 | ``` 224 | Then the caller sees _The following expression [] could not be parsed._ In this case it's more obvious that the expression was empty. 225 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # A C# Developer's Handbook 2 | 3 | The focus of this document is on providing a reference for writing C#. It includes naming, structural and formatting conventions as well as best practices for writing clean, safe and maintainable code. 4 | 5 | Many of the best practices and conventions apply equally well to other languages. 6 | 7 | 1. [Goals](#goals) 8 | 1. [Scope](#scope) 9 | 1. [Environment](#environment) 10 | 1. [Improvements](#improvements) 11 | 1. [In Practice](#in-practice) 12 | 1. [Table of Contents](#table-of-contents) 13 | 1. [Overview](#overview) 14 | 1. [Naming](#naming) 15 | 1. [Formatting](#formatting) 16 | 1. [Usage](#usage) 17 | 1. [Best Practices](#best-practices) 18 | 19 | ## Goals 20 | 21 | This handbook has the following aims and guiding principles: 22 | 23 | * Increase readability and maintainability with a unified style. 24 | * Minimize complexity with proven design principles 25 | * Increase code safety and prevent hard-to-find errors through best practices. 26 | * Maximize effectiveness of coding tools. 27 | * Accommodate IDE- or framework-generated code. 28 | * Provide justifications and examples for rules. 29 | 30 | ## Scope 31 | 32 | This handbook includes: 33 | 34 | * General programming advice and best practices 35 | * General formatting and style recommendations 36 | * C#-specific sections 37 | 38 | ## Environment 39 | 40 | The recommended environment and tools at this time are: 41 | 42 | * _Microsoft Visual Studio 2017_ 43 | * _JetBrains ReSharper 2016.3.2_ 44 | * _StyleCop 4.7_ 45 | * _StyleCop by JetBrains_ extension for ReSharper 46 | * _Cyclomatic Complexity_ extension for ReSharper 47 | * _EditorConfig_ 48 | * _C# 7.0_ 49 | 50 | For older versions of Visual Studio and C#, use what you can or refer to older versions of this handbook. 51 | 52 | ## Improvements 53 | 54 | This document is a work-in-progress. Please speak up or contribute if you think there is something missing. 55 | 56 | * If a guideline is not sufficiently clear, recommend a clearer formulation. 57 | * If you don’t like a guideline, try to get it changed or removed, but don’t just ignore it. Your code reviewer is most likely unaware that you are special and not subject to the rules. 58 | 59 | ## In Practice 60 | 61 | ### Applying the Guidelines 62 | 63 | * Unless otherwise noted, these guidelines are not optional, nor are they up to interpretation. 64 | * A reviewer always has the right to correct mistakes and aberrations, but is not obligated to do so in every review. 65 | * Please note issues with the guidelines during a review. These changes should flow into the guidelines if enough parties agree. 66 | 67 | The handbook defines the goal. Use iterations and refactoring to incrementally bring the code closer to full compliance. 68 | 69 | ### Fixing Problems in Code 70 | 71 | Fix non-conforming code at the earliest opportunity. 72 | 73 | * Fix small and localized errors immediately, in a "cleanup" commit. 74 | * Always use a separate commit to rename or move files. 75 | * Create an issue for larger problems that cannot be fixed quickly. 76 | 77 | ### Working with an IDE 78 | 79 | Modern IDEs generate code; this is very helpful and saves a lot of work. Within reason, the generated code should satisfy the coding guidelines. If the generated code is not under your control, then it's OK to turn a blind eye to style infractions. 80 | 81 | * Configure your IDE to produce code that is as close to the guidelines as possible. StyleCop and ReSharper are an enormous help. 82 | * Update names for visual-design elements and event handlers manually, if needed. 83 | * Write code generators to produce conforming code. 84 | * Do not update code generated by tools not under your control (e.g. *.Designer files). 85 | * Use “Format Document” to reformat auto-generated code. 86 | * Use the highest warning level available (level 4 in Visual Studio) and address all warnings (either by fixing the code or explicitly ignoring them). 87 | 88 | ### Settings files 89 | 90 | This repository includes configuration files that set up the rules outlined in this handbook for _StyleCop_ and _ReSharper_ and _EditorConfig_. 91 | 92 | ## Table of Contents 93 | 94 | ### [Overview](0_overview.md) 95 | 96 | 1. [Terminology](0_overview.md#terminology) 97 | 1. [History](0_overview.md#history) 98 | 1. [References](0_overview.md#referenced-documents) 99 | 100 | ### [Naming](1_naming.md) 101 | 102 | 1. [Characters](1_naming.md#characters) 103 | 1. [Words](1_naming.md#words) 104 | 1. [Semantics](1_naming.md#semantics) 105 | 1. [Case](1_naming.md#case) 106 | 1. [Grouping](1_naming.md#grouping) 107 | 1. [Algorithm](1_naming.md#algorithm) 108 | 1. [Structure](1_naming.md#structure) 109 | 1. [Assemblies](1_naming.md#assemblies) 110 | 1. [Files](1_naming.md#files) 111 | 1. [Namespaces](1_naming.md#namespaces) 112 | 1. [Types](1_naming.md#types) 113 | 1. [Classes](1_naming.md#classes) 114 | 1. [Interfaces](1_naming.md#interfaces) 115 | 1. [`enums`](1_naming.md#enumerations) 116 | 1. [Generic Parameters](1_naming.md#generic-parameters) 117 | 1. [Sequences and Lists](1_naming.md#sequences-and-lists) 118 | 1. [Members](1_naming.md#members) 119 | 1. [Properties](1_naming.md#properties) 120 | 1. [Methods](1_naming.md#methods) 121 | 1. [Extension Methods](1_naming.md#extension-methods) 122 | 1. [Parameters](1_naming.md#parameters) 123 | 1. [Lambdas](1_naming.md#lambdas) 124 | 1. [Events](1_naming.md#events) 125 | 1. [Delegates](1_naming.md#delegates) 126 | 1. [Statements and Expressions](1_naming.md#statements-and-expressions) 127 | 1. [Local Variables](1_naming.md#local-variables) 128 | 1. [Return Values](1_naming.md#return-values) 129 | 1. [Compiler Variables](1_naming.md#compiler-variables) 130 | 131 | ### [Formatting](2_formatting.md) 132 | 133 | 1. [Whitespace and Symbols](2_formatting.md#whitespace-and-symbols) 134 | 1. [Blank Lines](2_formatting.md#blank-lines) 135 | 1. [Line Breaks](2_formatting.md#line-breaks) 136 | 1. [Indenting and Spacing](2_formatting.md#indenting-and-spacing) 137 | 1. [Braces](2_formatting.md#braces) 138 | 1. [Parentheses](2_formatting.md#parentheses) 139 | 1. [Language Elements](2_formatting.md#language-elements) 140 | 1. [Methods](2_formatting.md#methods) 141 | 1. [Constructors](2_formatting.md#constructors) 142 | 1. [Initializers](2_formatting.md#initializers) 143 | 1. [Lambdas](2_formatting.md#lambdas) 144 | 1. [Multi-line Text](2_formatting.md#multi-line-text) 145 | 1. [`return` Statements](2_formatting.md#return-statements) 146 | 1. [`switch` Statements](2_formatting.md#switch-statements) 147 | 1. [Ternary & Coalescing Operators](2_formatting.md#ternary-and-coalescing-operators) 148 | 1. [Comments](2_formatting.md#comments) 149 | 1. [Regions](2_formatting.md#regions) 150 | 151 | ### [Usage](3_usage.md) 152 | 153 | 1. [Structure](3_usage.md#structure) 154 | 1. [Assemblies](3_usage.md#assemblies) 155 | 1. [Files](3_usage.md#files) 156 | 1. [Namespaces](3_usage.md#namespaces) 157 | 1. [Types](3_usage.md#types) 158 | 1. [Classes](3_usage.md#classes) 159 | 1. [Abstract](3_usage.md#abstract-classes) 160 | 1. [Static](3_usage.md#static-classes) 161 | 1. [Inner](3_usage.md#inner-classes) 162 | 1. [Partial](3_usage.md#partial-classes) 163 | 1. [Interfaces](3_usage.md#interfaces) 164 | 1. [Generics](3_usage.md#generics) 165 | 1. [`structs`](3_usage.md#structs) 166 | 1. [`enums`](3_usage.md#enumerations) 167 | 1. [Members](3_usage.md#members) 168 | 1. [Modifiers](3_usage.md#modifiers) 169 | 1. [`sealed`](3_usage.md#sealed) 170 | 1. [`internal`](3_usage.md#internal) 171 | 1. [Declaration Order](3_usage.md#declaration-order) 172 | 1. [Constants](3_usage.md#constants) 173 | 1. [Constructors](3_usage.md#constructors) 174 | 1. [Properties](3_usage.md#properties) 175 | 1. [Indexers](3_usage.md#indexers) 176 | 1. [Methods](3_usage.md#methods) 177 | 1. [Extension Methods](3_usage.md#extension-methods) 178 | 1. [Parameters](3_usage.md#parameters) 179 | 1. [Optional Parameters](3_usage.md#optional-parameters) 180 | 1. [Expression-bodied Members](3_usage.md#expression-bodied-members) 181 | 1. [`tuples`](3_usage.md#tuples) 182 | 1. [Overloads](3_usage.md#overloads) 183 | 1. [Virtual](3_usage.md#virtual) 184 | 1. [`new` Properties](3_usage.md#new-properties) 185 | 1. [Event Handlers](3_usage.md#event-handlers) 186 | 1. [Operators](3_usage.md#operators) 187 | 1. [`ref` Returns and Properties](3_usage.md#ref-returns-and-properties) 188 | 1. [Statements and Expressions](3_usage.md#statements-and-expressions) 189 | 1. [`base`](3_usage.md#base) 190 | 1. [`this`](3_usage.md#this) 191 | 1. [Value Types](3_usage.md#value-types) 192 | 1. [Strings](3_usage.md#strings) 193 | 1. [Interpolation](3_usage.md#interpolation) 194 | 1. [`nameof`](3_usage.md#nameof) 195 | 1. [Resource Strings](3_usage.md#resource-strings) 196 | 1. [Floating Point and Integral Types](3_usage.md#floating-point-and-integral-types) 197 | 1. [Local Variables](3_usage.md#local-variables) 198 | 1. [Local Functions](3_usage.md#local-functions) 199 | 1. [`var`](3_usage.md#var) 200 | 1. [`out` variables](3_usage.md#out-variables) 201 | 1. [Loops](3_usage.md#loops) 202 | 1. [Conditions](3_usage.md#conditions) 203 | 1. [`switch`](3_usage.md#switch-statements) 204 | 1. [Pattern-matching](3_usage.md#pattern-matching) 205 | 1. [`continue`](3_usage.md#continue-statements) 206 | 1. [`return`](3_usage.md#return-statements) 207 | 1. [`goto`](3_usage.md#goto-statements) 208 | 1. [`unsafe`](3_usage.md#unsafe-blocks) 209 | 1. [Ternary and Coalescing Operators](3_usage.md#ternary-and-coalescing-operators) 210 | 1. [Null-conditional Operator](3_usage.md#null-conditional-operator) 211 | 1. [`throw`-Expressions](3_usage.md#throw-expressions) 212 | 1. [Lambdas](3_usage.md#lambdas) 213 | 1. [System.Linq](3_usage.md#systemlinq) 214 | 1. [Casting](3_usage.md#casting) 215 | 1. [`checked`](3_usage.md#checked) 216 | 1. [Compiler Variables](3_usage.md#compiler-variables) 217 | 1. [Comments](3_usage.md#comments) 218 | 219 | ### [Best Practices](4_design.md) 220 | 221 | 1. [Design](4_design.md) 222 | 1. [Abstractions](4_design.md#abstractions) 223 | 1. [Inheritance vs. Composition](4_design.md#inheritance-vs-composition) 224 | 1. [Interfaces vs. Abstract Classes](4_design.md#interfaces-vs-abstract-classes) 225 | 1. [Open vs. Closed APIs](4_design.md#open-vs-closed-apis) 226 | 1. [Controlling API Size](4_design.md#controlling-api-size) 227 | 1. [Read-only Interfaces](4_design.md#read-only-interfaces) 228 | 1. [Single-Responsibility Principle](4_design.md#single-responsibility-principle) 229 | 1. [Loose vs. Tight Coupling](4_design.md#loose-vs-tight-coupling) 230 | 1. [Methods vs. Properties](4_design.md#methods-vs-properties) 231 | 1. [Code > Comments](4_design.md#code--comments) 232 | 1. [Safe Programming](5_safeProgramming.md) 233 | 1. [Be Functional](5_safeProgramming.md#be-functional) 234 | 1. [Avoid `null` references](5_safeProgramming.md#avoid-null-references) 235 | 1. [Local variables](5_safeProgramming.md#local-variables) 236 | 1. [Side Effects](5_safeProgramming.md#side-effects) 237 | 1. [”Access to Modified Closure”](5_safeProgramming.md#access-to-modified-closure) 238 | 1. ["Collection was modified; enumeration operation may not execute."](5_safeProgramming.md#collection-was-modified-enumeration-operation-may-not-execute) 239 | 1. ["Possible multiple enumeration of IEnumerable"](5_safeProgramming.md#possible-multiple-enumeration-of-ienumerable) 240 | 1. [Error Handling](6_errorHandling.md) 241 | 1. [Strategies](6_errorHandling.md#strategies) 242 | 1. [Terms](6_errorHandling.md#terms) 243 | 1. [Errors](6_errorHandling.md#errors) 244 | 1. [Bugs](6_errorHandling.md#bugs) 245 | 1. [Design-by-Contract](6_errorHandling.md#design-by-contract) 246 | 1. [Throwing Exceptions](6_errorHandling.md#throwing-exceptions) 247 | 1. [Catching Exceptions](6_errorHandling.md#catching-exceptions) 248 | 1. [Defining Exceptions](6_errorHandling.md#defining-exceptions) 249 | 1. [The Try* Pattern](6_errorHandling.md#the-try-pattern) 250 | 1. [Error Messages](6_errorHandling.md#error-messages) 251 | 1. [Object Lifetime](7_objectLifetime.md) 252 | 1. [`IDisposable`](7_objectLifetime.md#idisposable) 253 | 1. [`Finalize`](7_objectLifetime.md#finalize) 254 | 1. [Destructors](7_objectLifetime.md#destructors) 255 | 1. [Best Practices](7_objectLifetime.md#best-practices) 256 | 1. [Managing Change](8_managingChange.md) 257 | 1. [Modifying Interfaces](8_managingChange.md#modifying-interfaces) 258 | 1. [Marking Members as Obsolete](8_managingChange.md#marking-members-as-obsolete) 259 | 1. [Refactoring Names and Signatures](8_managingChange.md#refactoring-names-and-signatures) 260 | 1. [Roadmap for Safe Obsolescence](8_managingChange.md#roadmap-for-safe-obsolescence) 261 | 1. [Documentation](9_documentation.md) 262 | 1. [Files](9_documentation.md#files) 263 | 1. [Language](9_documentation.md#language) 264 | 1. [Style](9_documentation.md#style) 265 | 1. [XML Documentation](9_documentation.md#xml-documentation) 266 | 1. [Examples](9_documentation.md#examples) 267 | 1. [Miscellaneous](10_miscellaneous.md) 268 | 1. [Generated Code](10_miscellaneous.md#generated-code) 269 | 1. [Configuration and File System](10_miscellaneous.md#configuration-and-file-system) 270 | 1. [Logging](10_miscellaneous.md#logging) 271 | 1. [`ValueTask`](10_miscellaneous.md#valuetaskt) 272 | -------------------------------------------------------------------------------- /1_naming.md: -------------------------------------------------------------------------------- 1 | # Naming 2 | 3 | ## Characters 4 | 5 | * Names contain only alphabetic characters. 6 | * The underscore is allowed only as a leading character for `private` fields. 7 | * Numbers are allowed only for local variables in tests and then only as a suffix. 8 | * Do not use the @-symbol 9 | 10 | ## Words 11 | 12 | * Use **US-English** (e.g. “color” rather than “colour”). 13 | * Use **English grammar** (e.g. use `ImportableDatabase` instead of `DatabaseImportable`). 14 | * Use only **standard abbreviations** (e.g. “XML” or “SQL”). 15 | * Use **correct capitalization**. If a word is not hyphenated, then it does not need a capital letter in the camel- or Pascal-cased form. For example, “metadata” is written as `Metadata` in Pascal-case, not `MetaData`. 16 | * Use **number names** instead of numbers (e.g. `partTwo` instead of `part2`). 17 | * Do not use Hungarian notation or any other prefixing notation to "group" types or members. 18 | * Use **whole words** or stick to accepted short forms (e.g. you may use `max` for `maximum` but prefer the suffix `Count` to the prefix `num`). 19 | 20 | ## Semantics 21 | 22 | * A name must be semantically **meaningful** in its **scope**. 23 | * A name should be as **short** as possible. 24 | * A name communicates **intent**; prefer `UpdatesAutomatically` to `AutoUpdate`. 25 | * A name reflects **semantics**, not storage details; prefer `InterestRate` to `DecimalRate`. 26 | * A name should include explicit **units** where possible. The following property isn't clearly defined. 27 | ```csharp 28 | public Timeout { get; } = 3600; 29 | ``` 30 | This looks like a timeout of either an hour or 3.6 seconds. Timeouts are _usually_ expression in milliseconds. We can fix this with documentation. 31 | ```csharp 32 | /// 33 | /// The number of milliseconds to wait before aborting the operation. 34 | /// 35 | public Timeout { get; } = 3600; 36 | ``` 37 | This is a good start. Even better is to include the units in the name of the property. 38 | ```csharp 39 | /// 40 | /// The number of milliseconds to wait before aborting the operation. 41 | /// 42 | public TimeoutInMilliseconds { get; } = 3600; 43 | ``` 44 | 45 | ## Case 46 | 47 | The following table lists the capitalization and naming rules for different language elements. 48 | 49 | * Pascal-case capitalizes every word in a name. 50 | * Camel-case capitalizes all but the first word in a name. 51 | * **Acronyms** longer than two letters are in Pascal-case (e.g. `Xml` or `Sql`). Acronyms at the beginning of a camel-case name are always all lowercase (e.g. `html`). 52 | 53 | Language Element | Case 54 | --- | --- 55 | Class | Pascal 56 | Interface | Pascal w/leading `I` 57 | Struct | Pascal 58 | Enumerated type | Pascal 59 | Enumerated element | Pascal 60 | Properties | Pascal 61 | Generic parameters | Pascal 62 | Tuple field | Pascal 63 | Public or protected `readonly` or `const` field | Pascal 64 | Private field | Camel with leading underscore 65 | Method argument | Camel 66 | Local variable | Camel 67 | Attributes | Pascal with `Attribute` suffix 68 | Exceptions | Pascal with `Exception` suffix 69 | Event handlers | Pascal with `EventHandler` suffix 70 | 71 | ### Collision and Matching 72 | 73 | * An element may not have the same name as its containing element (e.g. class `Expressions` in namespace `Expressions` or property `Company` on class `Company`). 74 | * The most appropriate name for a property is often the same as its type (e.g. for `enum` properties). 75 | * Names differing only by case may be defined within the same scope only for different language elements (e.g. a local variable and a property or a method parameter and a local parameter). 76 | ```csharp 77 | public void UpdateLength(int newLength, bool refreshViews) 78 | { 79 | int length = Length; 80 | // ... 81 | } 82 | ``` 83 | 84 | ## Grouping 85 | 86 | * Do not use a “library” prefix for types (e.g. instead of `QnoDatabase`, use a more descriptive name, like `MetaDatabase` or `RelationalDatabase`). 87 | * You may use a prefix when it's more convenient for disambiguation, as for UI-control libraries. If you do use a prefix, use a whole word (e.g. prefer `Quino` to `Qno`) and apply it consistently. 88 | * Avoid very generic type names (e.g. `Element`, `Node`, `Message` or `Log`), which collide with types from the framework or other commonly-used libraries. Use a more specific name, if at all possible. 89 | * If there are multiple types encapsulating similar concepts (but with different implementations, for example), you should use a common suffix to group them. For example, all the expression node types in the Encodo expressions library end in the word Expression. 90 | 91 | ## Algorithm 92 | 93 | ### Local Variables and Parameters 94 | 95 | A parameter name or local variable should have the same name as the type. It is valid to use shorter forms for longer type names, as long as the context is clear. 96 | 97 | * `IMetaExpressionFactory`: can be `metaExpressionFactory`, `expressionFactory` or `factory`. 98 | * `f` is not acceptable for parameters, but is acceptable for a local variable in a short body. 99 | 100 | ## Structure 101 | 102 | ### Assemblies 103 | 104 | * Assemblies should be named after their content. 105 | * Group assemblies with a common prefix (e.g. `Encodo` or `Quino`). 106 | * Separate identifiers in assembly names with a period. 107 | * The root namespace of an assembly does not have to match the assembly name. 108 | * The `AssemblyInfo.cs` file must contain company, copyright and version information. 109 | 110 | ### Files 111 | 112 | * The name of the file should match the type name. 113 | * Generated partial classes belong in a separate file, using the same root name as the user-editable file, but extended by an identifier to indicate its purpose or origin (as in the example below). This extra part must be Pascal-cased. For example: 114 | ```csharp 115 | Company.cs // user-modifiable file 116 | Company.Metadata.cs // properties generated from metadata 117 | ``` 118 | * If a generic type is the only type with that name, then do not include the parameter names in the filename. If there is a non-generic type and a generic type with the same name, then the filename should include the generic argument names enclosed in {}. E.g. If the types `Atom` and `Atom` both exist, then the filenames should be `Atom.c` and `Atom{TInput}.cs`, respectively. 119 | * Tests for a file go in `Tests.cs` (if there are a lot of tests, they should be split into several files, but always using the form `Tests.cs`) where `Extra` identifies the group of tests found in the file. Tests should be defined in their own assembly to avoid dependencies on unit-testing assemblies. The tests for a class should appear in the same location as the class being tested. That is, the tests for the class `Encodo.Tools.Csv.CsvParser` should be in `Encodo.Testing.Tools.Csv.CsvParser`. 120 | 121 | ### Namespaces 122 | 123 | * The namespace must match the file location in the project. For example, if the project’s root namespace is `Encodo.Parsers`, then the file located at `Csv/CsvParser.cs` should have the namespace `Encodo.Parsers.Csv`. 124 | * Namespaces start with `.` (e.g. `Encodo.Quino.*` or `Encodo.Punchclock.*`) 125 | * Namespaces should be plural, as they will contain multiple types (e.g. `Encodo.Expressions` instead of `Encodo.Expression`). This also reduces the risk of collision. 126 | * If your framework or application encompasses more than one tier, use the same namespace identifiers for similar tasks. For example, common data-access code goes in `Encodo.Data`, but metadata-based data-access code goes in `Encodo.Quino.Data`. 127 | * Do not use “reserved” namespace names like `System` because these will conflict with standard .NET namespaces and require resolution using the `global::` namespace prefix. 128 | 129 | ## Types 130 | 131 | ### Classes 132 | 133 | * If a class implements a single interface, it should reflect this by incorporating the interface name into its own (e.g. `MetaList` implements `IList`). 134 | * Static classes containing extension methods end in `Extensions` 135 | * All other static classes should use the suffix `Tools`. 136 | * `abstract` classes should use the suffix `Base`. 137 | 138 | ### Interfaces 139 | 140 | * Prefix interfaces with the letter “I”. 141 | 142 | ### `enums` 143 | 144 | * Simple enumerations have singular names, whereas bit-sets have plural names. 145 | 146 | ### Generic Parameters 147 | 148 | * If a class or method has a single generic parameter, use the letter `T`. 149 | * If there are two generic parameters and they correspond to a key and a value, then use `K` and `V`. 150 | * Generic methods on classes with a generic parameter should use `TResult`, where appropriate. The example below shows a generic class with such a method. 151 | ```csharp 152 | public class ListBookmarkSelection : IBookmarkSelection 153 | { 154 | public IList GetObjects() 155 | { 156 | // Convert list contents from T to TResult 157 | } 158 | } 159 | ``` 160 | * Generic conversion functions should use `TInput` and `TOutput`, respectively. 161 | ```csharp 162 | public static IList ConvertList(IList input) 163 | { 164 | // Convert list contents from TInput to TOutput 165 | } 166 | ``` 167 | * If there are multiple parameters, but no pattern, name the “contained” element `T` (if there is one) and the other parameters something specific starting with the letter T. 168 | 169 | ### Sequences and Lists 170 | 171 | * Prefer the plural form (e.g. `appointments`) for sequences (e.g. `IEnumerable`) and lists or arrays 172 | * Use the suffix “List” when you want to emphasize that a parameter or property is a list (e.g. `appointmentList`). 173 | 174 | ## Members 175 | 176 | ### Properties 177 | 178 | * Properties should be nouns or adjectives. 179 | * Prepend “Is” to the name for Boolean properties only if the intent is unclear without it. The next example shows such a case: 180 | ```csharp 181 | public bool Empty { get; } 182 | public bool IsEmpty { get; } 183 | ``` 184 | Even though it’s a property not a method, the first example might still be interpreted as a verb rather than an adjective. The second example adds the verb “Is” to avoid confusion, but both formulations are acceptable. 185 | * A property’s backing field (if present) must be an underscore followed by the name of the property in camel case. 186 | * Use common names, like `Item` or `Value`, for accessing the central property of a type. 187 | * Do not include type information in property names. For example, for a property of type `IMetaRelation`, use the name Relation instead of the name `MetaRelation`. 188 | * Make the identifier as short as possible without losing information. For example, if a class named `IViewContext` has a property of type `IViewContextHandler`, that property should be called `Handler`. 189 | * If there are two properties that could be shortened in this way, then neither of them should be. If the class in the example above has another property of type `IEventListHandler`, then the properties should be named something like `ViewContextHandler` and `EventListHandler`, respectively. 190 | * Avoid repeating information in a class member that is already in the class name. Suppose, there is an interface named `IMessages`; instances of this interface are typically named messages. That interface should not have a property named `Messages` because that would result in calls to `messages.Messages.Count`, which is redundant and not very readable. Instead, name the property something more semantically relevant, like `All`, so the call would read `messages.All.Count`. 191 | 192 | ### Methods 193 | 194 | * Methods names should include a verb. 195 | * Method names should not repeat information from the enclosing type. For example, an interface named `IMessages` should not have a method named `LogMessage`; instead name the method `Log`. 196 | * State what a method does; do not describe the parameters (let code-completion and the signature do that for you). 197 | * Methods that return values should indicate this in their name, like `GetList()`, `GetItem()` or `CreateDefaultDatabase()`. Though there is garbage collection in C#, you should still use `Get` to indicate retrieval of a local value and `Create` to indicate a factory method, which always creates a new reference. For example, instead of writing: 198 | ```csharp 199 | public IDataList GetList(IMetaClass cls) 200 | { 201 | return ViewApplication.Application.CreateContext(cls); 202 | } 203 | ``` 204 | 205 | You should write: 206 | 207 | ```csharp 208 | public IDataList CreateList(IMetaClass cls) 209 | { 210 | return ViewApplication.Application.CreateContext(cls); 211 | } 212 | ``` 213 | * Avoid defining everything as a noun or a manager. Prefer names that are logically relevant, like `Missile.Launch()` rather than `MissileLauncher.Execute(missile)`. 214 | * Methods that set a single property value should begin with the verb `Set`. 215 | * The most generalized version of a method name should be reserved for the method that the framework wishes to encourage or that is used most often. An example from [6] is reproduced below: 216 | 217 | > Suppose you have two event-delivery mechanisms, one for immediate (synchronous) delivery and one for delayed (asynchronous) delivery. The names `sendEventNow()` and `sendEventLater()` suggest themselves. Now, if you want to encourage your users to use synchronous delivery (e.g., because it is more lightweight), you could name the synchronous method `sendEvent()` and keep `sendEventLater()` for the asynchronous case. 218 | 219 | ### Extension Methods 220 | 221 | * Extension methods for a given class or interface should appear in a class named after the class or interface being extended, plus the suffix “Tools”. For example, extension methods for the class `Enum` should appear in a class named `EnumTools`. 222 | * In the case of interfaces, the leading “I” should be dropped from the class name. For example, extension methods for the interface `IEnumerable` should appear in a class named `EnumerableTools`. 223 | 224 | ### Parameters 225 | 226 | * Prefer whole words instead of abbreviations (use `index` instead of `idx`). 227 | * Parameter names should be based on their intended use or purpose rather than their type (unless the type indicates the purpose adequately). 228 | * Do not simply repeat the type for the parameter name; use a name that is as short as possible, but doesn't lose meaning. (E.g. a parameter of type `IDataContext` should be called `context` instead of `dataContext`.) 229 | * However, if the method also, at some point, makes use of an `IViewContext`, you should make the parameter name more specific, using `dataContext` instead. 230 | * For copy constructors or equality operators, name the object to be copied or compared `other`. 231 | 232 | ### Lambdas 233 | 234 | * Do not use the highly non-expressive `x` as a parameter name. 235 | * Instead, use a single-letter variable starting with the first letter of the type or formal parameter. 236 | * Parameters in a lambda expression should follow the same conventions as for parameters in standard methods. 237 | 238 | ### Events 239 | 240 | * Single events should be named with a noun followed by a descriptive verb in the past tense. 241 | ```csharp 242 | event EventHandler MessageDispatched; 243 | ``` 244 | * For paired events—one raised before executing some code and one raised after—use the gerund form (i.e. ending in “ing”) for the first event and the past tense (i.e. ending in “ed”) for the second event. 245 | ```csharp 246 | event EventHandler MessageDispatching; 247 | event EventHandler MessageDispatched; 248 | ``` 249 | * Event receivers are like any other methods and should be named according to their task, not the event to which they are attached. The following method updates the user interface; it does this regardless of whether it is attached as an event receiver for the `MessageDispatched` event. 250 | ```csharp 251 | void UpdateUserInterface(object sender, EventArgs args) 252 | { 253 | // Implementation 254 | } 255 | ``` 256 | * Never start an event receiver method with “On” because Microsoft uses that convention for raising events. 257 | * To trigger an event, use `Raise[EventName]`; this method must be `protected` and `virtual` to allow descendants to perform work before and after calling the base method. 258 | * If you are raising events for changes made to properties, use the pattern `Raise[Property]Changed`. 259 | 260 | ### Delegates 261 | 262 | * Use a descriptive verb for delegate names, like the following examples: 263 | ```csharp 264 | delegate string TransformString(T item); 265 | delegate bool ItemExists(T item); 266 | delegate int CompareItems(T first, T second); 267 | ``` 268 | * Delegate method parameter names should use the same grammar as the type, so that it sound natural when executed: 269 | ```csharp 270 | public string[] ToStrings(TransformToString transform) 271 | { 272 | // ... 273 | result[] = transform(item); 274 | // ... 275 | } 276 | ``` 277 | * If a delegate is only used once or twice and has a relatively simple syntax, use `Func<>` for the parameter signature instead of declaring a delegate. The example above can be rewritten as: 278 | ```csharp 279 | public string[] ToStrings(Func transform) 280 | { 281 | // ... 282 | result[] = transform(item); 283 | // ... 284 | } 285 | ``` 286 | This practice makes it much easier to determine the expected signature in code-completion as well. 287 | 288 | ## Statements and Expressions 289 | 290 | ### Local Variables 291 | 292 | Since local variables are limited to a much smaller scope and are not documented, the rules for name-selection are somewhat more relaxed. 293 | 294 | * Avoid using `temp` or `i` or `idx` for loop indexes. Use the suffix `Index` together with a descriptive prefix, as in `colIndex` or `itemIndex` or `memberIndex`. 295 | * Names need only be as specific as the scope requires. 296 | * The more limited the scope, the more abbreviated the variable may be. 297 | * Use real words where there is enough space to do so. 298 | * Use single letters in lambdas where you're trying to save space. 299 | * Use `_` instead of declaring useless local variables during deconstruction. 300 | 301 | ### Return Values 302 | 303 | * If a method creates a local variable expressly for the purpose of returning it as the result, that variable should be named `result`. 304 | ```csharp 305 | object result = this[Fields.Id]; 306 | 307 | if (result == null) { return null; } 308 | 309 | return (Int32)result; 310 | ``` 311 | 312 | ### Compiler Variables 313 | 314 | * Compiler variables are all capital letters, with words separated by underscores. 315 | ```csharp 316 | #if ENCODO_DEVELOPER 317 | return true; 318 | #else 319 | return false; 320 | #endif 321 | ``` -------------------------------------------------------------------------------- /2_formatting.md: -------------------------------------------------------------------------------- 1 | # Formatting 2 | 3 | ## Whitespace and Symbols 4 | 5 | ### Blank Lines 6 | 7 | In the following list, the phrase “surrounding code” refers to a line consisting of more than just an opening or closing brace. That is, no new line is required when an element is at the beginning or end of a methods or other block-level element. 8 | Always place an empty line in the following places: 9 | 10 | * Between the file header and the `namespace` declaration or the first `using` statement. 11 | * Between the last `using` statement and the `namespace` declaration. 12 | * Between types (`classes`, `structs`, `interfaces`, `delegates` or `enums`). 13 | * Between public, protected and internal members. 14 | * Between preconditions and ensuing code. 15 | * Between post-conditions and preceding code. 16 | * Between a call to a `base` method and ensuing code. 17 | * Between `return` statements and surrounding code. 18 | * Between block constructs (e.g. `while` loops or `switch` statements) and surrounding code. 19 | * Between documented `enum` values; undocumented values may be grouped together. 20 | * Between logical groups of code in a method; this notion is subjective and more a matter of style. You should use empty lines to improve readability, but should not overuse them. 21 | * Between the last line of code in a block and a comment for the next block of code. 22 | * Between statements that are broken up into multiple lines. 23 | * Between a `#region` tag and the first line of code in that region. See next section. 24 | * Between the last line of code in a region and the `#endregion` tag. See next section. 25 | 26 | Do not place an empty line in the following places: 27 | 28 | * After another empty line. 29 | * Between retrieval code and handling for that code. Instead, they should be formatted together. 30 | ```csharp 31 | IMetaReadableObject obj = context.Find(); 32 | if (obj == null) 33 | { 34 | context.Recorder.Log(Level.Fatal, String.Format("Error!")); 35 | 36 | return null; 37 | } 38 | ``` 39 | * Between any line and a line that has only an opening or closing brace on it (i.e. there should be no leading or trailing newlines in a block). 40 | * Between undocumented fields (usually private); if there are many such fields, you may use empty lines to group them logically. 41 | 42 | ### Line Breaks 43 | 44 | * Use line-breaking only when necessary, as outlined below. 45 | * No line should exceed 120 characters. 46 | * Use as few line-breaks as possible. 47 | * Line-breaking should occur at natural boundaries; the most common such boundary is between parameters in a method call or definition. 48 | * A line-break at a boundary that defines a new block should be indented one more level. 49 | * A line-break at any other boundary should be indented at the same level as the original line. 50 | * The separator (e.g. a comma) between elements formatted onto multiple lines goes on the same line after the element. The IDE is much more helpful when formatting that way. 51 | * The most natural line-breaking boundary is often before and after a list of elements. For example, the following method call has line-breaks at the beginning and end of the parameter list. 52 | ```csharp 53 | people.DataSource = CurrentCompany.Employees.GetList( 54 | connection, metaClass, GetFilter(), null 55 | ); 56 | ``` 57 | * If one of the parameters is much longer, then you add line-breaking between the parameters; in that case, all parameters are formatted onto their own lines: 58 | ```csharp 59 | people.DataSource = CurrentCompany.Employees.GetList( 60 | connection, 61 | metaClass, 62 | GetFilter("Global.Applications.Updater.PersonList.Search"), 63 | null 64 | ); 65 | ``` 66 | * Note in the examples above that the parameters are indented. If the assignment or method call was longer, they would no longer fit on the same line. In that case, you should use two levels of indenting. 67 | ```csharp 68 | Application.Model.people.DataSource = 69 | Global.ApplicationEnvironment.CurrentCompany.Employees.GetList( 70 | connection, 71 | metaClass, 72 | GetFilter("Global.Applications.Updater.PersonList.Search"), 73 | null 74 | ); 75 | ``` 76 | * Even if there is a logical grouping for parameters, you should still apply line-breaking using the all-on-one-line or each-on-its-own-line rules stated above. For example, the following method specifying Cartesian coordinates feels natural, but is not well-supported by automatic formatting rules: 77 | ```csharp 78 | Geometry.PlotInBox( 79 | "Global.Applications.MainWindow", 80 | topLeft.X, topLeft.Y, 81 | bottomRight.X, bottomRight.Y 82 | ); 83 | ``` 84 | As nice as it might look, _do not use this line-breaking technique._ 85 | 86 | ### Indenting and Spacing 87 | 88 | * An indent is two spaces. 89 | * Use a single space after a comma (e.g. between function arguments). 90 | * There is no space after the leading parenthesis/bracket or before the closing parenthesis/bracket. 91 | * There is no space between a method name and the leading parenthesis, but there is a space before the leading parenthesis of a flow-control statement. 92 | * Use a single space to surround _all_ infix operators; there is no space between a prefix operator (e.g. “-” or “!”) and its single parameter. 93 | * Do not use spacing to align type members on the same column (e.g. as with the explicitly assigned values for members of an enumerated type). 94 | 95 | ### Braces 96 | 97 | * Curly braces should—with a few exceptions outlined below—go on their own line. 98 | * A line with only an opening brace should never be followed by an empty line. 99 | * A line with only a closing brace should never be preceded by an empty line. 100 | 101 | #### Properties 102 | 103 | * Simple getters and setters should go on the same line as all brackets. 104 | * Abstract properties should have get, set and all braces on the same line. 105 | * Complex getters and setters should have each bracket on its own line. 106 | * Place Auto-Property Initializers on the same line. 107 | ```csharp 108 | public int Maximum { get; } = 45; 109 | ``` 110 | 111 | #### Methods 112 | 113 | * Completely empty methods should have brackets placed on separate lines: 114 | ```csharp 115 | SomeClass(string name) 116 | : base(name) 117 | { 118 | } 119 | ``` 120 | 121 | ### Parentheses 122 | 123 | * Use parentheses only to improve clarity. 124 | * Do not use parentheses for simple expressions. The following expression is clear enough without extra parentheses. 125 | ```csharp 126 | if (context != null && context.Count > 0) 127 | { 128 | } 129 | ``` 130 | 131 | ## Language Elements 132 | 133 | ### Methods 134 | 135 | #### Definitions 136 | 137 | * Stay consistent with line-breaking in related methods within a class; if one is broken up onto multiple lines, then all related methods should be broken up onto multiple lines. 138 | * The closing brace of a method definition goes on the same line as the last parameter (unlike method calls). This avoids having a line with a solitary closing parenthesis followed by a line with a solitary opening brace. 139 | ```csharp 140 | public static void SetupLookupDefinition( 141 | RepositoryItemLookUpEdit lookupOptions, 142 | IMetaClass metaClass) 143 | { 144 | // Implementation... 145 | } 146 | ``` 147 | * Generic method constraints should always be on their own line, with a single indent. 148 | ```csharp 149 | string GetNames(IMetaCollection elements, string separator, NameOption options 150 | where T : IMetaBase; 151 | ``` 152 | * The indent for a generic-method constraint stays the same, even with wrapped parameters. 153 | ```csharp 154 | public static void SetupLookupFromData( 155 | RepositoryItemLookUpEdit lookupOptions, 156 | IDataList dataList) 157 | where T : IMetaReadable 158 | { 159 | SetupLookupFromData(lookupOptions, dataList, dataList.MetaClass); 160 | } 161 | ``` 162 | 163 | #### Calls 164 | 165 | * The closing parenthesis of a method call goes on its own line to “close” the block (see example below). 166 | ```csharp 167 | result.Messages.Log( 168 | Level.Error, 169 | String.Format( 170 | "Class [{0}] has the same name as class [{1}].", 171 | dbCls.Identifier, 172 | classMap[cls.MetaId] 173 | ) 174 | ); 175 | ``` 176 | * If the result of calling a method is assigned to a variable, the call may be on the same line as the assignment if it fits. 177 | ```csharp 178 | people.DataSource = CurrentCompany.Employees.GetList( 179 | connection, 180 | ViewAspectTools.GetViewableWrapper(cls), 181 | GetFilter().Where(String.Format(“PersonId = {0}”, personId)), 182 | null 183 | ); 184 | ``` 185 | * If the call does not fit easily—or if the method call is “too far away” from the ensuing parameters—you should move the call to its own line and indent it: 186 | ```csharp 187 | WindowTools.GetActiveWindow().GetActivePanel().GetActiveList().DataSource = 188 | CurrentCompany.Organization.MainOffice.Employees.GetList( 189 | connection, 190 | ViewAspectTools.GetViewableWrapper(cls), 191 | GetFilter().Where(String.Format(“PersonId = {0}”, personId)), 192 | null 193 | ); 194 | ``` 195 | 196 | #### Chaining 197 | 198 | * Chained method calls can be formatted onto multiple lines; if one chained method-call is formatted onto its own line, then they must all be on separate lines. 199 | ```csharp 200 | string contents = header 201 | .Replace("{Year}", DateTime.Now.Year.ToString()) 202 | .Replace("{User}", "ENCODO") 203 | .Replace("{DateTime}", DateTime.Now.ToString()); 204 | ``` 205 | * If a line of a chained method-call opens a new logical context, then ensuing lines should be indented to indicate this. For example, the following example joins tables together, with the last three statements applied to the last joined table. The indenting helps make this clear. 206 | ```csharp 207 | query 208 | .Join(Settings.Relations.Company) 209 | .Join(Company.Relations.Office) 210 | .Join(Office.Relations.Employees) 211 | .WhereEquals(Employee.Fields.Id, employee.Id) 212 | .OrderBy(Employee.Fields.LastName, SortDirection.Ascending) 213 | .OrderBy(Employee.Fields.FirstName, SortDirection.Ascending); 214 | ``` 215 | 216 | ### Constructors 217 | 218 | * Base constructors should be on a separate line, indented one level. 219 | ```csharp 220 | public class B : A 221 | { 222 | B(string name) 223 | : base(name) 224 | { 225 | } 226 | } 227 | ``` 228 | 229 | ### Initializers 230 | 231 | * Don't add a trailing comma for the last element. 232 | 233 | #### Object Initializers 234 | 235 | Longer initialization blocks should go on their own line; the rest can be formatted in the following ways (depending on line-length and preference): 236 | 237 | * Shorter initialization blocks (one or two properties) can be specified on the same line: 238 | ```csharp 239 | var personOne = new Person { LastName = "Miller", FirstName = "John" }; 240 | ``` 241 | * The `new`-clause is on the same line as the declaration: 242 | ```csharp 243 | var sizeAspect = new ViewPropertySizeAspect 244 | { 245 | VerticalSizeMode = SizeMode.Absolute, 246 | VerticalUnits = height 247 | }; 248 | 249 | prop.Aspects.Add(sizeAspect); 250 | ``` 251 | * The new-clause is on its own line: 252 | ```csharp 253 | var sizeAspect = 254 | new ViewPropertySizeAspect 255 | { 256 | VerticalSizeMode = SizeMode.Absolute, 257 | VerticalUnits = height 258 | }; 259 | 260 | prop.Aspects.Add(sizeAspect); 261 | ``` 262 | * The initializer is nested within the method-call on the same line as the declaration: 263 | ```csharp 264 | prop.Aspects.Add(new ViewPropertySizeAspect { VerticalUnits = height }); 265 | ``` 266 | * The initializer is nested within the method-call on its own line: 267 | ```csharp 268 | prop.Aspects.Add( 269 | new ViewPropertySizeAspect 270 | { 271 | VerticalSizeMode = SizeMode.Absolute, 272 | VerticalUnits = height 273 | }); 274 | ``` 275 | * Putting the `new` operator on the same line is also fine, but then you shouldn't indent the braces. 276 | ```csharp 277 | prop.Aspects.Add(new ViewPropertySizeAspect 278 | { 279 | VerticalSizeMode = SizeMode.Absolute, 280 | VerticalUnits = height 281 | }); 282 | ``` 283 | 284 | If the initializer goes spans multiple lines, then the new-clause must also go on its own line. 285 | 286 | #### Array Initializers 287 | 288 | The type is usually optional (unless you’re initializing an empty array), so you should leave it empty. 289 | 290 | ### Lambdas 291 | 292 | * Do not use anonymous delegates; use lambda notation instead. 293 | * Do not use parentheses around a single parameter in a lambda expression. 294 | * All rules for standard method calls also apply to method calls with lambdas. 295 | * Longer lambdas should be written with an indent, as follows: 296 | ```csharp 297 | var persistentProperties = 298 | model.ReferencedProperties.FindAll( 299 | prop => 300 | { 301 | // More code... 302 | 303 | return prop.Persistent; 304 | } 305 | ); 306 | ``` 307 | * Short lambdas benefit can just be inlined: 308 | ```csharp 309 | public string[] Keys 310 | { 311 | get 312 | { 313 | return ToStrings(i => i.Name); 314 | } 315 | } 316 | ``` 317 | Also OK, since the `get` body is short: 318 | ```csharp 319 | public string[] Keys 320 | { 321 | get { return ToStrings(i => i.Name); } 322 | } 323 | ``` 324 | Even better, use expression-bodied members: 325 | ```csharp 326 | public string[] Keys => ToStrings(i => i.Name); 327 | ``` 328 | * Short lambdas are just parameters; treat them as you would any other parameters: 329 | ```csharp 330 | _context = new DataContext( 331 | Settings.Default.ConfigFileName, 332 | DatabaseType.PostgreSql, 333 | () => ModelGenerator.CreateModel() 334 | ); 335 | ``` 336 | In the example above each parameter is on its own line, as required. 337 | * Keep parameters short in constructor bases. 338 | ```csharp 339 | public Application() 340 | : base(DatabaseType.PostgreSql, () => ModelGenerator.CreateModel()) 341 | { 342 | } 343 | ``` 344 | * All rules for standard method calls also apply to method calls with lambda expressions. 345 | * Very short lambda expressions may be written as a simple parameter on the same line as the method call: 346 | ```csharp 347 | ReportError(msg => MessageBox.Show(msg)); 348 | ``` 349 | * Longer lambda expressions should go on their own line, with the closing parenthesis of the method call closing the block on another line. Any calls attached to the result—like `ToList()` or `Count()`—should go on the same line as the closing parenthesis. 350 | ```csharp 351 | people.DataSource = CurrentCompany.Employees.Where( 352 | item => item.LessonTimeId == null 353 | ).ToList(); 354 | ``` 355 | * Longer lambda expressions should not be both wrapped and used in a `foreach`-statement; instead, use two statements as shown below. Use short parameter names in lambdas since they are used very close to their declaration (by definition). 356 | ```csharp 357 | var appointmentsForDates = data.Appointments.FindAll( 358 | a => a.StartTime >= startDate && a.EndTime <= endDate 359 | ); 360 | 361 | foreach (var appointment in appointmentsForDates) 362 | { 363 | // Do something with each appointment 364 | } 365 | ``` 366 | 367 | ### Multi-Line Text 368 | 369 | * Longer string-formatting statements with newlines should be formatted using the verbatim strings (`@""`) and should avoid using concatenation: 370 | ```csharp 371 | result.SqlText = String.Format( 372 | @"FROM person 373 | LEFT JOIN 374 | employee 375 | ON person.employee_id = employee.id 376 | LEFT JOIN 377 | company 378 | ON person.company_id = company.id 379 | LEFT JOIN 380 | program 381 | ON company.program_id = program.id 382 | LEFT JOIN 383 | settings 384 | ON settings.program_id = program.id 385 | WHERE 386 | program.id = {0} AND person.hire_date <= '{2}'; 387 | ", 388 | settings.ProgramId, 389 | state, 390 | offset.ToString("yyyy-MM-dd") 391 | ); 392 | ``` 393 | * If the indenting in the string argument above is important, you may break indenting rules and place the text all the way to the left of the source in order to avoid picking up extra, unwanted spaces. However, you should consider externalizing such text to resources or text files. 394 | * The trailing double-quote in the example above is not required, but is permitted; in this case, the code needs to include a newline at the end of the SQL statement. 395 | 396 | ### `return` Statements 397 | 398 | * If a `return` statement is not the only statement in a method, it should be separated from other code by a single newline. 399 | * Always use multi-line formatting for `return` statements so they're easy to see. 400 | ```csharp 401 | if (Count != other.Count) 402 | { 403 | return false; 404 | } 405 | ``` 406 | * Do _not_ use else with `return` statements. 407 | ```csharp 408 | if (a == 1) 409 | { 410 | return true; 411 | } 412 | else // Not necessary 413 | { 414 | return false; 415 | } 416 | ``` 417 | Instead, you should write the `return` as shown below. 418 | ```csharp 419 | if (a == 1) 420 | { 421 | return true; 422 | } 423 | 424 | return false; 425 | ``` 426 | In this case, return the condition instead. 427 | ```csharp 428 | return a == 1; 429 | ``` 430 | 431 | ### `switch` Statements 432 | 433 | The following rules apply for all `switch` statements, including pattern-matching. 434 | 435 | * Contents under `switch` statements should be indented. 436 | * Braces for a case-label are not indented; this maintains a nice alignment with the brackets from the switch-statement. 437 | * Use braces for longer code blocks under case-labels; leave a blank line above the break-statement to improve clarity. 438 | ```csharp 439 | switch (flavor) 440 | { 441 | case Flavor.Up: 442 | case Flavor.Down: 443 | { 444 | if (someConditionHolds) 445 | { 446 | // Do some work 447 | } 448 | 449 | // Do some more work 450 | 451 | break; 452 | } 453 | default: 454 | break; 455 | } 456 | ``` 457 | * Use braces to enforce tighter scoping for local variables used for only one `case`-label. 458 | ```csharp 459 | switch (flavor) 460 | { 461 | case Flavor.Up: 462 | case Flavor.Down: 463 | { 464 | int quarkIndex = 0; // valid within scope of case statement 465 | break; 466 | } 467 | case Flavor.Charm: 468 | case Flavor.Strange: 469 | int isTopOrBottom = false; // valid within scope of switch statement 470 | break; 471 | default: 472 | break; 473 | } 474 | ``` 475 | * If brackets are used for a case-label, the break-statement should go inside those brackets so that the bracket provides some white-space from the next case-label. 476 | ```csharp 477 | switch (flavor) 478 | { 479 | case Flavor.Up: 480 | case Flavor.Down: 481 | { 482 | int quarkIndex = 0; 483 | break; 484 | } 485 | case Flavor.Charm: 486 | case Flavor.Strange: 487 | { 488 | int isTopOrBottom = false; 489 | break; 490 | } 491 | default: 492 | { 493 | handled = false; 494 | break; 495 | } 496 | } 497 | ``` 498 | 499 | ### Ternary and Coalescing Operators 500 | 501 | * Do not use line-breaking to format statements containing ternary and coalescing operators; instead, convert to an `if`/`else` statement. 502 | * Do not use parentheses around the condition of a ternary expression. If the condition is not immediately recognizable, extract it to a local variable. 503 | ```csharp 504 | return _value != null ? _value.ToString() : "NULL"; 505 | ``` 506 | * Prefix operators (e.g. `!`) and method calls should not have parentheses around them. 507 | ```csharp 508 | return !HasValue ? Value.ToString() : "EMPTY"; 509 | ``` 510 | 511 | ### Comments 512 | 513 | * Place comments above referenced code. 514 | * Indent comments at the same level as referenced code. 515 | 516 | ### Regions 517 | 518 | * Do not use regions. 519 | * A historical usage of `#region` tags is to delineate code regions to be ignored by ReSharper in generated files. Instead, tell ReSharper to ignore files with the pattern of the generated filenames (e.g. `*.Class.cs`). 520 | -------------------------------------------------------------------------------- /4_design.md: -------------------------------------------------------------------------------- 1 | # Design 2 | 3 | Design decisions should not be made alone. You should apply these principles to come up with a design, but should always seek the advice and approval of at least one other team member before proceeding. 4 | 5 | ## Abstractions 6 | 7 | The first rule of design is: don’t overdo it (YAGNI). Overdesign leads to a framework that offers unused functionality and has interfaces that are difficult to understand and implement. Only create abstractions where there will be more than one implementation or where there is a reasonable need to provide for other implementations in the future. 8 | 9 | This leads directly to the second rule of design: “don’t under-design”. Understand your problem domain well enough before starting to code so that you accommodate reasonably foreseeable additional requirements. For example, you need to figure out whether multiple implementations will be required (in which case you should define interfaces) and whether any of those implementations will share code (in which case abstract interfaces or one or more abstract base classes are in order). You should create abstractions where they prevent repeated code—applying the DRY principle—or where they provide decoupling. 10 | 11 | If you do create an abstraction, make sure that there are tests which run against the abstraction rather than a concrete implementation so that all future implementations can be tested. For example, database access for a particular database should include an abstraction and tests for that abstraction that can be used to verify all supported databases. 12 | 13 | ## Inheritance vs. Composition 14 | 15 | The rule here is to only use inheritance where it makes semantic sense to do so. If two classes could share code because they perform similar tasks, but aren't really related, do not give them a common ancestor just to avoid repeating yourself. Extract the shared code into a helper class and use that class from both implementations. Prefer composition of instances over `static` helpers. 16 | 17 | ## Interfaces vs. Abstract Classes 18 | 19 | Whether or not to use interfaces is a hotly-debated topic. On the one hand, interfaces offer a clean abstraction and “interface” to a library component and, on the other hand, they restrict future upgrades by forcing new methods or properties on existing implementations. In a framework or library, you can safely add members to classes that have descendants in application code without forcing a change in that application code. However, abstract methods—which are necessary for very low-level objects because the implementation can’t be known—run into the same problems as new interface methods. Creating new, virtual methods with no implementation to avoid this problem is strongly frowned upon, as it fails to properly impart the intent of the method. 20 | 21 | One exception to this rather strictly enforced rule is for classes that simply cannot be abstract. This will be the case for user-interface components that interact with a visual designer. The Visual Studio designer, for example, requires that all components be non-abstract and include a default constructor in order to be used. In these cases, empty virtual methods that throw a `NotImplementedException` are the only alternative. If you must use such a method, include a comment explaining the reason. 22 | 23 | Where interfaces can also be very useful is in restricting write-access to certain properties or containers. That is, an interface can be declared with only a getter, but the implementation includes both a getter and setter. This allows an application to set the property when it works with an internal implementation, but to restrict the code receiving the interface to a read-only property. 24 | 25 | ## Open vs. Closed APIs 26 | 27 | This section used to be called “Virtual Methods” but could just as easily have been called “Virtual vs. Regular Methods” or “Protected vs. Private Methods” or “Sealed Classes and Methods vs. anyone using your APIs”. That last one was—kind of—a joke. 28 | 29 | The point is that any element that is exposed to other code imposes a maintenance burden of some kind. Public and protected elements: 30 | 31 | * Must be documented 32 | * Must be tested (either with automated tests or manual testing) 33 | * Must be properly designed (private members don’t need as much attention) 34 | * Can only expose public types 35 | * Cannot be refactored as mercilessly 36 | 37 | By nature, C# is more closed in this regard because classes are internal and methods are non-virtual by default. This makes for APIs that require less maintenance but are also less open to other coders. This can be a very frustrating experience for those coders when they inherit from these classes only to find that misbehaving methods cannot be overridden, vital functionality is hidden inside private or virtual methods or classes are sealed so as to prevent inheritance entirely. 38 | 39 | Very often, this leads to code duplication as entire swaths of code are copied via reverse-engineering just in order to apply a minor tweak. A historical case is the Silverlight framework from Microsoft where many classes are sealed and many methods are internal or private and very little of the API can be overridden. 40 | 41 | Naturally, it would be nice to avoid frustrating other coders in this way, but making everything public or protected and virtual is also not the solution. Such blanket approaches fail to guide users of your API sufficiently. It is up to you to find a happy medium, exposing exactly the functionality that any user might need: no more, no less. Naturally, this will force you to actually think about the purpose of the class that you are writing and consider the use-cases for it. 42 | 43 | With use-cases in mind, here are some points to consider when building a class. 44 | 45 | * What are the odds that anyone will want to create a descendant of your class? 46 | * If these odds are non-zero, do not seal the class. 47 | * Does your class include functionality that descendants will want to reuse? In that case, make the method or property protected. 48 | * Is it possible that descendants will want to change how that functionality works? In that case, make the method or property virtual. 49 | * Anything else should be made private in order to avoid exposing too large an interface to both users of the public interface and descendants, which use the protected interface. 50 | 51 | ## Controlling API Size 52 | 53 | * Be as stingy as possible when making methods public; smaller APIs are easier to understand. 54 | * If another assembly needs a type to be public, consider whether that type could not remain internalized if the API were higher-level. Use the Object Browser to examine the public API. 55 | * To this end, frameworks that are logically split into multiple assemblies can use the `InternalsVisibleTo` attributes to make “friend assemblies” and avoid making elements public. Given three assemblies, `Quino`, `QuinoWinform` and `QuinoWeb` (of which a standard Windows application would include only the first two), the `Quino` assembly can make its internals visible to `QuinoWinform`. 56 | 57 | ## Read-only Interfaces 58 | 59 | One advantage in using interfaces over abstract classes is that interfaces can enforce immutability even though the backing implementation is mutable. 60 | 61 | The following example illustrates this principle for properties: 62 | 63 | ```csharp 64 | interface IMaker 65 | { 66 | bool Enabled { get; } 67 | } 68 | 69 | class Maker : IMaker 70 | { 71 | bool Enabled { get; set; } 72 | } 73 | ``` 74 | 75 | The principle extends to making read-only sequences as well, using `IEnumerable` in the interface and exposing `IList` in the backing class, using explicit interface implementation, as shown below: 76 | 77 | ```csharp 78 | interface IProcessedCommands 79 | { 80 | IEnumerable SwitchCommands { get; } 81 | } 82 | 83 | class ProcessedCommands 84 | { 85 | IEnumerable IProcessedCommands.SwitchCommands 86 | { 87 | get { return SwitchCommands.ToList(); } 88 | } 89 | 90 | IList SwitchCommands { get; private set; } 91 | } 92 | ``` 93 | 94 | In this way, the client of the interface cannot call `Add()` or `Remove()` on the list of commands, but the provider has the convenience of doing so as it prepares the backing object. 95 | 96 | Using Linq, the client is free to convert the result to a list using `ToList()`, but will create its own copy, leaving the `SwitchCommands` on the object represented by the interface unaffected. 97 | 98 | Note, though, that the implementation of `IProcessedCommands.SwitchCommands` calls `ToList()` to avoid passing its internal representation back to the caller. If it didn't do this, then caller could alter the internal state with the following code. 99 | 100 | ```csharp 101 | var processedCommands = GetProcessedCommands(); 102 | var switches = processedCommands.SwitchCommands as IList; 103 | if (switches != null) 104 | { 105 | switches.Clear(); 106 | } 107 | ``` 108 | 109 | ## Single-Responsibility Principle 110 | 111 | Design types so that the caller cannot use them incorrectly. 112 | 113 | The example below shows a typical class indicates usage in documentation rather than the API. 114 | 115 | ```csharp 116 | /// 117 | /// Make sure to set the , 118 | /// and before calling . Call before calling and . 119 | /// 120 | public interface IBackEnd 121 | { 122 | string ServerName { get; set; } 123 | string UserName { get; set; } 124 | string Password { get; set; } 125 | void Connect(); 126 | void LogIn(); 127 | void RunTask([NotNull] ITask task); 128 | } 129 | 130 | internal static Main() 131 | { 132 | var backEnd = CreateBackEnd(); 133 | var tasks = GetTasks(); 134 | 135 | backEnd.ServerName = GetServerName(); 136 | backEnd.UserName = GetUserName(); 137 | backEnd.Password = GetPassword(); 138 | backEnd.Connect(); 139 | backEnd.LogIn(); 140 | 141 | foreach (var task in tasks) 142 | { 143 | backEnd.RunTask(task); 144 | } 145 | } 146 | ``` 147 | 148 | The block in `Main()` _should_ always look the same, but the pattern is not enforced. 149 | 150 | Instead of a single `IBackEnd` type with multiple methods, move the configuration parameters to separate objects and define _three_ interfaces, each of which has a single responsibility: 151 | 152 | ```csharp 153 | interface IDisconnectedBackEnd 154 | { 155 | [NotNull] 156 | IConnectedBackEnd Connect([NotNull] IConnectionSettings settings); 157 | } 158 | 159 | interface IConnectedBackEnd 160 | { 161 | [NotNull] 162 | ILoggedInBackEnd LogIn([NotNull] IUser user); 163 | } 164 | 165 | interface ILoggedInBackEnd 166 | { 167 | void RunTask([NotNull] ITask task); 168 | } 169 | 170 | internal static Main() 171 | { 172 | var settings = GetConnectionSettings(); 173 | var user = GetUser(); 174 | var tasks = GetTasks(); 175 | 176 | var backEnd = 177 | CreateDisconnectedBackEnd() 178 | .Connect(settings) 179 | .LogIn(user); 180 | 181 | foreach (var task in tasks) 182 | { 183 | backEnd.RunTask(task); 184 | } 185 | } 186 | ``` 187 | 188 | In this case, the code in `Main()` will still always look the same, _but it can now no longer take any other shape_. The caller _cannot_ call `LogIn()` without having called `Connect()` first. With these types, a method can require a `IConnectedBackEnd` or `ILoggedInBackEnd` rather than a generic `IBackEnd` in an unknown state. 189 | 190 | For example, we can extract a method for running tasks where the type of the parameter enforces the state of the back end. 191 | 192 | ```csharp 193 | internal static Main() 194 | { 195 | var settings = GetConnectionSettings(); 196 | var user = GetUser(); 197 | var tasks = GetTasks(); 198 | 199 | var backEnd = 200 | CreateDisconnectedBackEnd() 201 | .Connect(settings) 202 | .LogIn(user); 203 | 204 | RunTasks(backEnd, tasks); 205 | } 206 | 207 | private static void RunTasks([NotNull] ILoggedInBackEnd backEnd, [NotNull] IEnumerable tasks) 208 | { 209 | if (backEnd != null) { throw new ArgumentNullException(nameof(backEnd)); } 210 | if (tasks != null) { throw new ArgumentNullException(nameof(tasks)); } 211 | 212 | foreach (var task in tasks) 213 | { 214 | backEnd.RunTask(task); 215 | } 216 | } 217 | ``` 218 | 219 | ## Loose vs. Tight Coupling 220 | 221 | Whether to use loose or tight coupling for components depends on several factors. If a component on a lower-level must access functionality on a higher level, then you're doing something wrong. 222 | 223 | ### Callbacks vs. Interfaces 224 | 225 | Both callbacks and interfaces can be used to connect components in a loosely-coupled way. A delegate is more loosely-coupled than an interface because it specifies the absolute minimum amount of information needed in order to inter-operate whereas an interface forces the implementing component to satisfy a set of clearly-defined functionality. 226 | 227 | If the bridge between two components is truly that of an event sink communicating with an event listener, then you should use event handlers and delegates to communicate. However, if you start to have many such delegate connections between two components, you’ll want to improve clarity by defining an interface to more completely describe this relationship. 228 | 229 | Another consideration is the prevailing model of the surrounding components. For example, the Windows Forms components and designer use events exclusively and so should your components if they are to integrate with the designer. 230 | 231 | In other cases, where you have a handful of events that are highly interrelated, it is useful to create an interface so that components can be sure that they are handling the events correctly (and not forgetting to handle one or the other). 232 | 233 | ### An Example 234 | 235 | If the component on the higher level needs to be coupled to a component on a lower level, then it’s possible to have them be more tightly coupled by using an interface. The advantage of using an interface over a set or one or more callbacks is that changes to the semantics of how the coupling should occur can be enforced. The example below should make this much clearer. 236 | 237 | Imagine a class that provides a single event to indicate that it has received data from somewhere. 238 | 239 | ```csharp 240 | public class DataTransmitter 241 | { 242 | public event EventHandler DataReceived; 243 | } 244 | ``` 245 | 246 | This is the class way of loosely coupling components; any component that is interested in receiving data can simply attach to this event, like this: 247 | 248 | ```csharp 249 | public class DataListener 250 | { 251 | public DataListener(DataTransmitter transmitter) 252 | { 253 | transmitter.DataReceived += TransmitterDataReceived; 254 | } 255 | 256 | private TransmitterDataReceived(object sender, DataBundleEventArgs args) 257 | { 258 | // Do something when data is received 259 | } 260 | } 261 | ``` 262 | 263 | Another class could combine these two classes in the following, classic way: 264 | 265 | ```csharp 266 | var transmitter = new DataTransmitter(); 267 | var listener = new DataListener(transmitter); 268 | ``` 269 | 270 | The transmitter and listener can be defined in completely different assemblies and need no dependency on any common code (other than the .NET runtime) in order to compile and run. If this is an absolute _must_ for your component, then this is the pattern to use for all events. Just be aware that the loose coupling may introduce _semantic_ errors—errors in usage that the compiler will not notice. 271 | For example, suppose the transmitter is extended to include a new event, NoDataAvailableReceived. 272 | 273 | ```csharp 274 | public class DataTransmitter 275 | { 276 | public event EventHandler DataReceived; 277 | public event EventHandler NoDataAvailableReceived; 278 | } 279 | ``` 280 | 281 | Let’s assume that the previous version of the interface threw a timeout exception when it had not received data within a certain time window. Now, instead of throwing an exception, the transmitter triggers the new event instead. The code above will no longer indicate a timeout error (because no exception is thrown) nor will it indicate that no data was transmitted. 282 | 283 | One way to fix this problem (once detected) is to hook the new event in the `DataListener` constructor. If the code is to remain highly decoupled—or if the interface cannot be easily changed—this is the only real solution. 284 | 285 | Imagine now that the transmitter becomes more sophisticated and defines more events, as shown below. 286 | 287 | ```csharp 288 | public class DataTransmitter 289 | { 290 | public event EventHandler DataReceived; 291 | public event EventHandler NoDataAvailableReceived; 292 | public event EventHandler ConnectionOpened; 293 | public event EventHandler ConnectionClosed; 294 | public event EventHandler ErrorOccurred; 295 | } 296 | ``` 297 | 298 | Clearly, a listener that attaches and responds appropriately to _all_ of these events will provide a much better user experience than one that does not. The loose coupling of the interface thus far requires all clients of this interface to be proactively aware that something has changed and, once again, the compiler is no help at all. 299 | 300 | If we can change the interface—and if the components can include references to common code—then we can introduce tight coupling by defining an interface with methods instead of individual events. 301 | 302 | ```csharp 303 | public interface IDataListener 304 | { 305 | void DataReceived(IDataBundle bundle); 306 | void NoDataAvailableReceived(); 307 | void ConnectionOpened(); 308 | void ConnectionClosed(); 309 | void ErrorOccurred(Exception exception, string message); 310 | } 311 | ``` 312 | 313 | With a few more changes, we have a more tightly coupled system, but one that will enforce changes on clients: 314 | 315 | * Add a list of listeners on the `DataTransmitter` 316 | * Add code to copy and iterate the listener list instead of triggering events from the `DataTransmitter`. 317 | * Make `DataListener` implement `IDataListener` 318 | * Add the listener to the transmitter’s list of listeners. 319 | 320 | Now when the transmitter requires changes to the `IDataListener` interface, the compiler will required that all listeners also be updated. 321 | 322 | ## Methods vs. Properties 323 | 324 | Use methods instead of properties in the following situations: 325 | 326 | * For transformations or conversions, like `ToXml()` or `ToSql()`. 327 | * If the value of the property is not cached internally, but is expensive to calculate, indicate this with a method call instead of a property (properties generally give the impression that they reference information stored with the object). 328 | * If the result is not idempotent (yields the same result no matter how often it is called), it should be a method. 329 | * If the property returns a copy of an internal state rather than a direct reference; this is especially significant with array properties, where repeated access is very inefficient. 330 | * When a getter is not desired, use a method instead of a set-only property. 331 | 332 | For all other situations in which both a property and a method are appropriate, properties have the following advantages over methods: 333 | 334 | * Properties don’t require parentheses and result in cleaner code when called (especially when many calls are chained together). 335 | * It clearly indicates that the value is a logical property of the construct instead of an operation. 336 | 337 | ## Code > Comments 338 | 339 | Good variable and method names go a long way to making comments unnecessary. 340 | 341 | Replace comments with private methods with descriptive names. For example, the following code is commented, but a bit wordy. 342 | 343 | ```csharp 344 | public void MethodOne() 345 | { 346 | // Collect and aggregate results 347 | var projections = new List(); 348 | foreach (var p in OriginalProjections) 349 | { 350 | // Do a bunch of stuff with p 351 | projections.Add(new Projection(p, i)) 352 | } 353 | 354 | // Format the projections into a text report 355 | var lines = new List(); 356 | foreach (var projection in projections) 357 | { 358 | var line = string.Format($"Some text with a {projection}"); 359 | 360 | // Work with the line 361 | 362 | lines.Add(line); 363 | } 364 | 365 | // Save the lines to file 366 | File.WriteAllLines(OutputPath, lines); 367 | } 368 | ``` 369 | 370 | Instead, use methods to achieve the same clarity without comments. At the same time, we make use of better types (`IEnumerable`) and constructs (`Select()`, `NotNull`) to streamline and improve the code even more. 371 | 372 | ```csharp 373 | public void MethodOne() 374 | { 375 | var projections = CalculateFinalProjections(); 376 | var lines = CreateReport(projections); 377 | 378 | StoreReport(lines); 379 | } 380 | 381 | [NotNull] 382 | private IEnumerable CalculateFinalProjections() 383 | { 384 | return OriginalProjections.Select(CreateFinalProjection); 385 | } 386 | 387 | [NotNull] 388 | private Projection CreateFinalProjection([NotNull] Projection p) 389 | { 390 | if (p == null) { throw new ArgumentNullException(nameof(p)); } 391 | 392 | // Do a bunch of stuff with p 393 | 394 | return new Projection(p, i)); 395 | } 396 | 397 | [NotNull] 398 | private IEnumerable CreateReport([NotNull] IEnumerable projections) 399 | { 400 | if (projections == null) { throw new ArgumentNullException(nameof(projections)); } 401 | 402 | return projections.Select(p => string.Format($"Some text with a {p}")); 403 | } 404 | 405 | private void StoreReport([NotNull] IEnumerable lines) 406 | { 407 | if (lines == null) { throw new ArgumentNullException(nameof(lines)); } 408 | 409 | File.WriteAllLines(OutputPath, lines); 410 | } 411 | ``` 412 | This version obviously needs no comments: it is now clear what `MethodOne` does. In fact, we can streamline `MethodOne` to a single line without losing legibility. Using the syntactic constructs of the language eliminates the need for many, if not all, comments. 413 | ```csharp 414 | public void MethodOne() 415 | { 416 | StoreReport(CreateReport(CalculateFinalProjections())); 417 | } 418 | ``` 419 | 420 | ### Convert to Variables 421 | 422 | Replace comments with local variables with descriptive names. 423 | 424 | ```csharp 425 | // For new lessons that are within the time-span or not scheduled 426 | if (!lesson.Stored && ((StartTime <= lesson.StartTime && lesson.EndTime <= EndTime) || !lesson.Scheduled)) { } 427 | ``` 428 | With local variables, however, the logic is much clearer and no comment is required: 429 | ```csharp 430 | bool lessonInTimeSpan = StartTime <= lesson.StartTime && lesson.EndTime <= EndTime; 431 | 432 | if (!lesson.Stored && (lessonInTimeSpan || !lesson.Scheduled)) { } 433 | ``` -------------------------------------------------------------------------------- /3_usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## Structure 4 | 5 | ### Assemblies 6 | 7 | * Use a separate assembly to improve decoupling and reduce dependencies. 8 | * Top-level application assemblies should have as little code as possible. Most logic goes in class libraries. 9 | 10 | The example below illustrates the projects for a solution called “Calculator” with a _WPF_ application, a web-API application and a console application. 11 | * `Calculator.Core` 12 | * `Calculator.Core.Web` 13 | * `Calculator.Core.Wpf` 14 | * `Calculator.Web.Api` 15 | * `Calculator.Wpf` 16 | * `Calculator.Console` 17 | 18 | The first three define libraries of functionality that is used by the next four applications. The server and console only use the `Calculator.Core` library whereas the _Winform_ and _WPF_ applications use their respective libraries. Separating the renderer-dependent code into a separate library makes it much easier to add another application using the same renderer but performing a slightly different task. Only highly application-dependent code should be defined directly in an application project. 19 | 20 | ### Files 21 | 22 | * Place each type (classes, interfaces, enums, etc.) in a separate file. 23 | * Each file should include a header, with the following form: 24 | ```csharp 25 | // 26 | // Copyright (c) 2017 Encodo Systems AG. All rights reserved. 27 | // 28 | // 29 | // This file is subject to the terms and conditions defined in the 'LICENSE' file. 30 | // 31 | ``` 32 | A solution should include licensing conditions in a file named `LICENSE`. 33 | * Namespace `using` statements should go at the very top of the file, just after the header and just before the namespace declaration. 34 | * The namespaces at the top of the file should be in alphabetical order, with the exception that `System.*` assemblies come first. 35 | 36 | ### Namespaces 37 | 38 | #### Referencing 39 | 40 | * Do not use the global namespace. 41 | * Avoid fully-qualified type names; use the `using` statement instead. 42 | * Use aliases to resolve ambiguities. 43 | * Use static classes where they improve clarity of code. 44 | 45 | #### Defining 46 | 47 | * Put abstract/high-level types in outer namespaces (e.g. `Encodo.Quino.Data`). 48 | * Put concrete types in inner ones (e.g. `Encodo.Quino.Data.Ado`). 49 | * Group types in specific namespaces. 50 | * Avoid deep hierarchies, as they are more difficult to browse and understand. 51 | * For general-use types, it's OK to use “Utilities”, “Core” or “General”. 52 | * Refactor types into new namespaces if a clear presents itself. 53 | 54 | ## Types 55 | 56 | ### Classes 57 | 58 | * Declare at most one field per line. 59 | * Do not use public or protected fields; use properties instead. 60 | 61 | #### Abstract Classes 62 | 63 | * Define constructors for abstract classes as `protected`. 64 | * Consider providing a partial implementation of an abstract class that handles some of the abstraction in a standard way; implementors can use this class as a base and avoid having to repeat code in their own implementations. 65 | 66 | #### Static Classes 67 | 68 | * Use static classes only for extension methods _or_ constants, but not both. 69 | * Group functionality into logical static classes. 70 | 71 | #### Inner Classes 72 | 73 | * Inner classes should be `private` or `protected`. 74 | * Inner types should not replace namespaces for organization. 75 | * Use nested types if the inner type is logically within the other type (e.g. a `TableOfContents` class may have an `Options` inner class or a `Builder` inner class). 76 | * Use an inner class to group private or protected constants. 77 | 78 | Alternatives to consider: 79 | 80 | * Consider using composition to inject the class instead, to allow customization and testing. 81 | * Consider _local methods_ as an alternative to a `private` class. 82 | 83 | #### Partial Classes 84 | 85 | To control file size, partial classes can be useful to separate 86 | 87 | * Generated code. 88 | * `private` or `protected` inner classes. 89 | * larger blocks of `private` or `protected` methods. 90 | 91 | ### Interfaces 92 | 93 | #### Design 94 | 95 | * Use interfaces to clearly define your API surface, separate from implementation. 96 | * Use interfaces so that tests can mock behavior and test more precisely. 97 | * Remove interfaces that are not used outside of testing code. 98 | * Always create an interface for composed objects (i.e. those that are injected into other constructors) to ease testing and mocking. 99 | * An interface should be as concise as possible. This allows consumers to precisely mock or override functionality. 100 | * Remember the single-responsibility principle. 101 | * If the standard implementation of a method can always be written in terms of other interface methods, consider defining an extension method for the interface instead. This is a nice way of providing a default implementation for all implementors. 102 | * Avoid _marker_ interfaces. Attributes are a more appropriate way to mark types without _changing_ the type. 103 | 104 | #### Usage 105 | 106 | * Avoid similar interfaces that cause confusion as to which one should be used where. Re-use interfaces wherever possible and appropriate. 107 | * Provide a standard implementation or an abstract base. This provides both an implementation example and some protection from future changes to the interface. 108 | * Use explicit interface implementation where appropriate to avoid expanding a class API unnecessarily. 109 | 110 | ### `structs` 111 | 112 | Consider defining a structure instead of a class if most of the following conditions apply: 113 | 114 | * Instances of the type are small (16 bytes or less) and commonly short-lived. 115 | * The type is commonly embedded in other types. 116 | * The type logically represents a single value and is similar to a primitive type, like an `int` or a `double`. 117 | * The type is immutable. 118 | * The type will not be boxed frequently. 119 | 120 | Use the following rules when defining a `struct`. 121 | 122 | * Avoid methods; at most, have only one or two methods other than equality overrides and operator overloads. 123 | * Provide parameterized constructors for initialization. 124 | * Overload operators and equality as expected; implement `IEquatable` instead of overriding `Equals` in order to avoid the negative performance impact of boxing and un-boxing the value. 125 | * A `struct` should be valid when uninitialized so that consumers can declare an instance without calling a constructor. 126 | * Public fields are allowed (even encouraged) for structures used to communicate with external APIs through unmanaged code. 127 | 128 | ## Generics 129 | 130 | * Use generic collection types (e.g. use `IList` instead of `IList`). 131 | * Use generic constraints instead of casting or using the `is`-operator. 132 | * Use interfaces as generic constraints wherever possible. 133 | * When inheriting from both a generic and non-generic interface (e.g. `IEnumerable` and `IEnumerable`), implement the non-generic version explicitly and implement it using the generic interface. 134 | 135 | ### `enums` 136 | 137 | #### Design 138 | 139 | * Use enumerations for strongly typed sets of values 140 | * Use a singular name (e.g. `MigrationPhase` instead of `MigrationPhases`) 141 | * Use enumerations for a list of constants that is not logically open-ended; otherwise, use a `static` class with constants so that consumers can extend the list. 142 | * Use the default type of `Int32` whenever possible. 143 | * Do not include sentinel values, such as `FirstValue` or `LastValue`. 144 | * The first value in an enumeration is the default; make sure that the most appropriate simple enumeration value is listed first. 145 | * Do not assign explicit values except to enforce specific values for storage in a database or to match an external API. 146 | 147 | #### Usage 148 | 149 | * Enumerations are like interfaces; be extremely careful of changing them when they are already included in code that is not under your control (e.g. used by a framework that is, in turn, used by external application code). If the enumeration must be changed, use the `ObsoleteAttribute` to mark members that are no longer in use. 150 | 151 | #### Bit-sets 152 | 153 | * Use the `[Flags]` attribute to make a bit-set instead of a simple enumeration. 154 | * Use plural names for bit-sets. 155 | * Assign explicit values for bit-sets in powers of two; use hexadecimal notation. 156 | * The first value of a bit-set should always be `None` and equal to `0x00`. 157 | * In bit-sets, feel free to include commonly-used aliases or combinations of flags to improve readability and maintainability. One such common value is `All`, which includes all available flags and, if included, should be defined last. For example: 158 | ```csharp 159 | [Flags] 160 | public enum QuerySections 161 | { 162 | None = 0x00, 163 | Select = 0x01, 164 | From = 0x02, 165 | Where = 0x04, 166 | OrderBy = 0x08, 167 | NotOrderBy = All & ~OrderBy, 168 | All = Select | From | Where | OrderBy, 169 | } 170 | ``` 171 | The values `NotOrderBy` and `All` are aliases defined in terms of the other values. Note that the elements here are not aligned because it is expected that they will be documented, in which case column-alignment won’t make a difference in legibility. 172 | * Avoid designing a bit-set when certain combinations of flags are invalid; in those cases, consider dividing the enumeration into two or more separate enumerations that are internally valid. 173 | 174 | ## Members 175 | 176 | ### Modifiers 177 | 178 | * The visibility modifier is required for all types, methods and fields; this makes the intention explicit and consistent. 179 | * The visibility keyword is always the first modifier. 180 | * The `const` or `readonly` keyword, if present, comes immediately after the visibility modifier. 181 | * The static keyword, if present, comes after the visibility modifier and readonly modifier. 182 | ```csharp 183 | private readonly static string DefaultDatabaseName = "admin"; 184 | ``` 185 | 186 | #### `sealed` 187 | 188 | * Do not declare protected or virtual members on sealed classes 189 | * Avoid sealing classes unless there is a very good reason for doing so (e.g. to improve reflection performance). 190 | * Consider sealing only selected members instead of sealing an entire class. 191 | * Consider sealing members that you have overridden if you don’t want descendants to avoid your implementation. 192 | 193 | #### `internal` 194 | 195 | * Wherever possible, make implementation classes `internal` to reduce the surface area of the API. 196 | * Prefer `private` and `protected` to `internal` methods. 197 | * Instead of using `internal` to mean _assembly-local_, use composition to provide access to shared functionality 198 | 199 | Most historical uses for `internal` methods can be implemented with other, better patterns. One use is to allow overriding of a method inside an assembly, but not outside. Two questions: why are you overriding instead of composing? And, if it's useful for your library or framework to be able to override, why deny this to consumers? 200 | 201 | ### Declaration Order 202 | 203 | * Constructors, in descending order of complexity 204 | * public constants 205 | * public properties 206 | * public methods 207 | * protected constants 208 | * protected properties 209 | * protected methods 210 | * private constants 211 | * private properties 212 | * private methods 213 | * private fields 214 | 215 | ### Constants 216 | 217 | * Declare all constants other than `0`, `1`, `true`, `false` and `null`. 218 | * Use `true` and `false` only for assignment, never for comparison. 219 | * Avoid passing `true` or `false` for parameters; use an `enum` or constants to impart meaning instead. 220 | * If there is a logical connection between two constants, indicate this by making the initialization of one dependent on the other. 221 | ```csharp 222 | public const int DefaultCacheSize = 25; 223 | public const int DefaultGranularity = DefaultCacheSize / 5; 224 | ``` 225 | * Use `const` only when the value really is constant (e.g. `NumberDaysInWeek`); otherwise, use `readonly`. 226 | * Use `readonly` as much as possible. 227 | 228 | ### Constructors 229 | 230 | * Do not include a call to the default `base()` 231 | * Avoid doing more than setting properties in a constructor; provide a well-named method on the class to perform any extra work after the object has been constructed. 232 | * Avoid calling virtual methods from a constructor. The initialization order for constructors can lead to crashes. The example below illustrates this problem, where the override `CaffeineAddict.GoToWork()` uses Coffee before it has been initialized. 233 | ```csharp 234 | public abstract class Employee 235 | { 236 | public Employee() 237 | { 238 | Notify(); 239 | } 240 | 241 | protected abstract void Notify(); 242 | } 243 | 244 | public class CaffeineAddict : Employee 245 | { 246 | public CaffeineAddict([NotNull] Employee boss) 247 | : base() 248 | { 249 | if (boss == null) { throw new ArgumentNullException(nameof(beverage)); } 250 | 251 | Boss = boss; 252 | } 253 | 254 | [NotNull] 255 | public Employee Boss { get; } 256 | 257 | protected override void Notify() 258 | { 259 | // Crashes when called from the constructor 260 | Boss.Notify(); 261 | } 262 | } 263 | ``` 264 | * Constructors should "funnel" so that initialization code is written only once. For example: 265 | ```csharp 266 | protected Query() 267 | { 268 | _restrictions = new List(); 269 | _sorts = new List(); 270 | } 271 | 272 | public Query([NotNull] IMetaClass model) 273 | : this() 274 | { 275 | if (model == null) { throw new ArgumentNullException(nameof(model)); } 276 | 277 | Model = model; 278 | } 279 | 280 | public Query([NotNull] IDataRelation relation) 281 | : this() 282 | { 283 | if (relation == null) { throw new ArgumentNullException(nameof(relation)); } 284 | 285 | Relation = relation; 286 | } 287 | ``` 288 | 289 | ### Properties 290 | 291 | * Prefer immutable properties. 292 | * Prefer automatic properties. 293 | * Prefer getter-only auto-properties. 294 | * Prefer auto-property initializers. 295 | 296 | #### Declaration 297 | 298 | For example, the first version below is not only verbose, but B is mutable (within the class): 299 | 300 | ```csharp 301 | // Do not use this style 302 | public class A 303 | { 304 | A() 305 | { 306 | B = 1; 307 | } 308 | 309 | int B { get; private set; } 310 | } 311 | ``` 312 | 313 | This is also quite verbose, but B is now read-only: 314 | 315 | ```csharp 316 | public class A 317 | { 318 | int B 319 | { 320 | get { return _b; } 321 | } 322 | 323 | private readonly int _b = 1; 324 | } 325 | ``` 326 | 327 | Finally, this is the recommended syntax (C#6 and higher): 328 | 329 | ```csharp 330 | public class A 331 | { 332 | int B { get; } = 1; 333 | } 334 | ``` 335 | 336 | #### Commutativity 337 | 338 | Properties should be commutative; that is, it should not matter in which order you set them. Avoid enforcing an ordering by using a method to execute code that you would want to execute from the property setter. The following example is incorrect because setting the password before setting the name causes a login failure. 339 | 340 | ```csharp 341 | class SecuritySystem 342 | { 343 | private string _userName; 344 | 345 | public string UserName 346 | { 347 | get { return _userName; } 348 | set { _userName = value; } 349 | } 350 | 351 | private int _password; 352 | 353 | public int Password 354 | { 355 | get { return _password; } 356 | set 357 | { 358 | _password = value; 359 | LogIn(); 360 | } 361 | } 362 | 363 | protected void LogIn() 364 | { 365 | IPrincipal principal = Authenticate(UserName, Password); 366 | } 367 | 368 | private IPrincipal Authenticate(string UserName, int Password) 369 | { 370 | // Authenticate the user 371 | } 372 | } 373 | ``` 374 | 375 | Instead, you should take the call LogIn() out of the setter for Password and make the method public, so the class can be used like this instead: 376 | 377 | ```csharp 378 | var system = new SecuritySystem() 379 | { 380 | Password = "knock knock"; 381 | UserName = "Encodo"; 382 | } 383 | system.LogIn(); 384 | ``` 385 | 386 | In this case, Password can be set before the UserName without causing any problems. 387 | 388 | ### Indexers 389 | 390 | * Avoid indexers. They are difficult to navigate, even with good tools. Use well-named methods instead (e.g. `Get*()` and `Set*()`). 391 | * Provide an indexed property only if it really makes sense. 392 | * Indexes should be 0-based. 393 | 394 | ### Methods 395 | 396 | * Methods should not exceed a cyclomatic complexity of 20. 397 | * Prefer private methods. 398 | * Use the same names and positions for parameters shared by similar methods. 399 | * Avoid returning `null` for methods that return collections or strings. Instead, return an empty collection (declare a static empty list) or an empty string (`String.Empty`). 400 | * Methods that are explicitly left empty should be marked with NOP: 401 | ```csharp 402 | protected override void DoBeforeSave() 403 | { 404 | // NOP 405 | } 406 | ``` 407 | 408 | ### Extension Methods 409 | 410 | * Use extension methods for methods that can be defined in terms of public members of that interface. 411 | * Do so only for methods for which the implementation is certain to be the same for all implementations. That is, do not restrict an implementation’s efficiency because an extension instead of interface method was used. 412 | * Do not extend `object` or `string` in commonly used namespaces. 413 | * Do not mix extension methods with other static methods. If a class contains extension methods, it should contain only extension methods and private support methods. 414 | * Do not mix extension methods for different types in one class. 415 | * Define useful, but more rarely used extension methods in a separate namespace or assembly to force callers to "opt in". 416 | 417 | #### Bodies 418 | 419 | * Do not _make decisions_ in extension methods. Instead, declare components and inject them where needed. 420 | * Do not use static code that does _make decisions_ in extension methods. 421 | * Do not use a global service locator in extension methods. 422 | * Do not pass an IOC to extension methods. 423 | 424 | ### Parameters 425 | 426 | * Use at most 5 parameters per method. Otherwise, use a `class`, `struct` or `tuple`. 427 | * Use at most 1 `out` or `ref` parameter. Otherwise, return a `class`, `struct` or `tuple`. 428 | * A `ref` and `out` parameter belongs at the end of the list of non-optional parameters. 429 | * Use the same name for parameters in interface implementations or overrides. 430 | * Avoid re-assigning the value of a parameter. Instead, use a local variable. 431 | * A valid re-assignment is for nullable parameters, of the form shown below: 432 | ```csharp 433 | void Apply([NotNull] IMigrationPlan plan, [CanBeNull] ILogger logger = null) 434 | { 435 | if (plan != null) { throw new ArgumentNullException(nameof(plan)); } 436 | 437 | logger = logger ?? NullLogger.Default; 438 | 439 | // ... 440 | } 441 | ``` 442 | 443 | ### Optional Parameters 444 | 445 | * Do not use optional parameters in constructors. 446 | * Do not use optional parameters that might change in public APIs; instead, use overloaded methods. 447 | * Do not use more than one or two optional parameters, even for internal APIs. 448 | 449 | This blog post [Optional argument corner cases, part four](http://blogs.msdn.com/b/ericlippert/archive/2011/05/19/optional-argument-corner-cases-part-four.aspx) by Eric Lippert discusses the problem in more detail. 450 | 451 | > [Optional arguments can lead to] fairly serious versioning issue[s]. [...] The lesson here is to think carefully about the scenario with the long term in mind. If you suspect that you will be changing a default value and you want the callers to pick up the change without recompilation, don't use a default value in the argument list; make two overloads, where the one with fewer parameters calls the other. 452 | 453 | ### Expression-bodied Members 454 | 455 | * Use expression-bodied members for simple properties and methods. 456 | * The same rules apply as for any other expression; use a standard property or method body for complex logic. 457 | * Do not use expression-bodied members for constructors and finalizers. 458 | 459 | ### `tuples` 460 | 461 | * Do not use tuples with more than 3 fields. 462 | * In C# 7 or higher, consider using a `Tuple` instead of an `out` parameter. 463 | * In C# 6 or lower, use the Try* pattern because tuple fields cannot have names. 464 | * Avoid `tuple` return types for public APIs 465 | 466 | #### Naming 467 | 468 | * Always name the parameters in a tuple. 469 | * If the method returns a single constant tuple, then specify the names there (to keep the method declaration shorter). If there are several exit points, don't repeat the names in the literal tuples; instead, include the names only in the return-type declaration. 470 | * Prefer external `var (first, second, third)` declaration to the internal one `(var first, var second, var third)` 471 | 472 | #### Deconstruction 473 | 474 | * Provide a custom `deconstruct` method for structs. 475 | * Match the field order in a class's constructor, ToString override, and Deconstruct method. 476 | * Use deconstruction where appropriate to consume tuples. If the tuple has named members, then you can just use it; otherwise, use deconstruction to assign the members to variables with logical names. 477 | 478 | ### Overloads 479 | 480 | * Use overloads for methods that have similar behavior. Do not include parameter names in the method name. For example, the following is incorrect 481 | ```csharp 482 | void Update(); 483 | void UpdateUsingQuery(IQuery query); 484 | void UpdateUsingSql(string sql); 485 | ``` 486 | The overloaded version below reduces the perceived size of the API and makes it easier to understand. 487 | ```csharp 488 | void Update(); 489 | void Update(IQuery query); 490 | void Update(string sql); 491 | ``` 492 | * Avoid putting a lot of logic in overloaded methods. 493 | * Try to make overloads "funnel" to a single overload or other method. 494 | * Make at most one overload `virtual`. For example: 495 | ```csharp 496 | public void Update() 497 | { 498 | Update(QueryTools.NullQuery); 499 | } 500 | 501 | public void UpdateUsingSql(string sql) 502 | { 503 | Update(new Query(sql)); 504 | } 505 | 506 | public virtual void Update(IQuery query) 507 | { 508 | // Perform update 509 | } 510 | ``` 511 | 512 | ### Virtual 513 | 514 | * `virtual` is a code smell; consider _composition_ as an alternative. 515 | * Avoid `public virtual` methods, but do not create an extra layer of method call either. 516 | * If a method has logical pre-conditions or post-conditions, consider wrapping a `protected virtual` method in a `public` method, as shown below: 517 | ```csharp 518 | public void Update(IQuery query) 519 | { 520 | if (query == null) { throw new ArgumentNullException(nameof(query)); } 521 | if (!query.Valid) { throw new ArgumentException("Query is not valid.", nameof(query)); } 522 | if (!query.Updatable) { throw new ArgumentException("Query is not updatable.", nameof(query)); } 523 | 524 | DoUpdate(query); 525 | 526 | if (!query.UpToDate) { throw new ArgumentException("Query should have been updated.", nameof(query)); } 527 | } 528 | 529 | protected virtual void DoUpdate(IQuery query) 530 | { 531 | // Perform update 532 | } 533 | ``` 534 | Use a `Do` or `Internal` prefix for these methods. 535 | * Wrap multiple parameters in an "arguments" class to avoid changing the signature when more data is needed in future versions. 536 | 537 | ### `new` Properties 538 | 539 | * Do not use the `new` keyword to force overrides; use `override` instead or restructure the code to avoid it. 540 | 541 | ### Event Handlers 542 | 543 | Be aware of the following when raising events. 544 | 545 | * Event handlers can affect performance. 546 | * Event handlers can change the calling object. 547 | * Event handlers can throw exceptions. 548 | * Event handlers are not guaranteed to run in the calling thread. 549 | 550 | #### Alternatives 551 | 552 | * Do not use event handlers other than in legacy code (e.g. Winform) 553 | * For user interfaces, use the MVVM pattern instead 554 | * For back-end objects, use messaging or event-aggregator patterns 555 | 556 | #### Rules 557 | 558 | * Declare events using the `event` keyword. 559 | * Do not use delegate members. 560 | * Use delegate inference instead of writing `new EventHandler(…)`. 561 | * Declare events with `EventHandler`. 562 | * An event has two parameters named `sender` of type `object` and `args` with an event-specific type. 563 | * Do not allow `null` for either the `sender` or the `args` parameters. 564 | * Use `EventArgs` as the base class for custom arguments. 565 | * Use `CancelEventArgs` as the base class if you need to be able to cancel an event. 566 | * Custom arguments should include only properties, but no logic. 567 | 568 | #### Race conditions 569 | 570 | To avoid null-reference exceptions, get a reference to the handler in a local variable before checking it and calling it. The so-called "elvis" operator in C# 6 and higher is recommended. 571 | 572 | ```csharp 573 | protected virtual void RaiseMessageDispatched() 574 | { 575 | MessageDispatched?.(this, EventArgs.Empty); 576 | } 577 | ``` 578 | 579 | For C# 5 and lower, use: 580 | 581 | ```csharp 582 | protected virtual void RaiseMessageDispatched() 583 | { 584 | EventHandler handler = MessageDispatched; 585 | if (handler != null) 586 | { 587 | handler(this, EventArgs.Empty); 588 | } 589 | } 590 | ``` 591 | 592 | The following code is an example of a simple event handler and receiver. 593 | 594 | ```csharp 595 | public class Safe 596 | { 597 | public event EventHandler Locked; 598 | 599 | public void Lock() 600 | { 601 | // Perform work 602 | 603 | RaiseLocked(EventArgs.Empty); 604 | } 605 | 606 | protected virtual void RaiseLocked(EventArgs args) 607 | { 608 | Locked?.(this, args); 609 | } 610 | } 611 | 612 | public static class StoreManager 613 | { 614 | private static void SendMailAboutSafe(object sender, EventArgs args) 615 | { 616 | // Respond to the event 617 | } 618 | 619 | public static void TestSafe() 620 | { 621 | Safe safe = new Safe(); 622 | safe.Locked += SendMailAboutSafe; 623 | safe.Lock(); 624 | } 625 | } 626 | ``` 627 | 628 | ### Operators 629 | 630 | #### Caveats 631 | 632 | * Avoid overloading operators in general; it's not appropriate for most problem domains. 633 | * Do not override the `==`-operator for reference types; instead, override the `Equals()` method to avoid redefining reference equality. 634 | * Do not provide a conversion operator unless it can be logically expected by consumers of the API. 635 | 636 | #### Recommendations 637 | 638 | * If an operator is needed, re-use operator conventions from other languages or the problem domain of the API (e.g. mathematical operators) 639 | * If you do override Equals(), you must also override `GetHashCode()`. 640 | * If you do override the == operator, consider overriding the other comparison operators (!=, <, <=, >, >=) as well. 641 | * You should return `false` from the `Equals()` function if the objects cannot be compared. However, if they are different types, you may throw an exception. 642 | * Do not mix and match conversion operators and types. The type `DynamicString` can convert to `System.String` but should not convert to `System.Int32`. Instead, use a constructor to initialize from types that are not in the same domain. 643 | * Use `implicit` operators _only_ where the conversion _cannot_ result in a loss of data or an exception. 644 | * Use an `explicit` operator where data-loss or exceptions are possible. 645 | 646 | ### `ref` Returns and Properties 647 | 648 | `ref` returns are a feature that can improve memory-management in applications that uses larger structures. Taking references avoids copying values where not necessary. 649 | 650 | * Use ref properties for mutable structs. 651 | * Do not use ref properties for immutable structs or read-only classes. 652 | 653 | ## Statements and Expressions 654 | 655 | ### `base` 656 | 657 | * Use `base` only from a constructor or to call a predecessor method. 658 | * You may only call the `base` of the method being executed; do not call other `base` methods. In the following example, the call to `CheckProcess()` is not allowed, whereas the call to `RunProcess()` is. 659 | ```csharp 660 | public override void RunProcess() 661 | { 662 | base.CheckProcess(); // Not allowed 663 | base.RunProcess(); 664 | } 665 | ``` 666 | 667 | ### `this` 668 | 669 | * Use `this` only when referring to other constructors. 670 | * Do not use `this` to resolve name-clashes; instead, change one of the conflicting names. 671 | 672 | ### Value Types 673 | 674 | * Always use the lower-case primitive type. 675 | * Use `int` instead of `Int32` 676 | * Use `string` instead of `String` 677 | * Use `bool` instead of `Boolean` 678 | * Use `short` instead of `Int16` 679 | * Use `byte` instead of `Byte` 680 | * Use `long` instead of `Int64` 681 | * And so on. 682 | 683 | ### Strings 684 | 685 | * Use `string.Empty` instead of `“”`. 686 | * Use `string.IsNullOrEmpty` instead of `s == null` or `s.Length == 0`. 687 | 688 | #### Concatenation 689 | 690 | * Prefer string-interpolation for C#6 or higher. 691 | * Prefer `string.Format` for C#5 or lower. 692 | * Use string-concatenation or `string.Concat` only if you have identified a performance bottleneck. 693 | * Use a `StringBuilder` for more complex situations or when concatenation occurs over multiple statements. 694 | 695 | ### Interpolation 696 | 697 | * Prefer embedding variables rather than expressions. 698 | * Avoid embedding longer expressions. 699 | 700 | The following example uses short expressions and is legible. 701 | 702 | ```csharp 703 | var s = $"The total [{total + shipping}] is higher than the balance [{balance - fees}]."; 704 | ``` 705 | 706 | However, the following interpolated string isn't very easy to read. 707 | 708 | ```csharp 709 | var s = $"The total [{allOrders.Sum(o => o.Total + (o.Tax * o.Vat.Rate)) + shipping}] is higher than the balance [{accounts.Sum(a => a.Balance) - fees}]."; 710 | ``` 711 | 712 | Instead, extract variables to reduce complexity to the previous formulation. 713 | 714 | ```csharp 715 | var total = allOrders.Sum(o => o.Total + (o.Tax * o.Vat.Rate)); 716 | var balance = accounts.Sum(a => a.Balance); 717 | var s = $"The total [{total + shipping}] is higher than the balance [{balance - fees}]."; 718 | ``` 719 | 720 | ### `nameof` 721 | 722 | * Use `nameof` for passing names to `ArgumentExceptions`. 723 | * Use `nameof` wherever possible to avoid constant strings. 724 | 725 | ### Resource Strings 726 | 727 | * Use resources for all localizable strings. 728 | * Do not use resources for strings that will not be localized (e.g. log messages) 729 | * Resource identifiers follow the same rules as all other identifiers. 730 | * Do not waste time localizing strings until code is reviewed and stable. 731 | 732 | ### Floating Point and Integral Types 733 | 734 | * Be careful when comparing floating-point constants; unless you are using `decimals`, the representation will have limited precision and can lead to subtle bugs. 735 | * One exception to this rule is when you are comparing constants of fixed, known value (like `0.0` or `1.0`, but not `3.14`). 736 | * Be careful when casting representations with different sizes (e.g. `long` to `int`). 737 | * Use the literal `_` as a reasonable separator (e.g. to delineate hex groups or as a thousands-separator). 738 | 739 | ### Local Variables 740 | 741 | * Use `var` and initialization wherever possible. 742 | * Declare local variables individually. 743 | * Initialize a local variable on the same line as the declaration 744 | * Use standard line-breaking rules outline elsewhere for longer, fluent initialization. 745 | 746 | ### Local Functions 747 | 748 | * Use local functions for short private methods that are used only once. 749 | * If a local function body must be repeated, then use a private method instead. 750 | * Use local functions instead of anonymous functions. 751 | * Use local iterators when returning an IEnumerator when parameters need to be validated. 752 | * Put local functions at the beginning or end of its containing body. 753 | 754 | ### `var` 755 | 756 | * Use `var` everywhere. 757 | 758 | The justification is that the rest of this handbook encourages a style where: 759 | 760 | * methods are small 761 | * parameters and variables are well-named 762 | 763 | So the types, where relevant, will be obvious from the relatively small and local context. 764 | 765 | The following examples show calls without using `var`. 766 | 767 | ```csharp 768 | IList planes1 = new List(); 769 | IDataList planes2 = connection.GetList(); 770 | IDataList planes3 = hanger.GetAirplanes(connection); 771 | ``` 772 | 773 | In which cases is the type relevant or non-obvious? The following version using `var` only gains in clarity. 774 | 775 | ```csharp 776 | var planes1 = new List(); 777 | var planes2 = connection.GetList(); 778 | var planes3 = hanger.GetAirplanes(connection); 779 | ``` 780 | 781 | ## `out` variables 782 | 783 | * Use `out`-parameter declaration to define parameters. 784 | ```csharp 785 | if (list.TryGetValue("One", var out item)) 786 | { 787 | // use 'item' 788 | } 789 | ``` 790 | For C# 5 and older, use the standalone variable declaration. 791 | ```csharp 792 | Item item; 793 | if (list.TryGetValue("One", out item)) 794 | { 795 | // use 'item' 796 | } 797 | ``` 798 | 799 | ### Loops 800 | 801 | * Do not change the loop variable of a for-loop. 802 | * Update while loop variables either at the beginning or the end of the loop. 803 | * Keep loop bodies short; avoid excessive nesting. 804 | 805 | ### Conditions 806 | 807 | * Do not compare to `true` or `false`. 808 | * Use parentheses only if the precedence isn't relatively obvious 809 | * Use _StyleCop_ or _ReSharper_ to indicate where parentheses are needed and stick to it. 810 | * Initialize Boolean values with simple expressions rather than using an if-statement. 811 | ```csharp 812 | bool needsUpdate = Count > 0 && Objects.Any(o => o.Modified); 813 | ``` 814 | * Always use brackets for flow-control blocks (`switch`, `if`, `while`, `for`, etc.) 815 | * Do not add useless `else` blocks. An `if` statement may stand alone and an `else if` statement may be the last condition. 816 | ```csharp 817 | if (a == b) 818 | { 819 | // Do something 820 | } 821 | else if (a > b) 822 | { 823 | // Do something else 824 | } 825 | // No final "else" required 826 | ``` 827 | * Do not force really complicated logic into an `if` statement; instead, use local variables to make the intent clearer. For example, imagine we have a lesson planner and want to find all unsaved lessons that are either unscheduled or are scheduled within a given time-frame. The following condition is too long and complicated to interpret quickly: 828 | ```csharp 829 | if (!lesson.Stored && ((StartTime <= lesson.StartTime && lesson.EndTime <= EndTime) || !lesson.Scheduled)) 830 | { 831 | // Do something with the lesson 832 | } 833 | ``` 834 | Even trying to apply the line-breaking rules results in an unreadable mess: 835 | ```csharp 836 | if (!lesson.Stored && 837 | ((StartTime <= lesson.StartTime && lesson.EndTime <= EndTime) || 838 | ! lesson.Scheduled)) 839 | { 840 | // Do something with the lesson 841 | } 842 | ``` 843 | Even with this valiant effort, the intent of the ||-operator is difficult to discern. With local variables, however, the logic is much clearer: 844 | ```csharp 845 | bool lessonInTimeSpan = StartTime <= lesson.StartTime && lesson.EndTime <= EndTime; 846 | if (!lesson.Stored && (lessonInTimeSpan || !lesson.Scheduled)) 847 | { 848 | // Do something with the lesson 849 | } 850 | ``` 851 | 852 | ### `switch` Statements 853 | 854 | * Use `throw new UnexpectedEnumException(value)` in the `default` branch. This is more semantically correct than `InvalidEnumArgumentException`, which does not allow you to indicate the unexpected value _and_ erroneously suggests that the value was invalid. 855 | * The following code is correct: 856 | ```csharp 857 | switch (type) 858 | { 859 | case DatabaseType.PostgreSql: 860 | return new PostgreSqlMetaDatabase(); 861 | case DatabaseType.SqlServer: 862 | return new SqlServerMetaDatabase(); 863 | case DatabaseType.SQLite: 864 | return new SQLiteMetaDatabase(); 865 | default: 866 | throw new UnexpectedEnumException(value); 867 | } 868 | ``` 869 | * The `default` label must always be the last label in the statement. In C# 7, the default label is always interpreted last anyway. 870 | 871 | ### `continue` Statements 872 | 873 | * Do not use `continue`. 874 | * The following example is not allowed. 875 | ```csharp 876 | foreach (var search in searches) 877 | { 878 | if (!search.Path.Contains("CN=")) 879 | { 880 | continue; 881 | } 882 | 883 | // Work with valid searches 884 | } 885 | ``` 886 | Instead, use a condition to filter elements. 887 | ```csharp 888 | foreach (var search in searches) 889 | { 890 | if (search.Path.Contains("CN=")) 891 | { 892 | // Work with valid searches 893 | } 894 | } 895 | ``` 896 | Even better, use `Where()` to filter elements. 897 | ```csharp 898 | foreach (var search in searches.Where(s => s.Path.Contains("CN="))) 899 | { 900 | // Work with valid searches 901 | } 902 | ``` 903 | 904 | ### `return` Statements 905 | 906 | * Prefer multiple return statements to local variables and nesting. 907 | ```csharp 908 | if (specialTaxRateApplies) 909 | { 910 | return CalculateSpecialTaxRate(); 911 | } 912 | 913 | return CalculateRegularTaxRate(); 914 | ``` 915 | * Compose smaller methods to avoid local variables for return values. For example, the following method uses a local variable rather than multiple returns. 916 | ```csharp 917 | bool result; 918 | 919 | if (SomeConditionHolds()) 920 | { 921 | PerformOperationsForSomeCondition(); 922 | 923 | result = false; 924 | } 925 | else 926 | { 927 | PerformOtherOperations(); 928 | 929 | if (SomeOtherConditionHolds()) 930 | { 931 | PerformOperationsForOtherCondition(); 932 | 933 | result = false; 934 | } 935 | else 936 | { 937 | PerformFallbackOperations(); 938 | 939 | result = true; 940 | } 941 | } 942 | 943 | return result; 944 | ``` 945 | This method can be rewritten to return the value instead. 946 | ```csharp 947 | if (SomeConditionHolds()) 948 | { 949 | PerformOperationsForSomeCondition(); 950 | 951 | return false; 952 | } 953 | 954 | PerformOtherOperations(); 955 | 956 | if (SomeOtherConditionHolds()) 957 | { 958 | PerformOperationsForOtherCondition(); 959 | 960 | return false; 961 | } 962 | 963 | PerformFallbackOperations(); 964 | 965 | return true; 966 | ``` 967 | 968 | * The only code that may follow the last `return` statement is the body of an exception handler. 969 | ```csharp 970 | try 971 | { 972 | // Perform operations 973 | 974 | return true; 975 | } 976 | catch (Exception exception) 977 | { 978 | throw new DirectoryAuthenticatorException(exception); 979 | } 980 | ``` 981 | 982 | ### Pattern-matching 983 | 984 | * The same rules apply for `when` expressions as for other conditions: short expressions are fine; extract more complex logic to local or private methods. 985 | 986 | ### `goto` Statements 987 | 988 | * Do not use `goto`. 989 | 990 | ### `unsafe` Blocks 991 | 992 | * Do not use `unsafe`. 993 | 994 | ### Ternary and Coalescing Operators 995 | 996 | * Use these operators for simple expressions and results. 997 | * Do not use these operators with long conditions and values. Instead, use local variables and/or standard conditional statements. 998 | 999 | ### Null-conditional Operator 1000 | 1001 | * Use the `?.`-operator only when no handling for `null` cases is required. 1002 | * Most code should not use this operator; instead, enforce non-null values. 1003 | 1004 | For example, the following example is only allowed when the data comes from a dynamic source (e.g. JSON). 1005 | 1006 | ```csharp 1007 | company.People?[0]?.ContactInfo?.BusinessAddress.Street 1008 | ``` 1009 | 1010 | If the data is not dynamic, then `People`, `ContactInfo` and `BusinessAddress` should never be `null`. 1011 | 1012 | #### `throw`-Expressions 1013 | 1014 | * Only use `throw`-expressions at the end of an expression. 1015 | * Do not use `throw`-expressions anywhere else in an compound expression. 1016 | * Do not use `throw`-expressions as actual arguments. 1017 | 1018 | ### Lambdas 1019 | 1020 | * Do not make overly-complex lambda expressions; instead, define a method or use a delegate. 1021 | 1022 | ### System.Linq 1023 | 1024 | * Pay attention to the order of your Linq expressions to improve performance. 1025 | * Filter before sorting 1026 | * Apply filters that are likely to remove more items first 1027 | * Apply filters with low performance impact first 1028 | * Do not use `List.Foreach()`. 1029 | * Use multiple lines and indenting to to make expressions more legible. 1030 | ```csharp 1031 | var result = elements 1032 | .Where(e => e.Enabled) 1033 | .Where(e => LastUsed > clock.Now.AddWeeks(-2)) 1034 | .OrderBy(e => e.LastUsed) 1035 | .ThenBy(e => e.Name); 1036 | ``` 1037 | Here `Enabled` is tested first because it's cheaper to check. 1038 | * Use Linq syntax to share temporary variables instead of re-declaring them in several lambdas. 1039 | ```csharp 1040 | var result = 1041 | from e in elements 1042 | where e.Enabled 1043 | where e.LastUsed > clock.Now.AddWeeks(-2) 1044 | orderby e.LastUsed, e.Name; 1045 | ``` 1046 | 1047 | ### Casting 1048 | 1049 | * Use a direct cast if you are sure of the type. 1050 | ```csharp 1051 | ((IWeapon)item).Fire(); 1052 | ``` 1053 | * Use the `is`-operator when _testing_ but not _using_ the result of the cast. 1054 | ```csharp 1055 | return item is IWeapon; 1056 | ``` 1057 | * To use the result of the cast, use an `is`-expression in C# 7 and higher. 1058 | ```csharp 1059 | if (item == null) { throw new ArgumentNullException(nameof(item)); } 1060 | 1061 | if (item is IWeapon weapon) 1062 | { 1063 | return weapon.Fire(); 1064 | } 1065 | 1066 | return NullTurn.Default; 1067 | ``` 1068 | * Use a `switch` statement to match more than one or two patterns. Keep the argument precondition separate (even though it _could_ be the penultimate `case`). 1069 | ```csharp 1070 | if (item == null) { throw new ArgumentNullException(nameof(item)); } 1071 | 1072 | switch (item) 1073 | { 1074 | case IWeapon weapon: 1075 | return weapon.Fire(); 1076 | case IMagic magic: 1077 | return magic.Cast(); 1078 | default: 1079 | return NullTurn.Default; 1080 | } 1081 | ``` 1082 | * In C# 6 and lower, use the `as`-operator. 1083 | ```csharp 1084 | if (item == null) { throw new ArgumentNullException(nameof(item)); } 1085 | 1086 | var weapon = item as IWeapon; 1087 | if (weapon != null) 1088 | { 1089 | return weapon.Fire(); 1090 | } 1091 | 1092 | var magic = item as IMagic; 1093 | if (magic != null) 1094 | { 1095 | return magic.Cast(); 1096 | } 1097 | 1098 | return NullTurn.Default; 1099 | ``` 1100 | 1101 | ### `checked` 1102 | 1103 | * Enable range-checking during development and debugging. 1104 | * Disable range-checking in release builds only if there is a valid performance reason for doing so. 1105 | * Use explicit `checked` blocks for overflow- and underflow-prone operations. I.e. if there was a range-checking problem at some point, the block should be marked with a `checked` block. This guarantees checking for these blocks even when range-checking is disabled. 1106 | 1107 | ### Compiler Variables 1108 | 1109 | * Avoid using compiler variables. 1110 | * Avoid using `#define` in the code; use a compiler define in the project settings instead. 1111 | * Avoid suppressing compiler warnings. 1112 | 1113 | #### The [Conditional] Attribute 1114 | 1115 | Use the `ConditionalAttribute` instead of the `#ifdef`/`#endif` pair wherever possible (i.e. for methods or classes). 1116 | 1117 | ```csharp 1118 | public class SomeClass 1119 | { 1120 | [Conditional("TRACE_ON")] 1121 | public static void Msg(string msg) 1122 | { 1123 | Console.WriteLine(msg); 1124 | } 1125 | } 1126 | ``` 1127 | 1128 | #### \#if/#else/#endif 1129 | 1130 | For other conditional compilation, use a static method in a static class instead of scattering conditional options throughout the code. 1131 | 1132 | ```csharp 1133 | public static class EncodoCompilerOptions 1134 | { 1135 | public static bool DeveloperBuild() 1136 | { 1137 | #if ENCODO_DEVELOPER 1138 | return true; 1139 | #else 1140 | return false; 1141 | #endif 1142 | } 1143 | } 1144 | ``` 1145 | 1146 | This approach has the following advantages: 1147 | 1148 | * The compiler checks all code paths instead of just the one satisfying the current options; this avoids unknowingly retaining incompatible code in a library or application. 1149 | * Code formatting and indenting is not broken up by (possibly overlapping) compile conditions; the name `EncodoCompilerOptions` makes the connection to the compiler obvious enough. 1150 | * The compiler option is referenced only once, avoiding situations in which some code uses one compiler option (e.g. `ENCODO_DEVELOPER`) and other code uses another, misspelled option (e.g. `ENCODE_DEVELOPER`). 1151 | 1152 | ### Comments 1153 | 1154 | #### Styles 1155 | 1156 | * Use the single-line comment style—`//`—to indicate a comment. 1157 | * Use four slashes —`////`—to indicate a single line of code that has been temporarily commented. 1158 | * Use the multi-line comment style—`/*` … `*/`—to indicate a commented-out block of code. Do not push these comments to the master branch. 1159 | * Consider using a compiler variable to define a non-compiling block of code; this practice avoids misusing a comment. 1160 | ```csharp 1161 | #if FALSE 1162 | // commented code block 1163 | #endif 1164 | ``` 1165 | * Use the single-line comment style with `TODO` to indicate an issue that must be addressed. Before a check-in, these issues must either be addressed or documented in the issue tracker, adding the URL of the issue to the TODO as follows: 1166 | ```csharp 1167 | // TODO http://issue-tracker.encodo.com/?id=5647: Title of the issue in the issue tracker 1168 | ``` 1169 | 1170 | #### Placement 1171 | 1172 | * Longer comments should always precede the line being commented. Separate multi-line comments with an additional newline before the code. 1173 | * Short comments may appear to the right of the code being commented, but only for lines ending in semicolon (i.e. marking the end of a statement). For example: 1174 | ```csharp 1175 | int Granularity = Size / 5; // More than 50% is not valid! 1176 | ``` 1177 | * Comments on the same line as code should _never_ be wrapped to multiple lines. 1178 | 1179 | #### Use Cases 1180 | 1181 | * Use comments to explain algorithms or tricky bits that aren't immediately obvious from a quick read. 1182 | * Use comments to indicate where a hard-won bug-fix was added; if possible, include a reference to a URL in an issue tracker. 1183 | * Use comments to indicate assumptions not already evident from assertions or thrown exceptions. 1184 | * Comments are in US-English; prefer a short style that gets right to the point. 1185 | * A comment need not be a full, grammatically-correct sentence. For example, the following comment is too long 1186 | ```csharp 1187 | // Using a granularity that is more than 50% of the size is not valid! 1188 | int Granularity = Size / 5; 1189 | ``` 1190 | Instead, you should stick to the essentials so that the warning is immediately clear: 1191 | ```csharp 1192 | int Granularity = Size / 5; // More than 50% is not valid! 1193 | ``` 1194 | * Comments should not explain the obvious. In the following example, the comment is superfluous. 1195 | ```csharp 1196 | public const int Granularity = Size / 5; // granularity is 20% of size 1197 | ``` 1198 | --------------------------------------------------------------------------------