You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

376 lines
26 KiB
C#

3 years ago
// | CODING STANDARD-COMPLIANT REFERENCE C# FILE
// | _____ ____
// | / ___/ __/ / /_
// | / /__ /_ . __/
// | \___/ /_ . __/
// | /_/_/
// |
// |[About this file]
// | - This is a 'living document' and will be updated as our standard evolves.
// | - For best rendering, be sure to view this file in your text editor rather than through a web site source viewer.
// | - The '// |'-style comments denote documentation markup, and are not part of the actual sample.
// | - To avoid redundancy, rules inline in the reference code are only mentioned once. Assume they apply generally unless noted, or if obvious from the context.
// | - This code is only intended to demonstrate conventions, and as a result sometimes gets nonsensical. Pay no attention to the substance, only the form.
// | - Reasoning behind rules are not included here to save space. Ask on Q or browse 'code-conventions + c#' at https://q.unity3d.com/search.html?f=&type=question&redirect=search%2Fsearch&sort=relevance&q=%5Bcode-conventions%5D+and+%5Bc%23%5D.
// | - This code will always compile cleanly as a Unity script and will show as fully green in ReSharper using the Unity rule set.
// | - This file is currently maintained by scobi and lives in https://ono.unity3d.com/unity-extra/unity-meta/raw/@/ReferenceSource/CSharp/Assets/CSharpReference.cs.
// | - For clarification, or reporting of ambiguities or bugs, ask on Q or #devs-code-conventions in Slack.
// | - If you are writing C# intended for the Unity runtime (like UnityEngine.dll) then also read up on https://q.unity3d.com/questions/1814/what-should-i-consider-when-writing-c-code-that-wi.html
// |
// |[General]
// | - Our standard extends Microsoft's Framework Design Guidelines, which defines a number of rules not covered by this document. (see https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/).
// | - This document inlcludes a subset of the most used rules as well as any additions and exceptions to the FDG.
// | - If there is any disagreement between this file and the FDG, this file always wins. Exceptions to the FDG are marked with [FDG Exception]
// | - If the compiler does not require something, leave it out (i.e. 'this.' prefix, default access levels, 'Attribute' postfix, etc.)
// |
// |[Encoding]
// | - Text file encoding is UTF8 with no BOM, using LF (unix) line endings.
// | - 4-wide tabstops, using spaces only (no tab characters)
// | - No trailing whitespace on lines, but always include a single newline at the end of the file.
// | - (All of the above are ensured by a combination of automated tools. Make sure you have followed the setup instructions at http://confluence.hq.unity3d.com/x/ooPD.)
// |
// |[Files]
// | - No file header, copyright, etc. of any kind. Some IDE's may add them automatically - please remove them.
// | - Maintain the style of surrounding code if it has its own separate standard (i.e. is or heavily derived from external).
// | - Use PascalCase for file names, typically matching the name of the dominant type in the file (or if none is dominant, use a reasonable category name).
// |
// |[Naming]
// | - Use PascalCase for all symbol names, except where noted.
// | - No 'Hungarian notation' or other prefixes, except where noted.
// | - Spell words using correct US-English spelling. Note that there are a few legacy exceptions that use GB-English that we must preserve, but do not add new ones.
// | - Use descriptive and accurate names, even if it makes them longer. Favor readability over brevity.
// | - Avoid abbreviations when possible unless the abbreviation is commonly accepted.
// | - Acronyms are PascalCase, unless they are exactly two letters, in which case they are UPPERCASE. (ex. htmlText, GetCpuCycles(), IOStream)
// | - Do not capitalize each word in so-called closed-form compound words (see https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions for a sample list of compound words)
// | - Use semantically interesting names rather than language-specific keywords for type names (i.e. GetLength > GetInt).
// | - Use a common name, such as value/item/element, rather than repeating the type name, in the rare cases when an identifier has no semantic meaning and the type is not important (i.e. newElements > newInts).
// |
// | Definitions:
// | - camelCase: words* capitalized, except the first (see the humps?)
// | - PascalCase: all words* capitalized
// | - UPPERCASE: all letters in all words* capitalized
// | * A "word" may only contain letters and numbers (no underscores or other symbols).
// |
// | Readability examples:
// | - HorizontalAlignment instead of AlignmentHorizontal (more English-readable)
// | - CanScrollHorizontally instead of ScrollableX ('x' is somewhat obscure reference to the x axis)
// | - DirectionalVector instead of DirVec (unnecessary and use of nonstandard abbreviation)
// |
// | Common abbreviations:
// | - param (parameter), arg (argument), id (identifier), db (database), ok (okay)
// |
// |[Spacing]
// | - Space before opening parenthesis?
// | - If it looks like a function call, no space (function calls, function definitions, typeof(), sizeof())
// | - If it opens a scope, add a space (if, while, catch, switch, for, foreach, using, lock, fixed)
// | - No spaces immediately inside any parens or brackets (e.g. no 'if ( foo )' or 'x = ( y * z[ 123 ] )')
// | - Comma and semicolon spacing as in English ('int a, float b' and 'for (int i = 0; i < 10; ++i)')
// | - Exactly one space is required after the // in a C++ style comment.
// | - Do not add a space between a unary operator and its operand (!expr, +30, -1.4, i++, --j, &expr, *expr, (int)obj, etc.).
// | - Do not add spaces around member access operators (a.b, a->b, etc.).
// | - Spaces are required both before and after all other operators (math, assignment, comparison, lambdas, etc.).
// |
// |[Wrapping]
// | - Wrap code once it gets to around 120 columns wide to keep side-by-side diffs sane (not a hard limit; use your judgment).
// | - When necessary, break lines after boolean operators in conditional expressions, after ';' in for-statements, and after ',' in function calls
// |
// |[Comments]
// | - Documenting the 'why' is far more important than the 'what' or 'how'.
// | - Document anything that would surprise another engineer (or yourself in six months when you've forgotten it).
// | - /*C-Style comments*/ are not permitted. They are reserved for commenting out big hunks of code locally (never to be committed).
// | - No "divider" comments (i.e. long ----- and ////// comments just to break up code).
// | - No "category" comments (i.e. // Functions // Private Data // Globals etc.).
// | - Use of #region is _always_ disallowed.
// | - Only use /// (triple slash) comments if you are writing xmldoc, and never for ordinary comments that you want to stand out
// |________________________________________________________________________________________________
// |[Usings]
// | - Located at file scope at the top of the file, never within a namespace.
// | - Three groups, which are, top to bottom: System, non-System, aliases. Keep each group sorted.
// | - Strip unused 'usings' except the 'minimally-required set', which is marked with *required below.
// | - Only use aliases when required by the compiler for disambiguation, and not for hiding rarely-used symbols behind a prefix.
// | - Always drop explicit namespace qualifications on types when a 'using' can be added (i.e. almost all of the time).
using System; // | Not required, but strongly encouraged
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Company.BuildSystem; // | Start of non-System group
using Microsoft.Win32;
using UnityEngine;
using Component = UnityEngine.Component; // | Start of aliases group
using Debug = UnityEngine.Debug;
namespace UnityEditor // | Full contents of namespace indented
{
// |[Enums]
// | - Use a singular type name, and no prefix or suffix (e.g. no E- prefix or -Enum suffix).
// | - Constant names should have no prefix or suffix.
// | - Do not specify constant values unless absolutely required (e.g. for version-safe protocols - rare).
enum WindingOrder // | Drop redundant access specifiers (leave off 'internal' at file scope)
{ // | Opening brace is always on its own line at the same level of indentation as its parent
Clockwise, // | Code within the braces always indented one tab stop
CounterClockwise,
Charm,
Singularity, // | Trail last element in a list with ','
} // | Closing brace is on its own line at same level of indentation as parent
// | Put exactly one blank line between multi-line types
// |[Flags enums]
// | - Use a plural type name, and no prefix or suffix (e.g. no E- prefix and no -Flag or -Flags suffix).
// | - Constant names should have no prefix or suffix.
// | - Use column-aligned bit shift expressions for the constants (instead of 2, 4, 8, etc.)
[Flags]
public enum VertexStreams
{
Position = 1 << 0,
Normal = 1 << 1,
Tangent = 1 << 2,
Color = 1 << 3,
UV = 1 << 4,
}
// |[Interfaces]
// | - Name interfaces with adjective phrases, or occasionally with nouns or noun phrases.
// | - Nouns and noun phrases should be used rarely and they might indicate that the type should be an abstract class, and not an interface.
// | - Use 'I' prefix to indicate an interface.
// | - Ensure that the names differ only by the 'I' prefix on the interface name when you are defining a class-interface pair, where the class is a standard implementation of the interface.
public interface IThingAgent
{
string operationDescription { get; }
float scale { get; }
// |[Methods]
// | - Give methods names that are verbs or verb phrases.
// | - Parameter names are camelCase
bool DoThing(string propertyDescription, int spinCount);
}
// |[Classes]
// | - Name classes and structs with nouns or noun phrases.
// | - No prefix on class names (no 'C' or 'S' etc.).
class Example
{
// |[Fields]
// | - Use prefix + PascalCase for non-public field naming.
// | - Prefixes: m_ = instance field, s_ = static readwrite field, k_ = const
// | - Also prefix static/instance readonly with k_ if the intent is to treat the field as deeply const.
// | - Drop redundant initializers (i.e. no '= 0' on the ints, '= null' on ref types, etc.).
// | - Drop redundant access specifiers (leave off 'private' at type scope).
// | - Never expose public fields which are not const or static readonly. These fields should be published through a property.
// | - Use readonly where const isn't possible.
static readonly Vector3 k_DefaultLength = new Vector3(1, 2, 3); // | When it enhances readability, try to column-align blocks of variable definitions at symbol name and assignment tab stops
const int k_MaxCount = DisplayData.MaxItems;
static int s_SharedCount; // | Note no "= 0". All memory is zero'd out by default, so do not redundantly assign.
int m_CurrentCount;
public const int totalCount = 123; // | In the UnityEngine and UnityEditor namespaces (old conventions), public fields are camelCase with no prefix [FDG Exception]
// | In the Unity namespace, public fields are PascalCase with no prefix
public string defaultName { get { return Environment.MachineName; } } // | In the UnityEngine and UnityEditor namespaces (old conventions), public properties are camelCase with no prefix [FDG Exception]
// | In the Unity namespace, public properties are PascalCase with no prefix
[Example] // | Drop 'Attribute' postfix when applying an attribute
public int currentCount
{
get { return m_CurrentCount; } // | Getters are always trivial and do not mutate state (this includes first-run cached results); use a full method if you want to do calculations or caching
set { m_CurrentCount = value; }
} // | Put exactly one blank line between multi-line methods and properties
public string description
{
get // | For multiline method bodies, the 'get' and 'set' keywords must be on their own line
{
return string.Format(
"shared: {0}\ncurrent: {1}\n",
s_SharedCount, m_CurrentCount);
}
}
// |[Events]
// | - Do not declare new delegate types. Use Action<...> instead.
// | - Do not expose public delegate fields. Use events instead.
// | - Include one participle-form verb in the event name (generally ending in -ed or -ing, ex. occurred, loading, started, given)
// | - *EventArgs struct parameters are not necessary, but they should be used if the data sent to the event has the possibility of needing to be changed. [FDG Exception]
public event Action<ThingHappenedEventArgs> thingHappened;
[Description("I do things"), DebuggerNonUserCode] // | Attributes always go on a line separate from what they apply to (unless a parameter), and joining them is encouraged if they are short
public void DoThings(IEnumerable<IThingAgent> thingsToDo, string propertyDescription) // | For types that are already internal (like class Example), use public instead of internal for members and nested types
{
var doneThings = new List<IThingAgent>(); // | 'var' required on any 'new' where the type we want is the same as what is being constructed
var indent = new string(' ', 4); // | ...even primitive types
// | When appropriate, separate code blocks by a single empty line
IList<string> doneDescriptions = new List<string>(); // | (This is a case where 'var' not required because the types of the variable vs the ctor are different)
foreach (var thingToDo in thingsToDo) // | 'var' required in all foreach
{
if (!thingToDo.DoThing(propertyDescription, m_CurrentCount))
break; // | Braces not required for single statements under if or else, but that single statement must be on its own line
using (File.CreateText(@"path\to\something.txt")) // | Use @"" style string literal for paths with backslashes and regular expression patterns
using (new ComputeBuffer(10, 20)) // | Don't use braces for directly nested using's
{ // | Braces required for deepest level of nested using's
doneThings.Add(thingToDo);
}
}
foreach (var doneThing in doneThings) // | Dirty details about allocs at https://q.unity3d.com/questions/1465/when-does-using-foreach-in-c-cause-an-allocation.html
{ // | Braces are required for loops (foreach, for, while, do) as well as 'fixed' and 'lock'
doneDescriptions.Add(doneThing.operationDescription);
Debug.Log(indent + "Doing thing: " + doneThing.operationDescription); // | Prefer a + b + c over string.Concat(a, b, c)
}
Debug.Log("System Object is " + typeof(object)); // | Always use lowercase `object` for the System.Object class.
Debug.Log("Unity Object is " + typeof(UnityEngine.Object)); // | Always use a fully qualified name for Unity's Object type, and never 'Object'
}
public void ControlFlow(string message, object someFoo, WindingOrder windingOrder) // | Use c# aliases of System types (e.g. object instead of Object, float instead of Single, etc.)
{
for (int i = 0; i < k_MaxCount; ++i) // | Using i and j for trivial local iterators is encouraged
{
// all of this is nonsense, and is just meant to demonstrate formatting // | Place comments about multiple lines of code directly above them, with one empty line above the comment to visually group it with its code
if ((i % -3) - 1 == 0) // | Wrap parens around subexpressions is optional but recommended to make operator precedence clear
{
++m_CurrentCount;
s_SharedCount *= (int)k_DefaultLength.x + totalCount;
do // | 'while', 'do', 'for', 'foreach', 'switch' are always on a separate line from the code block they control
{
i += s_SharedCount;
}
while (i < m_CurrentCount);
}
else // | 'else' always at same indentation level as its 'if'
{
Debug.LogWarning("Skipping over " + i); // | Drop 'ToString()' when not required by compiler
goto skip; // | Goto's not necessarily considered harmful, not disallowed, but should be scrutinized for utility before usage
}
}
skip: // | Goto label targets un-indented from parent scope one tab stop
// more nonsense code for demo purposes
switch (windingOrder)
{
case WindingOrder.Clockwise: // | Case labels indented under switch
case WindingOrder.CounterClockwise: // | Braces optional if not needed for scope (but note indentation of braces and contents)
if (s_SharedCount == DisplayData.MaxItems) // | Constants go on the right in comparisons (do not follow 'yoda' style)
{
var warningDetails = someFoo.ToString(); // | 'var' for the result of assignments is optional (either way, good variable naming is most important)
for (var i = 0; i < s_SharedCount; ++i)
{
Debug.LogWarning("Spinning a " + warningDetails);
}
}
break; // | 'break' inside case braces, if any
case WindingOrder.Charm:
Debug.LogWarning("Check quark"); // | Indentation is the same, with or without scope braces
break;
case WindingOrder.Singularity:
{
var warningDetails = message; // | (this seemingly pointless variable is here solely to require braces on the case statements and show the required formatting)
if (message == Registry.ClassesRoot.ToString())
{
// Already correct so we don't need to do anything here // | Empty blocks should (a) only be used when it helps readability, (b) always use empty braces (never a standalone semicolon), and (c) be commented as to why the empty block is there
}
else if (m_CurrentCount > 3)
{
if (s_SharedCount < 10) // | Braces can only be omitted at the deepest level of nested code
Debug.LogWarning("Singularity! (" + warningDetails + ")");
}
else if (s_SharedCount > 5) // | 'else if' always on same line together
throw new IndexOutOfRangeException();
else if ((s_SharedCount > 7 && m_CurrentCount != 0) || message == null) // | Always wrap subexpressions in parens when peer precedence is close enough to be ambiguous (e.g. && and || are commonly confused)
throw new NotImplementedException();
break;
}
default:
throw new InvalidOperationException("What's a " + windingOrder + "?");
}
}
// |[Parameterized Types]
// | - When only a single parameterized type is used, naming it 'T' is acceptable.
// | - For more than one parameterized type, use descriptive names prefixed with 'T'.
// | - Consider indicating constraints placed on a type parameter in the name of the parameter.
public static TResult Transmogrify<TResult, TComponent>( // | When wrapping params, do not leave any on line with function name
TComponent component, Func<TComponent, TResult> converter) // | When wrapping, only indent one stop (do not line up with paren)
where TComponent : Component
{
return converter(component);
}
}
// |[Structs]
// | - Name classes and structs with nouns or noun phrases.
// | - No prefix on class names (no 'C' or 'S' etc.).
// | - Structs may be mutable, but consider immutability when appropriate. [FDG Exception]
struct MethodQuery
{
public string name { get; set; }
public IEnumerable<Type> paramTypes { get; set; }
public Type returnType { get; set; }
public override string ToString() // | Methods generally are not permitted in structs, with exceptions like this noted in the data-oriented programming guidelines.
{
var paramTypeNames = paramTypes // | Prefer fluent function call syntax over LINQ syntax (i.e. y.Select(x => z) instead of 'from x in y select z')
.Select(p => p.ToString()) // | Prefer breaking long fluent operator chains into one line per operator
.Where(p => p.Length > 2)
.OrderBy(p => p[0])
.ToArray();
return string.Format(
"{0} {1}({2})",
returnType, name, string.Join(", ", paramTypeNames));
}
}
// |[EventArgs]
// | - Always use structs for EventArgs types, and never extend System.EventArgs [FDG Exception]
// | - Make EventArgs structs immutable
// | - See the event example above for when to define EventArgs structs.
struct ThingHappenedEventArgs
{
public string thingThatHappened { get; }
public ThingHappenedEventArgs(string thingThatHappened)
{
this.thingThatHappened = thingThatHappened;
}
}
// |[Attributes]
// | - Mark up all attributes with an AttributeUsage, as narrow as possible.
// | - Postfix attribute class names with "Attribute".
[AttributeUsage(AttributeTargets.Property)]
public class ExampleAttribute : Attribute
{ // | Empty types have braces on their own lines
}
// |[Exceptions]
// | - Postfix exception class names with "Exception".
// | - Do not inherit from ApplicationException (see http://stackoverflow.com/a/5685943/14582).
public class ExampleException : Exception
{
public ExampleException() {}
public ExampleException(string message) : base(message) {}
public ExampleException(string message, Exception innerException) : base(message, innerException) {}
}
}
// (this stuff is just here to demo some rules above)
namespace Company.BuildSystem
{
public static class DisplayData
{
public const int MaxItems = 100;
}
}