More Effective C# Item4 : 使用泛型强制编译期类型推断
模式的作用在于标准化那些尚未被语言或者框架直接支持的常见算法逻辑。遵循模式即可解决一类常见的问题。提供模式是为了在无法进行代码层面的重用时,也能给出一定程度上编程思想的重用。若使用模式无法得到最优化方案,那是因为很难从模式的实现代码中分离开所有的特殊部分。
泛型类能够帮助你实现很多常用的、可重用的模式。模式的本质在于它必定包含一系列通用的算法和代码,支持应用于应用程序中各种特定类型上。泛型无法应用于每个模式,不过却能极大地减少实现某个模式所需要的代码。
我们来看下面的示例,来使用XML序列化器对对象进行序列化,首先是非泛型版本的。
1 public class XmlPersistenceManager
2 {
3 public static object LoadFormFile(Type typeToLoad, string filePath)
4 {
5 if (!File.Exists(filePath))
6 {
7 return default(object);
8 }
9 XmlSerializer factory = new XmlSerializer(typeToLoad);
10 using (TextReader reader = new StreamReader(filePath))
11 {
12 return factory.Deserialize(reader);
13 }
14 }
15
16 public static void SaveToFile(string filePath, object obj)
17 {
18 if (!File.Exists(filePath))
19 {
20 return;
21 }
22
23 Type typeToLoad = obj.GetType();
24 XmlSerializer factory = new XmlSerializer(typeToLoad);
25 using (TextWriter writer = new StreamWriter(filePath))
26 {
27 factory.Serialize(writer, obj);
28 }
29 }
30 }
上述代码已经提供了一个可以重用的接口,在很大程度上,可以解决使用者遇到的问题。但是上述的做法有以下几个不完美的地方:
- 在LoadFromFile()方法和SaveToFile()方法中,每次调用时都要重新创建一个XmlSerializer对象,虽然.NET框架已经尽可能的降低了创建对象所花费的开销,但还是需要尽量避免创建不必要的对象。
- 来看一下LoadFromFile()的返回值,是一个System.Object类型,对于使用者来说,需要类型转换,这样做会带来不方便。
为了解决上述两个问题,我们需要进行一些思考和改善,首先,我们是否可以向以下这种方式来修改代码呢?
1 public class XmlPersistenceManager
2 {
3 private static XmlSerializer factory = null;
4 public static object LoadFormFile(Type typeToLoad, string filePath)
5 {
6 if (!File.Exists(filePath))
7 {
8 return default(object);
9 }
10 //XmlSerializer factory = new XmlSerializer(typeToLoad);
11 if (factory == null)
12 {
13 factory = new XmlSerializer(typeToLoad);
14 }
15 using (TextReader reader = new StreamReader(filePath))
16 {
17 return factory.Deserialize(reader);
18 }
19 }
20
21 public static void SaveToFile(string filePath, object obj)
22 {
23 if (!File.Exists(filePath))
24 {
25 return;
26 }
27
28 Type typeToLoad = obj.GetType();
29 //XmlSerializer factory = new XmlSerializer(typeToLoad);
30 if (factory == null)
31 {
32 factory = new XmlSerializer(typeToLoad);
33 }
34 using (TextWriter writer = new StreamWriter(filePath))
35 {
36 factory.Serialize(writer, obj);
37 }
38 }
39 }
上述代码并没有解决问题,反而又引发了一些其他问题,如果两次调用XmlPersistenceManager时,第二次调用时,XmlSerializer实例中的类型使用的是第一次调用时的类型,很显然这是不正确的。
我们可以使用泛型的方式来解决上述的两个问题,来看下面的代码。
1 public class XmlPersistenceManager<T>
2 {
3 private static XmlSerializer factory = null;
4
5 public static T LoadFormFile(string filePath)
6 {
7 if (!File.Exists(filePath))
8 {
9 return default(T);
10 }
11 if (factory == null)
12 {
13 factory = new XmlSerializer(typeof(T));
14 }
15 using (TextReader reader = new StreamReader(filePath))
16 {
17 return (T)factory.Deserialize(reader);
18 }
19 }
20
21 public static void SaveToFile(string filePath, T data)
22 {
23 if (!File.Exists(filePath))
24 {
25 return;
26 }
27 if (factory == null)
28 {
29 factory = new XmlSerializer(typeof(T));
30 }
31 using (TextWriter writer = new StreamWriter(filePath))
32 {
33 factory.Serialize(writer, data);
34 }
35 }
36 }
我们来看一下,针对第一个问题,上述中factory作为全局变量,是不会产生之前代码的问题的,因为对于泛型来说,factory针对每一种泛型类型,只会保留一个实例;针对第二个问题,我们来看一下LoadFromFile()方法,它的返回值已经是泛型类型了。
下面我们来看一下针对上述代码的测试程序。
1 [Serializable]
2 public class Employee
3 {
4 private string m_strName;
5
6 [XmlElement("Name")]
7 public string Name
8 {
9 get { return m_strName; }
10 set { m_strName = value; }
11 }
12
13 private string m_strAddress;
14
15 [XmlElement("Address")]
16 public string Address
17 {
18 get { return m_strAddress; }
19 set { m_strAddress = value; }
20 }
21
22 public override string ToString()
23 {
24 return string.Format("{0},{1}", m_strName, m_strAddress);
25 }
26 }
27
28
29 private static void TestWithoutGenerics()
30 {
31 Console.WriteLine("TestWithoutGenerics:");
32 Employee empA = new Employee();
33 empA.Name = "Wing";
34 empA.Address = "BeiJing";
35 Console.WriteLine(empA.ToString());
36 XmlPersistenceManager.SaveToFile(@"D:\ConfigSample.xml", empA);
37
38 Object obj = XmlPersistenceManager.LoadFormFile(typeof(Employee), @"D:\ConfigSample.xml");
39 if (obj is Employee)
40 {
41 Console.WriteLine((obj as Employee).ToString());
42 }
43 }
44
45 private static void TestWithGenerics()
46 {
47 Console.WriteLine("TestWithGenerics:");
48 Employee empA = new Employee();
49 empA.Name = "UnKnown";
50 empA.Address = "Moon";
51 Console.WriteLine(empA.ToString());
52 XmlPersistenceManager<Employee>.SaveToFile(@"D:\ConfigSample.xml", empA);
53
54 Employee empTemp = XmlPersistenceManager<Employee>.LoadFormFile(@"D:\ConfigSample.xml");
55 Console.WriteLine(empTemp.ToString());
56 }
总结:在实现某一算法逻辑时,我们很多时候需要知道传入参数的类型,这时,通常可以创建一个泛型的实现,将方法的类型参数抽象到泛型参数中,随后编译器即可根据泛型参数创建出所需要的类型。