Design Hub

    • 注册
    • 登录
    • 搜索
    • 版块
    • 最新

    自用C#代码规范

    游戏开发
    1
    1
    281
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    • 在新帖中回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • Pamisu
      Pamisu 管理员 最后由 Pamisu 编辑

      在没有代码规范的情况下,一个多人合作的项目中很可能会出现多种代码风格,就像是一栋房子的装修中同时出现中式古典风格、现代简约风格、欧式奢华风格以及亚马逊原始风格。抛开美观不谈,这对项目成员间的协作以及后续维护会造成较大的阻碍,所以统一的代码规范是必要的。

      本规范综合参考Godot C# Style Guide、微软的C# Coding Style与Unity Code Style Guide,可作为Godot、Unity、.NET等C#项目代码规范。

      文中未特别说明部分优先遵循Godot C# Style Guide,其次遵循微软的C# Coding Style。

      格式

      本地文件中的换行符在提交到git后应转换为LF,而不是CRLF或CR,一般情况下git默认开启此功能。

      使用UTF-8无BOM编码,如果使用Visual Studio需要注意设置。

      使用4个空格作为tab键缩进,一般这是Unity、VSCode、Rider的默认设置,Godot需要在Editor Settings -> Text Editor -> Behavior -> Indent中修改。

      花括号换行使用Allman风格,而非K&R风格:

      while (x == y) 
      {
          DoSomething();
          DoSomethingElse();
      }
      
      if (x > 0)
      {
          DoSomething();
      }
      

      内容只有一行时,可省略花括号:

      while (x == y) 
          DoSomething();
      
      if (x > 0)
          DoSomething();
      

      属性的get/set方法,以及方法体较为简单时,可写成一行:

      public interface MyInterface
      {
          int MyProperty { get; set; }
      }
      
      public class MyClass : ParentClass
      {
          public int Value
          {
              get { return 0; }
              set
              {
                  ArrayValue = new [] {value};
              }
          }
      
          public int Foo() { return GetSomeValue(); }
      
          public void Bar() => DoSomething();
      }
      

      字段、属性、方法之间的空行不要超过2行。

      方法内语句之间的空行不要超过2行。

      保持代码紧凑易阅读,不要无意义空行。

      命名

      基础

      C#代码文件使用PascalCase命名,例如MyClass.cs。

      不使用默认命名空间,即必须指定类所在的命名空间,命名空间使用PascalCase。

      namespace Game 
      {
          public class MyClass
          {
          }
      }
      

      C# 10.0以上可使用文件范围的命名空间。

      namespace Game;
      
      public class MyClass
      {
      }
      

      命名空间与文件夹结构尽量保持一致,例如namespace Game.Combat.Characters,则其文件夹结构为Game/Combat/Characters。

      接口使用PascalCase命名,加“I”前缀。

      接口成员的访问修饰符没有要求,其他地方使用显式访问修饰符。

      public interface ITouchable
      {
          // 接口成员可省略访问修饰符
          void Interact();
      }
      

      枚举使用PascalCase命名,不加任何前缀。

      public enum DrinkType
      {
          None,
          Soft,
          Hard
      }
      

      类与结构体使用PascalCase命名,不加任何前缀,基类可用“BaseXxx”命名但不是硬性要求。

      public class SomeClass
      {
      }
      

      所有常量使用PascalCase命名,无论其访问修饰符是什么,包括局部常量,且不使用任何前缀如“k_”。

      public class SomeClass
      {
          public const float DefaultSpeed = 10f;
          private const string LogTag = "MyClass";
      }
      

      所有private字段,无论是否为static,均使用camelCase命名,加下划线前缀,除此之外不使用其他前缀如“m_”、“s_”、“t_”等。

      public class SomeClass
      {
          private static T _instance;
          private static readonly object _lockObj = new();
          private Vector3 _aimingAt;
          private Vector3 _velocity;
      }
      

      非private字段使用PascalCase命名。

      public class SomeClass
      {
          protected int HitPoints;
          internal int State;
          public string Name;
      }
      

      所有属性使用PascalCase命名,无论其访问修饰符是什么。

      public class SomeClass
      {
          private bool IsAlive => HitPoints > 0;
      
          protected float MyProperty { get; set; }
      
          public float AnotherProperty
          {
              get { return MyProperty; }
          }
      }
      

      所有方法使用PascalCase命名。

      public class SomeClass
      {
          public void MyMethod() 
          {
          }
      }
      

      局部变量及方法参数使用camelCase命名,不加任何前缀。局部常量使用PascalCase命名。

      public float SomeMethod(float someValue) 
      {
          const float Increment = 1.2f;
          var result = someValue + Increment;
          return result;
      }
      

      可读性

      含有三个字母及以上的缩写词时,遵循当前命名约定,例如“APIHandler”在PascalCase时写作“ApiHandler”,camelCase时写作“apiHandler”。

      两个字母的缩写词为特例,例如“UIUtil”在PascalCase时写作“UIUtil”,camelCase时写作“uiUtil”,仅限首字母缩写词,例如“Id”不是首字母缩写。

      命名应该尽量表达清晰,尽量达到自解释,缩写不要影响可读性,例如:

      FindNearbyEnemy()?.Damage(weaponDamage); √
      
      FindNode()?.Change(wpnDmg); ×
      

      bool变量不加任何固定的前缀,例如“b”。但推荐使用“is”、“has”等词来表明其含义,例如“isDead”, “isWalking”, "hasDamageMultiplier"。

      尽量使用动词短语为事件命名,用动词时态区分事件发生的时机。事件不加任何前缀或后缀。

      public event Action OpeningDoor;    // 开门事件发生之前
      public event Action DoorOpened;     // 开门事件发生之后
      

      事件的接收方法以“On事件名”命名。

      public void OnOpeningDoor() 
      {
      }
      
      public void OnDoorOpened() 
      {
      }
      

      非特殊情况不使用拼音命名。

      避免拼写错误,很多时候拼写错误会造成一些让人一时摸不着头脑的Bug(例如JSON序列化、服务端传值错误等等),建议开启编辑器的拼写检查功能。

      示例

      namespace MyGame;
      
      // 类与结构体使用PascalCase命名,不加任何前缀
      public class MyClass<T, R> : Parent<T, R> where T : class, new()
      {
          // 所有常量使用PascalCase命名
          public const float DefaultSpeed = 10f;
          private const string LogTag = "MyClass";
      
          // 所有private字段使用camelCase命名,加下划线前缀
          // 使用显式访问修饰符,不省略private
          private static T _instance;
          private static readonly object _lockObj = new();
          private Vector3 _aimingAt;
          private Vector3 _velocity;
      
          // 非private字段使用PascalCase命名
          protected int HitPoints;
          internal int State;
          public string Name;
      
          // 所有属性使用PascalCase命名
          private bool IsAlive => HitPoints > 0;
      
          protected float MyProperty { get; set; }
      
          public float AnotherProperty
          {
              get { return MyProperty; }
          }
      
          public static T Instance
          {
              get
              {
                  if (null == _instance)
                  {
                      lock (_lockObj)
                      {
                          _instance ??= new T();
                      }
                  }
                  return _instance;
              }
          }
      
          // 所有方法使用PascalCase命名
          public void MyMethod()
          {
              int[] values = {1, 2, 3, 4};
              int sum = 0;
      
              for (int i = 0; i < values.Length; i++)
              {
                  switch (i)
                  {
                      case 3: return;
                      default:
                          sum += i > 2 ? 0 : 1;
                          break;
                  }
              }
      
              i += (int)MyProperty;
          }
      
          // 使用显式访问修饰符,不省略private
          private int MyMethod2() 
          {
              return 0;    
          }
      }
      
      1 条回复 最后回复 回复 引用
      • 1 / 1
      • First post
        Last post
      版权所有 ©2023 Design Hub 保留所有权利
      鄂ICP备2021004164号-2