This style guide is for C# code developed internally at Google, and is thedefault style for C# code at Google. It makes stylistic choices that conform toother languages at Google, such as Google C++ style and Google Java style.
Naming rules followMicrosoft’s C# naming guidelines.Where Microsoft’s naming guidelines are unspecified (e.g. private and localvariables), rules are taken from theCoreFX C# coding guidelines
Rule summary:
PascalCase
.camelCase
._camelCase
.MyRpc
instead ofMyRPC
I
, e.g.IInterface
.PascalCase
, e.g.MyFile.cs
.MyClass.cs
.public protected internal privatenew abstract virtual override sealed static readonly extern unsafe volatileasync
.using
declarations go at the top, before any namespaces.using
import order is alphabetical, apart fromSystem
imports which always gofirst.Developed from Google Java style.
else
.if
/for
/while
etc., and after commas.usingSystem;// `using` goes at the top, outside the// namespace.namespaceMyNamespace{// Namespaces are PascalCase.// Indent after namespace.publicinterfaceIMyInterface{// Interfaces start with 'I'publicintCalculate(floatvalue,floatexp);// Methods are PascalCase// ...and space after comma.}publicenumMyEnum{// Enumerations are PascalCase.Yes,// Enumerators are PascalCase.No,}publicclassMyClass{// Classes are PascalCase.publicintFoo=0;// Public member variables are// PascalCase.publicboolNoCounting=false;// Field initializers are encouraged.privateclassResults{publicintNumNegativeResults=0;publicintNumPositiveResults=0;}privateResults_results;// Private member variables are// _camelCase.publicstaticintNumTimesCalled=0;privateconstint_bar=100;// const does not affect naming// convention.privateint[]_someTable={// Container initializers use a 22,3,4,// space indent.}publicMyClass(){_results=newResults{NumNegativeResults=1,// Object initializers use a 2 spaceNumPositiveResults=1,// indent.};}publicintCalculateValue(intmulNumber){// No line break before opening brace.varresultValue=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++;}elseif(resultValue>0){// No newline between brace and else._results.NumPositiveResults++;}}returnresultValue;}publicvoidExpressionBodies(){// 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)=>{longdiff=(long)x-y;returndiff>=0?diff:-diff;};// If defining after a continuation line break, indent the whole body.Func<int,int,long>difference2=(x,y)=>{longdiff=(long)x-y;returndiff>=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)=>{longdiff=(long)x-y;returndiff>=0?diff:-diff;});}voidDoNothing(){}// Empty blocks may be concise.// If possible, wrap arguments by aligning newlines with the first argument.voidAVeryLongFunctionNameThatCausesLineWrappingProblems(intlongArgumentName,intp1,intp2){}// 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.voidAnotherLongFunctionNameThatCausesLineWrappingProblems(intlongArgumentName,intlongArgumentName2,intlongArgumentName3){}voidCallingLongFunctionName(){intveryLongArgumentName=1234;intshortArg=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 madeconst
.const
isn’t possible,readonly
can be a suitable alternative.IReadOnlyCollection
/IReadOnlyList
/IEnumerable
as inputs to methodswhen the inputs should be immutable.IList
overIEnumerable
. If not transferring ownership, prefer themost restrictive option.ToList()
will be less performant than filling in a container directly.=>
) when possible.{ get; set; }
syntax.For example:
intSomeProperty=>_someProperty
Structs are very different from classes:
transform.position.x = 10
doesn’t set the transform’sposition.x to 10;position
here is a property that returns aVector3
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 - forexample, if instances of the type are small and commonly short-lived or arecommonly 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 onlyrequired when the supplied container needs be replaced with an entirelydifferent container instance.myList.Where(x)
tomyList 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 onIList
/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,whereasList<>
can be added to.List<>
ismore flexible.Tuple<>
, particularly whenreturning complex types.String.Format()
vsString.Concat
vsoperator+
operator+
concatenations will be slower and causesignificant memory churn.StringBuilder
will be faster for multiplestring concatenations.using
using
. Often this is a signthat aTuple<>
needs to be turned into a class.using RecordList = List<Tuple<int, float>>
should probably be anamed class instead.using
statements are only file scoped and so of limited use.Type aliases will not be available for external users.For example:
varx=newSomeClass{Property1=value1,Property2=value2,};
unity_app
, namespaces are not necessary.out
value.Notes:
StatusOr
equivalent in the future, if there is enough demand.C# (like many other languages) does not provide an obvious mechanism forremoving items from containers while iterating. There are a couple of options:
someList.RemoveAll(somePredicate)
is recommended.RemoveAll
may not besufficient. A common alternative pattern is to create a new containeroutside of the loop, insert items to keep in the new container, and swap theoriginal container with the new one at the end of iteration.Invoke()
and use the null conditionaloperator - e.g.SomeDelegate?.Invoke()
. This clearly marks the call at thecallsite as ‘a delegate that is being called’. The null check is concise androbust against threading race conditions.var
keywordvar
is encouraged if it aids readability by avoiding type namesthat are noisy, obvious, or unimportant.Encouraged:
var apple = new Apple();
, orvarrequest = Factory.Create<HttpRequest>();
var item = GetItem(); ProcessItem(item);
Discouraged:
var success = true;
varnumber = 12 * ReturnsFloat();
varlistOfItems = GetList();
Derived from the Google C++ style guide.
When the meaning of a function argument is nonobvious, consider one of thefollowing remedies:
bool
argument withanenum
argument. This will make the argument values self-describing.Consider the following example:
// Bad - what are these arguments?DecimalNumberproduct=CalculateProduct(values,7,false,null);
versus:
// GoodProductOptionsoptions=newProductOptions();options.PrecisionDecimals=7;options.UseCache=CacheUsage.DontUseCache;DecimalNumberproduct=CalculateProduct(values,options,completionDelegate:null);