在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
本文为《effective c#》的读书笔记,此书类似于大名鼎鼎的《effective c++》,是入门后提高水平的进阶读物,此书提出了50个改进c#代码的原则,但是由于主要针对.net framework,而unity中的mono平台只支持.net framework 2.0,所以有很多原则在unity中并不适用,本文总结了其中在unity中也适用的一些原则。整理后,一共20多个原则仍然适用于unity,将分为两篇文章来记录。 1 使用属性,不使用可访问的数据成员属性更加灵活,有了新的需求时便于修改。例如定义name属性,后来我们想到名字不应为空,那么只需要修改一处就可以,如果使用公有成员变量,则要在每处使用的地方修改。还可以使用所引器来访问。 public class MyClass { private string[] namelist = new string[10]; private string username; public string Username { get { return username; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentException("Username can't be blank!"); } username = value; } } //简单索引器 public string this[int idx] { get { if (idx < namelist.Length && idx >= 0) { return namelist[idx]; } return string.Empty; } set { if (idx >= 0 && idx < namelist.Length) { namelist[idx] = value; } } } public void UseIndexor() { this[0] = "first name";//效果等同于namelist[0]="first name" } } C#建议总是使用property,但是在Unity中有很多局限性: 1.如果在monobehavior脚本类中使用property,那么在编辑器的inspector中无法显示出property。 2 使用运行时常量readonly而不是编译时常量constconst性能有优势但是微乎其微,且只能应用于基本数据类型,readonly更加灵活,可以应用于所有类型。 3 使用as 或 is,不用强制类型转换使用as后需要检查是否为null ,对于值类型不能使用as,应该使用is。总之,能用as用as,不能则用is判断后用强制类型转换。 public class Test { internal class MyType { } internal class MyFactory { public static MyType GetObject() { return new MyType(); } } void asAndIs() { object o = MyFactory.GetObject(); MyType t = o as MyType; if (t != null) { //t is a MyType } else { // failed,throw exception } object o2 = MyFactory.GetObject(); //int i = o2 as int;//编译错误 //应该使用is if (o2 is int) { int i2 = (int) o2; } else { // failed,throw exception } } } 4 使用Conditional attribute而不是#if 条件编译Conditional应用于方法,因此强制我们把条件代码拆分成不同的方法,这比使用#if结构更加清晰。 public class UseConditional { [Conditional("DEBUG")] void NewDebugFunc() { //do something } void MainFunc() { NewDebugFunc(); //do something } //多个特性默认为or运算,如果有其他需要则需自定义 [Conditional("DEBUG"), Conditional("REALESE")] void OrConditionFunc() { //如果定义了DEBUG或者REALESE,会执行这个方法 } //如果要多个条件执行and运算,需要自定义 #if (VAR1 && VAR2) #define BOTH #endif [Conditional("BOTH")] void AndConditionFunc() { //dosomething } } 5 等同性判断4种方法判断等同性 注意,覆写Equals()实例方法的同时也要覆写GetHashCode()方法 例子: //引用类型实现值相等性 class TwoDPoint : IEquatable<TwoDPoint> { public int X { get; set; } public int Y { get; set; } public TwoDPoint(int x, int y) { this.X = x; this.Y = y; } public override bool Equals(object obj) { return this.Equals(obj as TwoDPoint); } public bool Equals(TwoDPoint other) { if (object.ReferenceEquals(other, null)) { return false; } if (object.ReferenceEquals(this, other)) { return true; } if (this.GetType() != other.GetType()) { return false; } return (X == other.X) && (Y == other.Y); } public override int GetHashCode() { return X*0x00010000 + Y; } public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs) { if (object.ReferenceEquals(lhs, null)) { if (object.ReferenceEquals(rhs, null)) { return true; //null==null = true } return false; } return lhs.Equals(rhs); } public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs) { return !(lhs == rhs); } } //值类型实现相等性,默认使用反射方法验证相等性,性能差,所以总是应该自己实现 struct TwoDPointStruct : IEquatable<TwoDPointStruct> { public int X { get; set; } public int Y { get; set; } public TwoDPointStruct(int x, int y) : this() { X = x; Y = y; } public override bool Equals(object obj) { if (obj is TwoDPointStruct) { return this.Equals((TwoDPointStruct) obj); } return false; } public bool Equals(TwoDPointStruct other) { return (X == other.X) && (Y == other.Y); } public override int GetHashCode() { return X ^ Y; } public static bool operator ==(TwoDPointStruct lhs, TwoDPointStruct rhs) { return lhs.Equals(rhs); } public static bool operator !=(TwoDPointStruct lhs, TwoDPointStruct rhs) { return !(lhs == rhs); } } 6 GetHashCode的陷阱GetHashCode()仅在一个地方用到,为基于散列的集合定义键的散列值时,如HashSet<T>,Dictionary<K,V>容器等。 7 理解短小方法的优势JIT编译器在运行时把C#编译器生成的IL代码翻译成机器码,这个过程不是在启动一开始就进行的,而是按照函数的粒度来进行,没有被调用的方法不会被JIT编译 public void LargeFunc(bool flag) { if (flag) { //do something } else { //do other things } } //如下函数则只会编译一个分支。另外分支用到时才编译 public void LargeFuncWithShortFunc(bool flag) { if (flag) { DoSomeThing(); } else { DoOtherThings(); } } private void DoOtherThings() { //do something } private void DoSomeThing() { //do other things } 函数简单短小,也让jit更好的决定哪些变量放到寄存器上,以及哪些函数编译为内联函数。总之,使用简单短小的函数,有助于jit进行更好的优化。 8 推荐使用成员初始化器,而不是赋值语句随着时间的推移,通常来说构造函数越来愈多,预防这种情况最好的方法就是在变量声明时就进行初始化,而不是在每个构造函数中进行。 有如下三种情况,应该避免使用初始化器 2当需要对一个变量进行不同的初始化方法时,不应使用初始化器 这个构造函数使用不同的方法初始化labels,相当于构造函数之前初始化一次,这里又初始化一次,无谓的消耗应该避免。 3最后一种情况是,初始化器无法进行异常处理,当需要进行异常处理时,把初始化工作放到构造函数内。 总上,初始化器是保证成员变量都被初始化的最简单方便的方法,若是所有的构造函数都要将某个成员初始化为同一个值,那么就应该使用初始化器。 9 正确的初始化静态成员变量使用静态初始化器和静态构造函数来初始化静态成员变量 private MyClass () public static MyClass Instance 如果需要更复杂的初始化,或者异常处理,可以使用静态构造函数,静态构造函数在所有实例成员变量初始化器以及实例构造函数前调用。 private static readonly MyClass instance; private MyClass () public static MyClass Instance static MyClass () 10 尽量减少重复的初始化逻辑把多个构造函数中的重复逻辑提取到一个公共构造函数中,其他构造函数可以通过构造函数初始化器调用公共构造函数 private List<object> data; private string name; public MyClass():this(0,string.Empty) { } public MyClass(int initCount) : this(initCount, string.Empty) { } public MyClass(int initCount, string name) { data = initCount > 0 ? new List<object>(initCount) : new List<object>(); this.name = name; } 11 避免创建非必要的对象创建对象会在堆上分配内存,带来GC的工作量,有一些规则可以让你尽量降低GC的工作量,关于Unity中的gc优化,请参考 Unity性能优化(3)-官方教程Optimizing garbage collection in Unity games翻译 未完待续…… |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论