C# 1中定义的产品类型
以定义一个表示产品的类型作为开始,然后进行处理。
其中Product 类型内部封装了几个属性。同时还要创建预定义产品的一个列表。
//代码清单1-1 public class Product { string name; public string Name { get { return name; } } decimal price; public decimal Price { get { return price; } } public Product(string name,decimal price) { this.name = name; this.price = price; } public static ArrayList GetSampleProducts() { ArrayList list = new ArrayList(); list.Add(new Product("A", 1.99m)); list.Add(new Product("B", 2.99m)); list.Add(new Product("C", 3.99m)); list.Add(new Product("D", 4.99m)); return list; } }
以上C# 1代码存在如下3个局限:
①ArrayList 没有提供与其内部内容有关的编译时信息。不慎在 GetSampleProducts 创建的列表中添加一个字符串是完全有可能的,而编译器对此没有任何反应。
②代码中为属性提供了公共的取值方法,这意味着如果添加对应的赋值方法,那么赋值方法也必须是公共的。
③用于创建属性和变量的代码很复杂——封装一个字符串和一个十进制数应该是一个十分简单的任务,不该这么复杂。
来看看C# 2作了哪些改进。
C# 2中的强类型集合
我们所做的第一组改动,针对上面列出的前两项,包含C# 2中最重要的改变:泛型。
//代码清单1-2 public class Product { string name; public string Name { get { return name; } private set { name = value; } } decimal price; public decimal Price { get { return price; } private set { price = value; } } public Product(string name,decimal price) { Name = name; Price = price; } public static List<Product> GetSampleProducts() { List<Product> list = new List<Product>(); list.Add(new Product("A", 1.99m)); list.Add(new Product("B", 2.99m)); list.Add(new Product("C", 3.99m)); list.Add(new Product("D", 4.99m)); return list; } public override string ToString() { return string.Format("{0}:{1}", name, price); } }
现在属性拥有了私有的赋值方法(我们在构造函数中使用了这两个赋值方法)。
并且它能非常“聪明”地猜出 List<Product> 是告知编译器列表中只能包含 Product 。
试图将一个不同的类型添加到列表中,会造成编译时错误,并且当你从列表中获取结果时,也并不需要转换结果的类型。
C# 2解决了原先的3个问题中的2个。下面展示了展示了C# 3如何解决剩下的那个问题。
C# 3中自动实现的属性
自动实现的属性和简化的初始化,相比Lambda表达式等特性来说,有点微不足道,不过它们可以大大地简化代码。
//代码清单1-3 public class Product { public string Name { get; private set; } public decimal Price { get; private set; } Product() { } public static List<Product> GetSampleProducts() { return new List<Product>() { new Product{Name="A", Price=1.99m}, new Product{Name="B", Price=2.99m}, new Product{Name="C", Price=3.99m}, new Product{Name="D", Price=4.99m} }; } public override string ToString() { return string.Format("{0}:{1}", Name, Price); } }
不再有任何代码、可见的变量与属性关联,而且硬编码的列表是以一种全然不同的方式构建的。
由于没有 name 和 price 变量可供访问,我们必须在类中处处使用属性,这增强了一致性。
现在有一个私有的无参构造函数,用于新的基于属性的初始化。(设置这些属性之前,会对每一项调用这个构造函数。)
在本例中,实际上可以完全删除旧的公共构造函数。但这样一来,外部代码就不能再创建其他的产品实例了。
C# 4中的命名实参
对于C# 4,涉及属性和构造函数时,我们需要回到原始代码。
其中有一个原因是为了让它不易变:尽管拥有私有赋值方法的类型不能被公共地改变,但如果它也不能被私有地改变,将会显得更加清晰。
不幸的是,对于只读属性,没有快捷方式,但C# 4允许我们在调用构造函数时指定实参的名称,
如下所示,它为我们提供了和C# 3的初始化程序一样的清晰度,而且还移除了易变性(mutability)。
//代码清单1-4 public class Product { readonly string name; public string Name { get { return name; } } readonly decimal price; public decimal Price { get { return price; } } public Product(string name,decimal price) { this.name = name; this.price = price; } public static List<Product> GetSampleProducts() { return new List<Product>() { new Product(name:"A", price:1.99m), new Product(name:"B", price:2.99m), new Product(name:"C", price:3.99m), new Product(name:"D", price:4.99m) }; } public override string ToString() { return string.Format("{0}:{1}", Name, Price); } }
在这个特定的示例中,该特性的好处不是很明显,但当方法或构造函数包含多个参数时,它可以使代码的含义更加清楚——特别是当参数类型相同,或某个参数为 null 时。
当然,你可以选择什么时候使用该特性,只在使代码更好理解时才指定参数的名称。
图1-1总结了 Product 类型的演变历程。注意,图中没有涉及C# 5。这是因为C# 5主要特性(异步函数)面向的领域还没有太多的语言支持。稍后,我们将简单看一下。
到目前为止,变化幅度都不大。事实上,泛型的加入( List<Product> 语法)或许是C# 2最重要的一个部分。
我们现在只看到了它的部分用处,不过这只是刚刚开始。下一个任务是以字母顺序打印产品列表。