首页 > 代码库 > CLR类型设计之泛型(二)
CLR类型设计之泛型(二)
在上一篇文章中,介绍了什么是泛型,以及泛型和非泛型的区别,这篇文章主要讲一些泛型的高级用法,泛型方法,泛型泛型接口和泛型委托,协变和逆变泛型类型参数和约束性,泛型的高级用法在平时的业务中用的不多,多用于封装高级方法和一些底层封装,前几天读了一篇文章,如何选择网络上的技术文章,因为现在关于技术的文章可以说非常多,但是时间是有限的,如果花很多时间阅读了一篇文章却没有什么用,岂不是很浪费时间,所以第一步选择自己感兴趣的文章阅读,第二要把阅读过的文章尽可能实现一次,读书万遍不如走上一遍,第三尽量不读翻译性的文章,这里其实我觉得不是所有人都能很轻松的看懂官方文档,所以这点还是仁者见仁,智者见智。为了让文章尽可能的有深度,所以我觉得以后的博文中应该尽可能的贴出的知识模块都有所解释,有所理解。不是在网上复制粘贴以后在给别人看,博文不是笔记,所以要说出自己的见解
说了这么多,那么就开始务实的甩开膀子做吧!
泛型方法
既然讲到了泛型是为了高级封装,那么我们就来封装一个C#中的ORM吧,在封装ORM之前还要有一个SQL帮助类,这个网上有很多,感兴趣的可以直接到网上找一个,C#中封装的ORM最好的Entity FromWork,感兴趣的可以看下源码,我们先看下下面的代码,这是一个典型的泛型方法
1 /// <summary> 2 /// 查询获得一个实体 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 /// <param name="sql"></param> 6 /// <param name="sqlParameters"></param> 7 /// <param name="transaction"></param> 8 /// <returns></returns> 9 public static T Get<T>(string sql, IList<SqlParameter> sqlParameters = null, IDbTransaction transaction = null) 10 { 11 DataTable table = SQLServerHelper.Query(sql, sqlParameters, transaction); 12 if (table != null && table.Rows != null && table.Rows.Count > 0) 13 { 14 DataRow row = table.Rows[0]; 15 return ConvertRowToModel<T>(row, table.Columns); 16 } 17 else 18 { 19 Type modeType = typeof(T); 20 return default(T); 21 } 22 23 }
注释已经表明了这是用来获取一个实体的Get<T>()方法中T定义为泛型类型,方法有一个必选参数两个默认参数,string类型的sql语句,默认IList<SqlParameter> sqlParameters继承自IList集合的SQL参数。这是参数化SQL的,每一个SQL语句都应该写成参数化的,还有一个IDbTransaction transaction = null是否开启事务,有了这样三个参数就可以定义一个查询指定SQL语句,是否有Where条件,是否开启事务的方法,方法继续执行,第是一行代码是使用帮助类获得数据,第14行和第15行是将获得数据映射成对应的结构体,我们可以看下 ConvertRowToModel<T>(row, table.Columns)的内部实现
1 public static T ConvertRowToModel<T>(DataRow row, DataColumnCollection columns) 2 { 3 Type modeType = typeof(T); 4 Object model = Activator.CreateInstance(modeType); 5 6 foreach (var p in model.GetType().GetProperties()) 7 { 8 var propertyName = p.Name.ToUpper(); 9 var propertyType = p.PropertyType.Name; 10 if (columns.Contains(propertyName)) 11 { 12 var value =http://www.mamicode.com/ row[propertyName]; 13 14 if (propertyType.ToUpper().Contains("STRING")) 15 { 16 17 if (Convert.IsDBNull(value)) 18 { 19 value = http://www.mamicode.com/string.Empty; 20 } 21 else 22 { 23 p.SetValue(model, value.ToString(), null); 24 } 25 } 26 else if (propertyType.ToUpper().Contains("INT")) 27 { 28 29 if (Convert.IsDBNull(value)) 30 { 31 value = http://www.mamicode.com/0; 32 } 33 34 p.SetValue(model, Int32.Parse(value.ToString()), null); 35 } 36 else if (propertyType.ToUpper().Contains("SINGLE")) 37 { 38 39 if (Convert.IsDBNull(value)) 40 { 41 value = http://www.mamicode.com/0.0f; 42 } 43 p.SetValue(model, Single.Parse(value.ToString()), null); 44 } 45 else if (propertyType.ToUpper().Contains("DATETIME")) 46 { 47 48 if (Convert.IsDBNull(value)) 49 { 50 value =http://www.mamicode.com/ DateTime.MinValue; 51 } 52 p.SetValue(model, DateTime.Parse(value.ToString()), null); 53 } 54 else if (propertyType.ToUpper().Contains("DOUBLE")) 55 { 56 57 if (Convert.IsDBNull(value)) 58 { 59 value = http://www.mamicode.com/0.0d; 60 } 61 p.SetValue(model, Double.Parse(value.ToString()), null); 62 } 63 else if (propertyType.ToUpper().Contains("BOOLEAN")) 64 { 65 66 if (Convert.IsDBNull(value)) 67 { 68 value = http://www.mamicode.com/false; 69 } 70 if (value.GetType() == typeof(Int32)) 71 { 72 p.SetValue(model, Int32.Parse(value.ToString()) == 1, null); 73 74 } 75 else if (value.GetType() == typeof(String)) 76 { 77 p.SetValue(model, Boolean.Parse(value.ToString()), null); 78 } 79 else if (value.GetType() == typeof(Boolean)) 80 { 81 p.SetValue(model, (Boolean)(value), null); 82 } 83 84 } 85 else if (p.PropertyType.IsEnum)//Enum 86 { 87 if (Convert.IsDBNull(value) || string.IsNullOrEmpty(value.ToString())) 88 { 89 value = http://www.mamicode.com/"0"; 90 } 91 92 p.SetValue(model, int.Parse(value.ToString()), null); 93 } 94 else if (propertyType.ToUpper().Contains("DECIMAL")) 95 { 96 97 if (Convert.IsDBNull(value)) 98 { 99 value = http://www.mamicode.com/0.0f; 100 } 101 p.SetValue(model, Decimal.Parse(value.ToString()), null); 102 } 103 104 } 105 } 106 return (T)model; 107 }
这个方法有点长,但实际上很好理解,并且这个方法也是一个泛型方法,其中的关键点在于他会把所有的字段类型转换成基础的元类型,转换成int,string这些最终会在返回给Get<T>()方法,这样就完成了实体的映射,那么有了上面两个方法,就可以编写简单的ORM了,如果是增删查改的话,就改变其中的逻辑,获得返回的影响行数就可以了,那么如何调用这个ORM呢,封装后最主要的是使用。可以用如下方法直接调用
1 public static Student Getmodel(long id) { 2 StringBuilder sql = new StringBuilder(); 3 List<SqlParameter> args = new List<SqlParameter>(); 4 //根据学生id获取学生信息 5 sql.Append("select * from student where id=@id"); 6 //id参数化后赋值 7 args.Add(new SqlParameter("@id", id)); 8 //调用封装ORM所在的CommonDao类 调用刚才的Get方法 9 //映射类Student,就会返回和Student类相符合的数据库字段内容 10 return CommonDao.Get<Student>(sql.ToString(), args); 11 }
是不是就简单了很多呢。当然你也可以封装的更深一些,这里还是在传递sql语句,如果你喜欢Entitie fromwork那种方式,可以把Sql语句也作为固定的写法,只传递where条件后面的参数就可以了,可以看到泛型方法很有用处
但是有的时候定义了泛型方法,却希望他只能用于某一种特定的类型,上述的例子可以用于所有泛型类型,但是如果我要封装的不是底层,只是某一个高级方法,只允许某一种类型使用这个方法,那么该如何做呢?可以使用泛型约束
泛型约束
1 public static T WhereGet<T>(string sql, IList<SqlParameter> sqlParameters = null, IDbTransaction transaction = null) 2 where T:IList<T> 3 { 4 DataTable table = SQLServerHelper.Query(sql, sqlParameters, transaction); 5 if (table != null && table.Rows != null && table.Rows.Count > 0) 6 { 7 DataRow row = table.Rows[0]; 8 return ConvertRowToModel<T>(row, table.Columns); 9 } 10 else 11 { 12 Type modeType = typeof(T); 13 return default(T); 14 } 15 16 }
CLR类型设计之泛型(二)