本风格指南适用于 Google 内部开发的 C# 代码,是 Google C# 代码的默认风格。 它在风格选择上与其他 Google 语言保持一致,例如 Google C++ 风格和 Google Java 风格。
命名规则遵循 Microsoft 的 C# 命名指南。 对于 Microsoft 未指定的命名指南(例如,私有变量和局部变量),规则取自 CoreFX C# 编码指南
规则总结
PascalCase
。camelCase
。_camelCase
。MyRpc
而不是 MyRPC
I
开头,例如 IInterface
。PascalCase
,例如 MyFile.cs
。MyClass.cs
。public protected internal private new abstract virtual override sealed static readonly extern unsafe volatile async
。using
声明位于顶部,在任何命名空间之前。 using
导入顺序是字母顺序,除了 System
导入始终排在第一位。基于 Google Java 风格开发。
else
之间没有换行。if
/for
/while
等之后以及逗号之后有空格。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
的变量和字段应始终设置为 const
。const
不可行,则 readonly
可以作为合适的替代方案。IReadOnlyCollection
/ IReadOnlyList
/ IEnumerable
用作方法的输入。IList
而不是 IEnumerable
。 如果不转移所有权,则首选最具限制性的选项。ToList()
直接转换为容器的生成器代码的性能将低于直接填充容器的性能。=>
)。{ get; set; }
语法。例如
int SomeProperty => _someProperty
结构与类截然不同
transform.position.x = 10
不会将变换的 position.x 设置为 10;此处的 position
是一个属性,该属性按值返回 Vector3
,因此这只会设置原始副本的 x 参数。几乎总是使用类。
当类型可以像其他值类型一样处理时,请考虑使用结构 - 例如,如果类型的实例很小且通常生命周期很短,或者通常嵌入在其他对象中。 好的示例包括 Vector3、Quaternion 和 Bounds。
请注意,此指导可能因团队而异,例如,性能问题可能会迫使使用结构。
out
。out
参数放在方法定义中所有其他参数之后。ref
应很少使用,仅在必须修改输入时使用。ref
作为传递结构的优化。ref
将可修改的容器传递到方法中。 仅当需要用完全不同的容器实例替换提供的容器时,才需要 ref
。myList.Where(x)
而不是 myList where x
。Container.ForEach(...)
。List<>
而不是数组(请记住上面关于 IList
/ IEnumerable
/ IReadOnlyList
的指导)。List<>
。List<>
都代表线性、连续的容器。std::vector
类似,数组的容量是固定的,而可以向 List<>
中添加内容。List<>
更灵活。Tuple<>
,尤其是在返回复杂类型时。String.Format()
vs String.Concat
vs operator+
operator+
连接会降低速度并导致严重的内存抖动。StringBuilder
对于多个字符串连接会更快。using
using
为长类型名称创建别名。 通常,这表明需要将 Tuple<>
转换为类。using RecordList = List<Tuple<int, float>>
可能应该是一个命名的类。using
语句仅在文件范围内,因此用处有限。 类型别名将不适用于外部用户。例如
var x = new SomeClass {
Property1 = value1,
Property2 = value2,
};
unity_app
,命名空间不是必须的。out
值。备注
StatusOr
。C#(像许多其他语言一样)没有提供一种明显的机制来在迭代时从容器中移除元素。 有几种选择
someList.RemoveAll(somePredicate)
。RemoveAll
可能不足以满足需求。 一种常见的替代模式是在循环外部创建一个新容器,将要保留的元素插入到新容器中,并在迭代结束时将原始容器与新容器交换。Invoke()
并使用空条件运算符 - 例如 SomeDelegate?.Invoke()
。 这在调用点清楚地标记了“正在调用一个委托”。 空检查简洁且对线程竞争条件具有鲁棒性。var
关键字var
。鼓励
var apple = new Apple();
,或 var request = Factory.Create<HttpRequest>();
var item = GetItem(); ProcessItem(item);
不鼓励
var success = true;
var number = 12 * ReturnsFloat();
var listOfItems = GetList();
源自 Google C++ 风格指南。
当函数参数的含义不明显时,请考虑以下补救措施之一
enum
参数替换 bool
参数。 这将使参数值具有自描述性。考虑以下示例
// Bad - what are these arguments?
DecimalNumber product = CalculateProduct(values, 7, false, null);
与
// Good
ProductOptions options = new ProductOptions();
options.PrecisionDecimals = 7;
options.UseCache = CacheUsage.DontUseCache;
DecimalNumber product = CalculateProduct(values, options, completionDelegate: null);