在程序中需要进行判断的时候,我们经常使用分支结构,如if-else和switch-case分支结构,当然,有时候我们也和循环体联合、多重分支结构嵌套使用以实现复杂判断。
当然,如大多数朋友所熟知,如果只有简单的判断(即情况只有‘是’和‘否’两种),且需要通过判断进行变量赋值时,我们可以使用“三段式”(即在变量赋值语句中使用如下语法)完成。
1 using System; 2 3 namespace EGForSomeSmallSkills 4 { 5 class Test1 6 { 7 static void Main(string[] args) 8 { 9 // 在如下代码中完成字符串"strTest"的赋值 10 // 语句由 类型 变量名 = 判断条件 ? 可赋值1 : 可赋值2; 组成 11 // 如果判断条件成立变量被赋值为可赋值1,反之为可赋值2 12 string strTest = 1 > 2 ? "1大于2成立" : "1大于2不成立"; 13 Console.WriteLine(strTest); 14 Console.ReadKey(); 15 // 输出结果为 "1大于2不成立" 16 // 如下写法错误 17 // 1 > 2 ? Console.WriteLine("1大于2成立") : Console.WriteLine("1大于2不成立"); 18 } 19 } 20 }
通过这种“三段式”,可以简化我们程序的编码,也是思路和逻辑更加清晰。
在实际编码中,我们为了减少异常抛出率,增加程序的健壮性(即代码的严谨性),时常在引用类型的使用之前进行是否为空的判断,在以前使用if-else判断,使代码显得冗余或者麻烦,那么现在我们可以使用“三段式”进行判断。
1 using System; 2 3 namespace EGForSomeSmallSkills 4 { 5 class Test2 6 { 7 static void Main(string[] args) 8 { 9 string strTest1 = null; 10 string strTest2 = strTest1 == null ? "将要被赋的值为空" : "将要被赋的值不为空"; 11 // 以下的写法也可以,调用string类的静态方法判断参数字符串是否为空、空字符串或以空白字符组成 12 // 也可以用string.IsNullOrEmpty(string value)方法,可以判断参数字符串是否为空、空字符串组成 13 //string strTest2 = string.IsNullOrWhiteSpace(strTest1) ? "将要被赋的值为空" : "将要被赋的值不为空"; 14 Console.WriteLine(strTest2); 15 Console.ReadKey(); 16 // 输出结果为 "将要被赋的值为空" 17 18 // 条件简单时,可以不将条件用括号提升优先级、保证完整 19 // 当然,也可以在为空的情况下为变量赋默认值 20 // 如下 21 // string strTest2 = (strTest1 == null) ? "将要被赋的值为空,这是默认值" : strTest1; 22 // 输出结果为 "将要被赋的值为空,这是默认值" 23 } 24 } 25 }
但是这样看起来没有显著地减少代码量是吧?别急,当然有符号可以满足我们对代码更简练的要求,它可以在判断为空并赋默认值的时候大发神威哦,比如下面的示例。
1 using System; 2 3 namespace EGForSomeSmallSkills 4 { 5 class Test3 6 { 7 static void Main(string[] args) 8 { 9 string strTest1 = null; 10 string strTest2 = strTest1 ?? "将要被赋的值为空,这是默认值"; 11 // 以上语句完全等同于如下代码 12 // string strTest2 = strTest1 == null ? "将要被赋的值为空,这是默认值" : strTest1; 13 Console.WriteLine(strTest2); 14 Console.ReadKey(); 15 // 输出结果为 "将要被赋的值为空" 16 } 17 } 18 }
是不是很方便?为变量赋值时在等号右边给出两个可选值,用??隔开,如果??左边的值不为空,则将??左边的值赋给变量,否则就将右边的值赋给变量,即赋默认值。
当然,我经常在公开属性里使用此语句,如下。
1 using System; 2 using System.Collections.Generic; 3 4 namespace EGForSomeSmallSkills 5 { 6 class Test4 7 { 8 private string _name; 9 10 public string Name 11 { 12 // 在访问器的读取器中使用,如果_name为空值,返回默认的值 13 get { return _name ?? "Johness"; } 14 set { _name = value; } 15 } 16 17 // 此语句在如下访问器中尤其有效 18 private List_list; 19 20 public List List 21 { 22 // 对于比较不细心的朋友来说 23 // 这里加上一句话有助于减少异常 24 get { return _list ?? new List (); } 25 set { _list = value; } 26 } 27 28 // 当然,这是治标不治本的方法 29 // 最好的解决办法是在构造方法实体内对引用类型进行必要的初始化 30 // 如下 31 public Test4() 32 { 33 _list = new List (); 34 // 我一般即时在类内部也尽量使用字段的对外属性 35 // 即这样书写 36 // List = new List (); 37 // 通过构造方法参数等对引用类型属性进行初始化 38 // …… 39 } 40 } 41 }
下面我们要介绍另一个强大的符号,?。使用它,我们可以在需要的情况下使值类型可空引用,即使值类型的值可以引用空,可以判断是否为空,请看下面的代码。
1 using System; 2 3 namespace EGForSomeSmallSkills 4 { 5 class Test5 6 { 7 public static void Main(string[] args) 8 { 9 // int i = null; 10 // 一般情况下,以上代码是违反C#(其他大部分语言是一样)规范的 11 // 编译无法通过会产生“无法将 Null 转换成“int”,因为它是一种不可以为 null 值的类型”的错误 12 // 但是一下代码是可以的! 13 int? i = null; 14 // 在值类型声明时在声明的类型后添加?,时被声明值类型拥有部分引用类型的特质 15 // 比如可为空、可比较为空 16 17 Console.WriteLine(i == null); 18 // 输出“True” 19 20 // 但是,在这样做了之后,又有了一个难题:int?类型并不是int了,原来取得其值的方法(如下) 21 // int j = i; 22 // 就会发生错误,这是因为类型不同了 23 // 我们希望取得int?型的值可以用如下表达式 24 int j = i.Value; 25 // 当然,如果int?为空的话,它对应的value也是为空 26 // 现在如果使用j,会发生异常 27 } 28 } 29 }
通过以下例子,看看System.Nullable<T>,即值类型加?的实际应用(注:参考(Nullable(Of T)结构
))。
1 using System; 2 3 namespace EGForSomeSmallSkills 4 { 5 class Test6 6 { 7 public static void Main(string[] args) 8 { 9 Test(0.3, 100); 10 // 输出 “是会员 应付金额:30” 11 Test(null,100); 12 // 输出 "不是会员 应付金额:100" 13 } 14 15 ///16 /// 一下方法演示通过传递的“折扣”是否为空判断方法调用者是否为VIP 17 /// 18 /// 折扣,在本示例中表示VIP的折扣 19 /// 为0到1的小数 20 /// 如果传递null,则表示为普通用户,没有打折 21 /// 22 /// 原价 23 public static void Test(double? discount, double money) 24 { 25 double discountR = discount ?? 1; // 保存折扣,如果不是VIP则折扣率为1 26 double pay = money * discountR; // 实际应付金额 27 string outPutStr = (discount == null ? "不是会员" : "是会员") + " 应付金额:" + pay; // 连接语句并输出 28 Console.WriteLine(outPutStr); 29 Console.ReadKey(); 30 } 31 } 32 33 }
接下来我们说一说new,这个东西我们平时用来创建对象新示例的关键字其实还可以作为修饰符使用,而我们现在主要研究一下new作为修饰符使用,先看看下面的例子。
总所周知,在类的继承里,JAVA奉行所有的父类方法都可以被子类覆盖重写,即所有方法都是C#里对应的虚方法。那么这样其实可能会造成效率的损失,而在C#里有实例方法,但是我们如何在父类示例方法的基础上进行扩展,即覆盖重写父类实例方法呢?
1 using System; 2 3 namespace EGForSomeSmallSkills 4 { 5 class Test7_Pro 6 { 7 public static void Main(string[] args) 8 { 9 Test7_Child child = new Test7_Child(); // 创建子类对象 10 Test7_Base bas = child; // 用父类引用保存子类对象 11 Test7_Base basR = new Test7_Base(); // 创建父类对象 12 13 basR.ViMethod(); // 调用父类对象的虚方法 14 bas.ViMethod(); // 调用父类引用保存子类对象的重写的虚方法(没有重写即调用父类虚方法) 15 (bas as Test7_Child).ViMethod(); // 调用子类重写自父类的虚方法 即 child.ViMethod(); 16 17 basR.EnMethod(); // 调用父类实例方法 18 bas.EnMethod(); // 调用父类实例方法(不会涉及到子类的方法) 19 (bas as Test7_Child).EnMethod(); // 调用子类与父类实例方法重名的方法 20 21 // 输出结果 22 /* 执行了父类的虚方法! 23 执行了子类重写自父类的虚方法! 24 执行了子类重写自父类的虚方法! 25 执行了父类实例方法! 26 执行了父类实例方法! 27 执行了子类与父类实例方法同名的方法!*/ 28 } 29 } 30 31 class Test7_Base 32 { 33 // 父类虚方法,用于实现多态,可以被子类重写 34 public virtual void ViMethod() 35 { 36 Console.WriteLine("执行了父类的虚方法!"); 37 } 38 // 父类示例方法,属于父类而不属于其派生类 39 public void EnMethod() 40 { 41 Console.WriteLine("执行了父类实例方法!"); 42 } 43 } 44 45 class Test7_Child : Test7_Base 46 { 47 // 重写父类虚方法 48 public override void ViMethod() 49 { 50 Console.WriteLine("执行了子类重写自父类的虚方法!"); 51 } 52 // 使用new修饰符向父类隐藏与父类冲突的实例方法 53 // new和public的顺序可以交换 54 public new void EnMethod() 55 { 56 Console.WriteLine("执行了子类与父类实例方法同名的方法!"); 57 } 58 } 59 60 }
new作为修饰符主要是向父类隐藏同名成员,在接口也可以用。
在作为对象声明时,new关键字还有着更为重要的作用,上一次随笔我就匿名委托(或匿名方法)做了浅浅的分析,这次介绍.NET3.0和4.0的新特性:匿名类型和动态类型。(注:参考(匿名类型(C# 编程指南))(使用类型 dynamic(C# 编程指南)))
1 using System; 2 3 namespace EGForSomeSmallSkills 4 { 5 class Test8 6 { 7 public static void Main(string[] args) 8 { 9 var v = new { Name = "Johness" }; // 定义匿名类型变量 10 Console.WriteLine("Hello," + v.Name); // 使用该对象 11 // 输出“Hello,Johness” 12 // 在.NET4.0的Linq查询中会大量使用匿名类型 13 } 14 } 15 }
在我们的编码中也可以使用匿名类型的属性赋值方式,如下:
1 using System; 2 3 namespace EGForSomeSmallSkills 4 { 5 class Test9_Pro 6 { 7 public static void Main(string[] args) 8 { 9 Test9 t91 = new Test9(19, "Johness"); 10 // 相比参数较多带来的传递不便,下面的代码简洁且清晰 11 Test9 t92 = new Test9 { Name = "Johness", Age = 19 }; 12 // 也可以显示调用带参构造方法并在其后使用类似匿名类型声明方式完成属性初始化 13 // 注意:只读域不能在如下{}中声明,但可以在构造方法体中初始化,如下 14 Test9 t93 = new Test9(5) { Name = "Johness", Age = 19 }; 15 Console.WriteLine(string.Format("年龄:{0} 姓名:{1}", t91.Age, t91.Name)); 16 Console.WriteLine(string.Format("年龄:{0} 姓名:{1}", t92.Age, t92.Name)); 17 Console.WriteLine(string.Format("年龄:{0} 姓名:{1}", t93.Age, t93.Name)); 18 // 输出 19 // 年龄:19 姓名:Johness 20 // 年龄:19 姓名:Johness 21 // 年龄:19 姓名:Johness 22 } 23 } 24 25 class Test9 26 { 27 private readonly int i; 28 public int Age { get; set; } 29 public string Name { get; set; } 30 // …… 31 32 public Test9(int age, string name) // 在属性较多并且需要在构造对象时初始化的情况下,我们可能会对构造方法进行重载 33 { // 但是即时经过重载,我们传递较多参数时仍可能混淆先后次序,造成不便 34 this.Age = age; 35 this.Name = name; 36 } 37 38 public Test9(int i) // 传递只读属性初始值进行只读域初始化 39 { 40 this.i = i; 41 } 42 43 public Test9() 44 { 45 46 } 47 } 48 }
动态类型和匿名类型的差别有两点:一是匿名类型的真正类型只能指定一次,而动态类型不是,二是匿名类型是.NET3.0新特性,而动态类型是.NET4.0新特性……开玩笑的,不过在以下实例中可以真正看到他们的第二点不同:
1 using System; 2 3 namespace EGForSomeSmallSkills 4 { 5 class Test10 6 { 7 public static void Main(string[] args) 8 { 9 // 可以将匿名类型转化为对象类型返回 10 object obj1 = GetSomeObjectByVar(); 11 // 但系统无法识别它的真正类型 12 //Console.WriteLine(obj1.Name); 13 14 dynamic obj2 = GetSomeObjectByDynamic(); 15 Console.WriteLine(obj2.Name); 16 17 // 输出“Johness” 18 } 19 20 public static object GetSomeObjectByVar() 21 { 22 var someObj = new { Name = "Johness", Age = 19 }; 23 return someObj; 24 } 25 26 // 由于是动态类型,返回值可以为object、dynamic 27 public static dynamic GetSomeObjectByDynamic() 28 { 29 // dynamic 也可以替换为var、object 30 dynamic someObj = new { Name = "Johness", Age = 19 }; 31 return someObj; 32 } 33 } 34 }
这次的小技巧就到这儿。