From fa44b1c518a96cd060f7b094938c0887df19350c Mon Sep 17 00:00:00 2001 From: Jonathan Coe Date: Wed, 3 Jun 2020 15:09:42 +0100 Subject: [PATCH] Add Google C# style guide --- README.md | 4 +- csharp-style.md | 477 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 csharp-style.md diff --git a/README.md b/README.md index f7e755f..90e3958 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ style guidelines we use for Google code. If you are modifying a project that originated at Google, you may be pointed to this page to see the style guides that apply to that project. -This project holds the [C++ Style Guide][cpp], [Swift Style Guide][swift], [Objective-C Style Guide][objc], +This project holds the [C++ Style Guide][cpp], [C# Style Guide][csharp], +[Swift Style Guide][swift], [Objective-C Style Guide][objc], [Java Style Guide][java], [Python Style Guide][py], [R Style Guide][r], [Shell Style Guide][sh], [HTML/CSS Style Guide][htmlcss], [JavaScript Style Guide][js], [AngularJS Style Guide][angular], @@ -36,6 +37,7 @@ The following Google style guides live outside of this project: Creative Commons License [cpp]: https://google.github.io/styleguide/cppguide.html +[csharp]: https://google.github.io/styleguide/csharp-style.html [swift]: https://google.github.io/swift/ [objc]: objcguide.md [java]: https://google.github.io/styleguide/javaguide.html diff --git a/csharp-style.md b/csharp-style.md new file mode 100644 index 0000000..d7438c7 --- /dev/null +++ b/csharp-style.md @@ -0,0 +1,477 @@ +# C# at Google Style Guide + +This style guide is for C# code developed internally at Google, and is the +default style for C# code at Google. It makes stylistic choices that conform to +other languages at Google, such as Google C++ style and Google Java style. + +## Formatting guidelines + +### Naming rules + +Naming rules follow +[Microsoft's C# naming guidelines](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines). +Where Microsoft's naming guidelines are unspecified (e.g. private and local +variables), rules are taken from the +[CoreFX C# coding guidelines](https://github.com/dotnet/runtime/blob/master/docs/coding-guidelines/coding-style.md) + +Rule summary: + +#### Code + +* Names of classes, methods, enumerations, public fields, public properties, + namespaces: `PascalCase`. +* Names of local variables, parameters: `camelCase`. +* Names of private, protected, internal and protected internal fields and + properties: `_camelCase`. +* Naming convention is unaffected by modifiers such as const, static, + readonly, etc. +* For casing, a "word" is anything written without internal spaces, including + acronyms. For example, `MyRpc` instead of ~~`MyRPC`~~. +* Names of interfaces start with `I`, e.g. `IInterface`. + +#### Files + +* Filenames and directory names are `PascalCase`, e.g. `MyFile.cs`. +* Where possible the file name should be the same as the name of the main + class in the file, e.g. `MyClass.cs`. +* In general, prefer one core class per file. + +### Organization + +* Modifiers occur in the following order: `public protected internal private + new abstract virtual override sealed static readonly extern unsafe volatile + async`. +* Namespace `using` declarations go at the top, before any namespaces. `using` + import order is alphabetical, apart from `System` imports which always go + first. +* Class member ordering: + * Group class members in the following order: + * Nested classes, enums, delegates and events. + * Static, const and readonly fields. + * Fields and properties. + * Constructors and finalizers. + * Methods. + * Within each group, elements should be in the following order: + * Public. + * Internal. + * Protected internal. + * Protected. + * Private. + * Where possible, group interface implementations together. + +### Whitespace rules + +Developed from Google Java style. + +* A maximum of one statement per line. +* A maximum of one assignment per statement. +* Indentation of 2 spaces, no tabs. +* Column limit: 100. +* No line break before opening brace. +* No line break between closing brace and `else`. +* Braces used even when optional. +* Space after `if`/`for`/`while` etc., and after commas. +* No space after an opening parenthesis or before a closing parenthesis. +* No space between a unary operator and its operand. One space between the + operator and each operand of all other operators. +* Line wrapping developed from Google C++ style guidelines, with minor + modifications for compatibility with Microsoft's C# formatting tools: + * In general, line continuations are indented 4 spaces. + * Line breaks with braces (e.g. list initializers, lambdas, object + initializers, etc) do not count as continuations. + * For function definitions and calls, if the arguments do not all fit on + one line they should be broken up onto multiple lines, with each + subsequent line aligned with the first argument. If there is not enough + room for this, arguments may instead be placed on subsequent lines with + a four space indent. The code example below illustrates this. + +### Example + +```c# +using System; // `using` goes at the top, outside the + // namespace. + +namespace MyNamespace { // Namespaces are PascalCase. + // Indent after namespace. + public interface IMyInterface { // Interfaces start with 'I' + public int Calculate(float value, float exp); // Methods are PascalCase + // ...and space after comma. + } + + public enum MyEnum { // Enumerations are PascalCase. + Yes, // Enumerators are PascalCase. + No, + } + + public class MyClass { // Classes are PascalCase. + public int Foo = 0; // Public member variables are + // PascalCase. + public bool NoCounting = false; // Field initializers are encouraged. + private class Results { + public int NumNegativeResults = 0; + public int NumPositiveResults = 0; + } + private Results _results; // Private member variables are + // _camelCase. + public static int NumTimesCalled = 0; + private const int _bar = 100; // const does not affect naming + // convention. + private int[] _someTable = { // Container initializers use a 2 + 2, 3, 4, // space indent. + } + + public MyClass() { + _results = new Results { + NumNegativeResults = 1, // Object initializers use a 2 space + NumPositiveResults = 1, // indent. + }; + } + + public int CalculateValue(int mulNumber) { // No line break before opening brace. + var resultValue = Foo * mulNumber; // Local variables are camelCase. + NumTimesCalled++; + Foo += _bar; + + if (!NoCounting) { // No space after unary operator and + // space after 'if'. + if (resultValue < 0) { // Braces used even when optional and + // spaces around comparison operator. + _results.NumNegativeResults++; + } else if (resultValue > 0) { // No newline between brace and else. + _results.NumPositiveResults++; + } + } + + return resultValue; + } + + public void ExpressionBodies() { + // For simple lambdas, fit on one line if possible, no brackets or braces required. + Func increment = x => x + 1; + + // Closing brace aligns with first character on line that includes the opening brace. + Func difference1 = (x, y) => { + long diff = (long)x - y; + return diff >= 0 ? diff : -diff; + }; + + // If defining after a continuation line break, indent the whole body. + Func difference2 = + (x, y) => { + long diff = (long)x - y; + return diff >= 0 ? diff : -diff; + }; + + // Inline lambda arguments also follow these rules. Prefer a leading newline before + // groups of arguments if they include lambdas. + CallWithDelegate( + (x, y) => { + long diff = (long)x - y; + return diff >= 0 ? diff : -diff; + }); + } + + void DoNothing() {} // Empty blocks may be concise. + + // If possible, wrap arguments by aligning newlines with the first argument. + void AVeryLongFunctionNameThatCausesLineWrappingProblems(int longArgumentName, + int p1, int p2) {} + + // If aligning argument lines with the first argument doesn't fit, or is difficult to + // read, wrap all arguments on new lines with a 4 space indent. + void AnotherLongFunctionNameThatCausesLineWrappingProblems( + int longArgumentName, int longArgumentName2, int longArgumentName3) {} + + void CallingLongFunctionName() { + int veryLongArgumentName = 1234; + int shortArg = 1; + // If possible, wrap arguments by aligning newlines with the first argument. + AnotherLongFunctionNameThatCausesLineWrappingProblems(shortArg, shortArg, + veryLongArgumentName); + // If aligning argument lines with the first argument doesn't fit, or is difficult to + // read, wrap all arguments on new lines with a 4 space indent. + AnotherLongFunctionNameThatCausesLineWrappingProblems( + veryLongArgumentName, veryLongArgumentName, veryLongArgumentName); + } + } +} +``` + +## C# coding guidelines + +### Constants + +* Variables and fields that can be made `const` should always be made `const`. +* If `const` isn’t possible, `readonly` can be a suitable alternative. +* Prefer named constants to magic numbers. + +### IEnumerable vs IList vs IReadOnlyList + +* For inputs use the most restrictive collection type possible, for example + `IReadOnlyCollection` / `IReadOnlyList` / `IEnumerable` as inputs to methods + when the inputs should be immutable. +* For outputs, if passing ownership of the returned container to the owner, + prefer `IList` over `IEnumerable`. If not transferring ownership, prefer the + most restrictive option. + +### Generators vs containers + +* Use your best judgement, bearing in mind: + * Generator code is often less readable than filling in a container. + * Generator code can be more performant if the results are going to be + processed lazily, e.g. when not all the results are needed. + * Generator code that is directly turned into a container via `ToList()` + will be less performant than filling in a container directly. + * Generator code that is called multiple times will be considerably slower + than iterating over a container multiple times. + +### Property styles + +* For single line read-only properties, prefer expression body properties + (`=>`) when possible. +* For everything else, use the older `{ get; set; }` syntax. + +### Expression body syntax + +For example: + +```c# {.good} +int SomeProperty => _someProperty +``` + +* Judiciously use expression body syntax in lambdas and properties. +* Don’t use on method definitions. This will be reviewed when C# 7 is live, + which uses this syntax heavily. +* As with methods and other scoped blocks of code, align the closing with the + first character of the line that includes the opening brace. See sample code + for examples. + +### Structs and classes: + +* Structs are very different from classes: + + * Structs are always passed and returned by value. + * Assigning a value to a member of a returned struct doesn’t modify the + original - e.g. `transform.position.x = 10` doesn’t set the transform’s + position.x to 10; `position` here is a property that returns a `Vector3` + by value, so this just sets the x parameter of a copy of the original. + +* Almost always use a class. + +* Consider struct when the type can be treated like other value types - for + example, if instances of the type are small and commonly short-lived or are + commonly embedded in other objects. Good examples include Vector3, + Quaternion and Bounds. + +* Note that this guidance may vary from team to team where, for example, + performance issues might force the use of structs. + +### Lambdas vs named methods + +* If a lambda is non-trivial (e.g. more than a couple of statements, excluding + declarations), or is reused in multiple places, it should probably be a + named method. + +### Field initializers + +* Field initializers are generally encouraged. + +### Extension methods + +* Only use an extension method when the source of the original class is not + available, or else when changing the source is not feasible. +* Only use an extension method if the functionality being added is a ‘core’ + general feature that would be appropriate to add to the source of the + original class. + * Note - if we have the source to the class being extended, and the + maintainer of the original class does not want to add the function, + prefer not using an extension method. +* Only put extension methods into core libraries that are available + everywhere - extensions that are only available in some code will become a + readability issue. +* Be aware that using extension methods always obfuscates the code, so err on + the side of not adding them. + +### ref and out + +* Use `out` for returns that are not also inputs. +* Place `out` parameters after all other parameters in the method definition. +* `ref` should be used rarely, when mutating an input is necessary. +* Do not use `ref` as an optimisation for passing structs. +* Do not use `ref` to pass a modifiable container into a method. `ref` is only + required when the supplied container needs be replaced with an entirely + different container instance. + +### LINQ + +* In general, prefer single line LINQ calls and imperative code, rather than + long chains of LINQ. Mixing imperative code and heavily chained LINQ is + often hard to read. +* Prefer member extension methods over SQL-style LINQ keywords - e.g. prefer + `myList.Where(x)` to `myList where x`. +* Avoid `Container.ForEach(...)` for anything longer than a single statement. + +### Array vs List + +* In general, prefer `List<>` over arrays for public variables, properties, + and return types (keeping in mind the guidance on `IList` / `IEnumerable` / + `IReadOnlyList` above). +* Prefer `List<>` when the size of the container can change. +* Prefer arrays when the size of the container is fixed and known at + construction time. +* Prefer array for multidimensional arrays. +* Note: + * array and `List<>` both represent linear, contiguous containers. + * Similar to C++ arrays vs `std::vector`, arrays are of fixed capacity, + whereas `List<>` can be added to. + * In some cases arrays are more performant, but in general `List<>` is + more flexible. + +### Folders and file locations + +* Be consistent with the project. +* Prefer a flat structure where possible. + +### Use of tuple as a return type + +* In general, prefer a named class type over `Tuple<>`, particularly when + returning complex types. + +### String interpolation vs `String.Format()` vs `String.Concat` vs `operator+` + +* In general, use whatever is easiest to read, particularly for logging and + assert messages. +* Be aware that chained `operator+` concatenations will be slower and cause + significant memory churn. +* If performance is a concern, `StringBuilder` will be faster for multiple + string concatenations. + +### `using` + +* Generally, don’t alias long typenames with `using`. Often this is a sign + that a `Tuple<>` needs to be turned into a class. + * e.g. `using RecordList = List>` should probably be a + named class instead. +* Be aware that `using` statements are only file scoped and so of limited use. + Type aliases will not be available for external users. + +### Object Initializer syntax + +For example: + +```c# {.good} +var x = new SomeClass { + Property1 = value1, + Property2 = value2, +}; +``` + +* Object Initializer Syntax is fine for ‘plain old data’ types. +* Avoid using this syntax for classes or structs with constructors. +* If splitting across multiple lines, indent one block level. + +### Namespace naming + +* In general, namespaces should be no more than 2 levels deep. +* Don't force file/folder layout to match namespaces. +* For shared library/module code, use namespaces. For leaf 'application' code, + such as `unity_app`, namespaces are not necessary. +* New top-level namespace names must be globally unique and recognizable. + +### Default values/null returns for structs + +* Prefer returning a ‘success’ boolean value and a struct `out` value. +* Where performance isn't a concern and the resulting code significantly more + readable (e.g. chained null conditional operators vs deeply nested if + statements) nullable structs are acceptable. +* Notes: + + * Nullable structs are convenient, but reinforce the general ‘null is + failure’ pattern Google prefers to avoid. We will investigate a + `StatusOr` equivalent in the future, if there is enough demand. + +### Removing from containers while iterating + +C# (like many other languages) does not provide an obvious mechanism for +removing items from containers while iterating. There are a couple of options: + +* If all that is required is to remove items that satisfy some condition, + `someList.RemoveAll(somePredicate)` is recommended. +* If other work needs to be done in the iteration, `RemoveAll` may not be + sufficient. A common alternative pattern is to create a new container + outside of the loop, insert items to keep in the new container, and swap the + original container with the new one at the end of iteration. + +### Calling delegates + +* When calling a delegate, use `Invoke()` and use the null conditional + operator - e.g. `SomeDelegate?.Invoke()`. This clearly marks the call at the + callsite as ‘a delegate that is being called’. The null check is concise and + robust against threading race conditions. + +### The `var` keyword + +* Use of `var` is encouraged if it aids readability by avoiding type names + that are noisy, obvious, or unimportant. +* Encouraged: + + * When the type is obvious - e.g. `var apple = new Apple();`, or `var + request = Factory.Create();` + * For transient variables that are only passed directly to other methods - + e.g. `var item = GetItem(); ProcessItem(item);` + +* Discouraged: + + * When working with basic types - e.g. `var success = true;` + * When working with compiler-resolved built-in numeric types - e.g. `var + number = 12 * ReturnsFloat();` + * When users would clearly benefit from knowing the type - e.g. `var + listOfItems = GetList();` + +### Attributes + +* Attributes should appear on the line above the field, property, or method + they are associated with, separated from the member by a newline. +* Multiple attributes should be separated by newlines. This allows for easier + adding and removing of attributes, and ensures each attribute is easy to + search for. + +### Argument Naming + +Derived from the Google C++ style guide. + +When the meaning of a function argument is nonobvious, consider one of the +following remedies: + +* If the argument is a literal constant, and the same constant is used in + multiple function calls in a way that tacitly assumes they're the same, use + a named constant to make that constraint explicit, and to guarantee that it + holds. +* Consider changing the function signature to replace a `bool` argument with + an `enum` argument. This will make the argument values self-describing. +* Replace large or complex nested expressions with named variables. +* Consider using + [Named Arguments](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments) + to clarify argument meanings at the call site. +* For functions that have several configuration options, consider defining a + single class or struct to hold all the options and pass an instance of that. + This approach has several advantages. Options are referenced by name at the + call site, which clarifies their meaning. It also reduces function argument + count, which makes function calls easier to read and write. As an added + benefit, call sites don't need to be changed when another option is added. + +Consider the following example: + +```c# {.bad} +// What are these arguments? +DecimalNumber product = CalculateProduct(values, 7, false, null); +``` + +versus: + +```c# {.good} +ProductOptions options = new ProductOptions(); +options.PrecisionDecimals = 7; +options.UseCache = CacheUsage.DontUseCache; +DecimalNumber product = CalculateProduct(values, options, completionDelegate: null); +```