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.
Naming rules follow Microsoft's C# naming guidelines. Where Microsoft's naming guidelines are unspecified (e.g. private and local variables), rules are taken from the CoreFX C# coding guidelines
Rule summary:
PascalCase
.camelCase
._camelCase
.MyRpc
instead of MyRPC
I
, e.g. IInterface
.PascalCase
, e.g. MyFile.cs
.MyClass.cs
.public protected internal private new abstract virtual override sealed static readonly extern unsafe volatile async
.using
declarations go at the top, before any namespaces. using
import order is alphabetical, apart from System
imports which always go first.Developed from Google Java style.
else
.if
/for
/while
etc., and after commas.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<int, int> increment = x => x + 1; // Closing brace aligns with first character on line that includes the opening brace. Func<int, int, long> 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<int, int, long> 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); } } }
const
should always be made const
.const
isn’t possible, readonly
can be a suitable alternative.IReadOnlyCollection
/ IReadOnlyList
/ IEnumerable
as inputs to methods when the inputs should be immutable.IList
over IEnumerable
. If not transferring ownership, prefer the most restrictive option.ToList()
will be less performant than filling in a container directly.=>
) when possible.{ get; set; }
syntax.For example:
int SomeProperty => _someProperty
Structs are very different from classes:
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.
out
for returns that are not also inputs.out
parameters after all other parameters in the method definition.ref
should be used rarely, when mutating an input is necessary.ref
as an optimisation for passing structs.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.myList.Where(x)
to myList where x
.Container.ForEach(...)
for anything longer than a single statement.List<>
over arrays for public variables, properties, and return types (keeping in mind the guidance on IList
/ IEnumerable
/ IReadOnlyList
above).List<>
when the size of the container can change.List<>
both represent linear, contiguous containers.std::vector
, arrays are of fixed capacity, whereas List<>
can be added to.List<>
is more flexible.Tuple<>
, particularly when returning complex types.String.Format()
vs String.Concat
vs operator+
operator+
concatenations will be slower and cause significant memory churn.StringBuilder
will be faster for multiple string concatenations.using
using
. Often this is a sign that a Tuple<>
needs to be turned into a class.using RecordList = List<Tuple<int, float>>
should probably be a named class instead.using
statements are only file scoped and so of limited use. Type aliases will not be available for external users.For example:
var x = new SomeClass { Property1 = value1, Property2 = value2, };
unity_app
, namespaces are not necessary.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:
StatusOr
equivalent in the future, if there is enough demand.C# (like many other languages) does not provide an obvious mechanism for removing items from containers while iterating. There are a couple of options:
someList.RemoveAll(somePredicate)
is recommended.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.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.var
keywordUse of var
is encouraged if it aids readability by avoiding type names that are noisy, obvious, or unimportant.
Encouraged:
var apple = new Apple();
, or var request = Factory.Create<HttpRequest>();
var item = GetItem(); ProcessItem(item);
Discouraged:
var success = true;
var number = 12 * ReturnsFloat();
var listOfItems = GetList();
Derived from the Google C++ style guide.
When the meaning of a function argument is nonobvious, consider one of the following remedies:
bool
argument with an enum
argument. This will make the argument values self-describing.Consider the following example:
// Bad - what are these arguments? DecimalNumber product = CalculateProduct(values, 7, false, null);
versus:
// Good ProductOptions options = new ProductOptions(); options.PrecisionDecimals = 7; options.UseCache = CacheUsage.DontUseCache; DecimalNumber product = CalculateProduct(values, options, completionDelegate: null);