在上一篇文章中,介绍了什么是泛型,以及泛型和非泛型的区别,这篇文章主要讲一些泛型的高级用法,泛型方法,泛型接口和泛型委托,协变和逆变泛型类型参数和约束性,泛型的高级用法在平时的业务中用的不多,多用于封装高级方法和一些底层封装,前几天读了一篇文章,如何选择网络上的技术文章,因为现在关于技术的文章可以说非常多,但是时间是有限的,如果花很多时间阅读了一篇文章却没有什么用,岂不是很浪费时间,所以第一步选择自己感兴趣的文章阅读,第二要把阅读过的文章尽可能实现一次,读书万遍不如走上一遍,第三尽量不读翻译性的文章,这里其实我觉得不是所有人都能很轻松的看懂官方文档,所以这点还是仁者见仁,智者见智。为了让文章尽可能的有深度,所以我觉得以后的博文中应该尽可能的贴出的知识模块都有所解释,有所理解。不是在网上复制粘贴以后在给别人看,博文不是笔记,所以要说出自己的见解

说了这么多,那么就开始务实的甩开膀子做吧!

泛型方法

既然讲到了泛型是为了高级封装,那么我们就来封装一个C#中的ORM吧,在封装ORM之前还要有一个SQL帮助类,这个网上有很多,感兴趣的可以直接到网上找一个,C#中封装的ORM最好的Entity FromWork,感兴趣的可以看下源码,我们先看下下面的代码,这是一个典型的泛型方法

     /// <summary>
/// 查询获得一个实体
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="sqlParameters"></param>
/// <param name="transaction"></param>
/// <returns></returns>
public static T Get<T>(string sql, IList<SqlParameter> sqlParameters = null, IDbTransaction transaction = null)
{
DataTable table = SQLServerHelper.Query(sql, sqlParameters, transaction);
if (table != null && table.Rows != null && table.Rows.Count > )
{
DataRow row = table.Rows[];
return ConvertRowToModel<T>(row, table.Columns);
}
else
{
Type modeType = typeof(T);
return default(T);
} }

注释已经表明了这是用来获取一个实体的Get<T>()方法中T定义为泛型类型,方法有一个必选参数两个默认参数,string类型的sql语句,默认IList<SqlParameter> sqlParameters继承自IList集合的SQL参数。这是参数化SQL的,每一个SQL语句都应该写成参数化的,还有一个IDbTransaction transaction = null是否开启事务,有了这样三个参数就可以定义一个查询指定SQL语句,是否有Where条件,是否开启事务的方法,方法继续执行,第是一行代码是使用帮助类获得数据,第14行和第15行是将获得数据映射成对应的结构体,我们可以看下 ConvertRowToModel<T>(row, table.Columns)的内部实现

   public static T ConvertRowToModel<T>(DataRow row, DataColumnCollection columns)
{
Type modeType = typeof(T);
Object model = Activator.CreateInstance(modeType); foreach (var p in model.GetType().GetProperties())
{
var propertyName = p.Name.ToUpper();
var propertyType = p.PropertyType.Name;
if (columns.Contains(propertyName))
{
var value = row[propertyName]; if (propertyType.ToUpper().Contains("STRING"))
{ if (Convert.IsDBNull(value))
{
value = string.Empty;
}
else
{
p.SetValue(model, value.ToString(), null);
}
}
else if (propertyType.ToUpper().Contains("INT"))
{ if (Convert.IsDBNull(value))
{
value = ;
} p.SetValue(model, Int32.Parse(value.ToString()), null);
}
else if (propertyType.ToUpper().Contains("SINGLE"))
{ if (Convert.IsDBNull(value))
{
value = 0.0f;
}
p.SetValue(model, Single.Parse(value.ToString()), null);
}
else if (propertyType.ToUpper().Contains("DATETIME"))
{ if (Convert.IsDBNull(value))
{
value = DateTime.MinValue;
}
p.SetValue(model, DateTime.Parse(value.ToString()), null);
}
else if (propertyType.ToUpper().Contains("DOUBLE"))
{ if (Convert.IsDBNull(value))
{
value = 0.0d;
}
p.SetValue(model, Double.Parse(value.ToString()), null);
}
else if (propertyType.ToUpper().Contains("BOOLEAN"))
{ if (Convert.IsDBNull(value))
{
value = false;
}
if (value.GetType() == typeof(Int32))
{
p.SetValue(model, Int32.Parse(value.ToString()) == , null); }
else if (value.GetType() == typeof(String))
{
p.SetValue(model, Boolean.Parse(value.ToString()), null);
}
else if (value.GetType() == typeof(Boolean))
{
p.SetValue(model, (Boolean)(value), null);
} }
else if (p.PropertyType.IsEnum)//Enum
{
if (Convert.IsDBNull(value) || string.IsNullOrEmpty(value.ToString()))
{
value = "";
} p.SetValue(model, int.Parse(value.ToString()), null);
}
else if (propertyType.ToUpper().Contains("DECIMAL"))
{ if (Convert.IsDBNull(value))
{
value = 0.0f;
}
p.SetValue(model, Decimal.Parse(value.ToString()), null);
} }
}
return (T)model;
}

这个方法有点长,但实际上很好理解,并且这个方法也是一个泛型方法,其中的关键点在于他会把所有的字段类型转换成基础的元类型,转换成int,string这些最终会在返回给Get<T>()方法,这样就完成了实体的映射,那么有了上面两个方法,就可以编写简单的ORM了,如果是增删查改的话,就改变其中的逻辑,获得返回的影响行数就可以了,那么如何调用这个ORM呢,封装后最主要的是使用。可以用如下方法直接调用

     public static Student Getmodel(long id) {
StringBuilder sql = new StringBuilder();
List<SqlParameter> args = new List<SqlParameter>();
//根据学生id获取学生信息
sql.Append("select * from student where id=@id");
//id参数化后赋值
args.Add(new SqlParameter("@id", id));
//调用封装ORM所在的CommonDao类 调用刚才的Get方法
//映射类Student,就会返回和Student类相符合的数据库字段内容
return CommonDao.Get<Student>(sql.ToString(), args);
}

是不是就简单了很多呢。当然你也可以封装的更深一些,这里还是在传递sql语句,如果你喜欢Entitie fromwork那种方式,可以把Sql语句也作为固定的写法,只传递where条件后面的参数就可以了,可以看到泛型方法很有用处

但是有的时候定义了泛型方法,却希望他只能用于某一种特定的类型,上述的例子可以用于所有泛型类型,但是如果我要封装的不是底层,只是某一个高级方法,只允许某一种类型使用这个方法,那么该如何做呢?可以使用泛型约束

泛型约束

  public static T WhereGet<T>(string sql, IList<SqlParameter> sqlParameters = null, IDbTransaction transaction = null)
where T:IList<T>
{
DataTable table = SQLServerHelper.Query(sql, sqlParameters, transaction);
if (table != null && table.Rows != null && table.Rows.Count > )
{
DataRow row = table.Rows[];
return ConvertRowToModel<T>(row, table.Columns);
}
else
{
Type modeType = typeof(T);
return default(T);
} }

上面的代码示例,可以看到和第一次的写法上一样的,只是行2处多加了一个where T:IList<T>,这就限制了T只能是实现了IList接口的集合访问。可以看到泛型方法很有用处,写一个方法可以适应很多类型,避免了同一套逻辑下的不同类型参数的重载。而且泛型不仅仅支持方法,还支持接口和委托

泛型接口和泛型委托

     目的是一样的,只是一种拓展,将泛型用于接口和委托上,但是如果没有泛型接口,那么每次用非泛型接口来操作值类型都会发生一次装箱操作,因此C#提供了泛型接口的支持,来看下C#中提供的IList泛型接口

    //
// 摘要:
// 表示可按照索引单独访问的一组对象。
//
// 类型参数:
// T:
// 列表中元素的类型。
[DefaultMember("Item")]
[TypeDependencyAttribute("System.SZArrayHelper")]
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
//
// 摘要:
// 获取或设置位于指定索引处的元素。
//
// 参数:
// index:
// 要获得或设置的元素从零开始的索引。
//
// 返回结果:
// 位于指定索引处的元素。
//
// 异常:
// T:System.ArgumentOutOfRangeException:
// index 不是 System.Collections.Generic.IList`1 中的有效索引。
//
// T:System.NotSupportedException:
// 设置该属性,而且 System.Collections.Generic.IList`1 为只读。
T this[int index] { get; set; } //
// 摘要:
// 确定 System.Collections.Generic.IList`1 中特定项的索引。
//
// 参数:
// item:
// 要在 System.Collections.Generic.IList`1 中定位的对象。
//
// 返回结果:
// 如果在列表中找到,则为 item 的索引;否则为 -1。
int IndexOf(T item);
//
// 摘要:
// 将一个项插入指定索引处的 System.Collections.Generic.IList`1。
//
// 参数:
// index:
// 从零开始的索引,应在该位置插入 item。
//
// item:
// 要插入到 System.Collections.Generic.IList`1 中的对象。
//
// 异常:
// T:System.ArgumentOutOfRangeException:
// index 不是 System.Collections.Generic.IList`1 中的有效索引。
//
// T:System.NotSupportedException:
// System.Collections.Generic.IList`1 为只读。
void Insert(int index, T item);
//
// 摘要:
// 移除指定索引处的 System.Collections.Generic.IList`1 项。
//
// 参数:
// index:
// 要移除的项的从零开始的索引。
//
// 异常:
// T:System.ArgumentOutOfRangeException:
// index 不是 System.Collections.Generic.IList`1 中的有效索引。
//
// T:System.NotSupportedException:
// System.Collections.Generic.IList`1 为只读。
void RemoveAt(int index);
}

我们平时所写的List集合,其实都默认实现了IList接口的行为,在下一篇文章接口中,我们就重点讨论接口的行为,这里先不研究这个问题,先只看IList为什么是泛型接口,它实现了什么?行10中 :public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable这句话表明了IList是一个接口,可访问性公开继承了ICollection,IEnumerable,IEnumerable的接口行为和方法,里面的代码是其中的具体实现,比如行29:T this[int index] { get; set; },这个是被封装成了属性的方法,从返回结果中可以看到这个属性是为了实现获取位于指定索引处的元素。用法其实就是List.Index('要获得或设置的元素'),这就是C#中实现的一个泛型接口,那么泛型委托又是什么呢?

CLR支持泛型委托,目的是保证任何类型对象都能以类型安全的方式传给回掉方法,此外泛型委托允许值类型实例在传给回掉方法时不进行任何装箱,在C#中委托实际只提供了四个方法和一个类定义,4个方法包括一个构造器,一个Invoke方法,一个BeginInvoke方法和EndInvoke方法,如果定义的委托类型指定了类型参数,编译器会定义委托类的方法,用指定的参数类型替换方法的参数类型和返回值类型。

委托最大的作用就是为类的时间绑定事件处理程序,但这和泛型委托没关系,以后的文章中会有关于委托的详细介绍,示例

     //
// 摘要:
// 基于谓词筛选值序列。
//
// 参数:
// source:
// 要筛选的 System.Collections.Generic.IEnumerable`1。
//
// predicate:
// 用于测试每个元素是否满足条件的函数。
//
// 类型参数:
// TSource:
// source 中的元素的类型。
//
// 返回结果:
// 一个 System.Collections.Generic.IEnumerable`1,包含输入序列中满足条件的元素。
//
// 异常:
// T:System.ArgumentNullException:
// source 或 predicate 为 null。
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

上述代码看起来有点眼熟?没错这个就是Linq中得where。实际上Linq中得Where就是使用委托实现Func关键字就代表了委托

委托和接口的逆变和协变泛型类型实参

委托的每个泛型类型参数都可标记为协变量或逆变量,利用这个功能可将泛型委托类型的变量转换为相同的委托类型,可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的。我们举一个例子,

List<汽车> 一群汽车 = new List<汽车>();

       List<车子> 一群车子 = 一群汽车;

      显然,上面那段代码是会报错的, 虽然汽车继承于车子,可以隐士转换为车子,但是List<汽车>并不继承于List<车子>,所以上面的转换,是行不通的。

IEnumerable<汽车> 一群汽车 = new List<汽车>();

          IEnumerable<车子> 一群车子 = 一群汽车;

          然而这样却是可以的。那么IEnumerable接口有什么不同呢,更高级?但是IList继承自IEnumerable,IEnumerable有的IList应该都有,但是IList有的IEnumerable不一定有,那么为啥IEnumerable能实现这种转化?跟踪到 IEnumerable接口发现其定义是这样的  public interface IEnumerable<out T>,T前面多了一个out,实际上是这个参数起了作用,在T前面声明out代表这是一个协变变量,声明in代表这是一个逆变变量,使用“out”,和“in”两个关键字。但是只能用在接口和委托上面,对泛型的类型进行声明,当声明为“out”时,代表它是用来返回的,只能作为结果返回,中途不能更改。当声明为"in"时,代表它是用来输入的,只能作为参数输入,不能被返回。
           “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。

            “逆变”则是指能够使用派生程度更小的类型。逆变,逆于常规的变。
           回到上面的例子,正因为“IEnumerable”接口声明了out,所以,代表参数T只能被返回,中途不会被修改,所以,IEnumerable<车子> 一群车子 = 一群汽车;  这样的强制转换

 

是合法的,IL中实际上是作了强制转换的。

总结:

泛型可以说是一个非常重要的概念和点,在这里有很多知识可以学习,但学习和实际练习还是有一定区别的,所以还是要多写一些代码辅助理解

CLR类型设计之泛型(二)的更多相关文章

  1. CLR类型设计之泛型(一)

    在讨论泛型之前,我们先讨论一下在没有泛型的世界里,如果我们想要创建一个独立于被包含类型的类和方法,我们需要定义objece类型,但是使用object就要面对装箱和拆箱的操作,装箱和拆箱会很损耗性能,我 ...

  2. CLR类型设计之参数传递

    写到这篇文章的时候,笔者回忆起来以前的开发过程中,并没有注意参数的传递是以值传递还是引用传递的,也是第一次了解到可变参数params,常用的不一定就代表理解,可能只是会用.接下来我们就一起回忆一下关于 ...

  3. CLR类型设计之方法与构造器

    无论学习那门语言都要学习函数体,C#,JAVA,PHP,都会涉及到函数体,而C#的函数体成员并不少,方法和构造器就是函数体成员之一,函数体成员还包括但不限于:方法,属性,构造器,终结器,运算符及索引器 ...

  4. CLR类型设计之类型之常量和字段

             前言 孔子说:温故而知新,可以为师矣.所以对于学习过的知识要多复习,并且每一次复习都要尽可能的去扩展,而不是书本上的几句理论知识.很多人都喜欢分享自己的学习内容,记录下生活的点点滴滴 ...

  5. CLR类型设计之属性

    在之前的随笔中,我们探讨了参数,字段,方法,我们在开始属性之前回顾一下,之前的探讨实际上串联起来就是OOP编程的思想,在接下来的文章中,我们还会讨论接口(就是行为),举个例子:我们如果要做一个学生档案 ...

  6. 【译】CLR类型加载器设计

    前言 本文翻译自BotR中的一篇,原文链接 Type Loader Design ,可以帮助我们了解CLR的类型加载机制(注意是Type类型,而不是Class类),文中涉及到术语或者容易混淆的地方,我 ...

  7. C#高级编程之泛型二(泛型类型约束、类型、反射与泛型)

    泛型类型约束 简言之:对泛型类型进行约束,细化,限定. MSDN的定义:泛型定义中的 where 子句指定对用作泛型类型.方法.委托或本地函数中类型参数的参数类型的约束,意思就是可以有泛型类.泛型方法 ...

  8. CLR via C#(16)--泛型

    泛型就像是一个模板,常常定义一些通用的算法,具体调用时再替换成实际的数据类型,提高了代码的可重用性. 一.初识泛型 1. 简单实例 以最常用的FCL中的泛型List<T >为例: stat ...

  9. [CLR via C#]12. 泛型

    泛型(generic)是CLR和编程语言提供一种特殊机制,它支持另一种形式的代码重用,即"算法重用". 简单地说,开发人员先定义好一个算法,比如排序.搜索.交换等.但是定义算法的开 ...

随机推荐

  1. java泛型使用总结

    1. 泛型方法: 2. 泛型类: 3. 通配符. 1.泛型方法 泛型方法在调用时可以接收不同类型的参数.根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用. 下面是定义泛型方法的规则: 所有 ...

  2. hadoop streaming编程小demo(python版)

    大数据团队搞数据质量评测.自动化质检和监控平台是用django,MR也是通过python实现的.(后来发现有orc压缩问题,python不知道怎么解决,正在改成java版本) 这里展示一个python ...

  3. 线性布局(LinearLayout)

    线性布局(LinearLayout) 备注 match_parent填充布局单元内尽可能多的空间 wrap_content完整显示控件内容 orientation有两个值,horizontal水平显示 ...

  4. 四种Sandcastle方法生成c#.net帮助类帮助文档

    方法一 前端时间在网上收集和自己平时工作总结整理了<干货,比较全面的c#.net公共帮助类>,整理完成上传github之后我又想,既然是帮助类,总得有个帮助文档于是乎想到了Sandcast ...

  5. Appium python自动化测试系列之appium环境搭建(二)

    ​2.1 基础环境搭建 当我们学习新的一项技术开始基本都是从环境搭建开始,本书除了第一章节也是的,如果你连最基础的环境都没有那么我们也没必要去说太多,大概介绍一下: 1.因为appium是支持andr ...

  6. Java常见异常处理

    Exception类: 在java中用类的形式对不正常情况进行了描述和封装对象,异常就是java通过面向对象的思想将问题封装成了对象. 异常发生的原因有很多,通常包含以下几大类: 用户输入了非法数据. ...

  7. ios 指定页面禁用第三方键盘,使用系统的键盘

    因为项目需要,需要在添加银行卡和提现页面使用数字键盘, 如果用户没有安装第三方键盘是没啥大问题的,但是如果用户手机安装了第三方的键盘的话,有时候会无法调用起第三方的数字键盘,或者第三方键盘样式不符合, ...

  8. 从零开始教你封装自己的vue组件

    组件(component)是vue.js最强大的功能之一,它可以实现功能的复用,以及对其他逻辑的解耦.但经过一段时间的使用,我发现自己并没有在业务中发挥出组件的最大价值.相信很多刚开始使用vue的朋友 ...

  9. Windows系统下Nginx的安装与配置

    Nginx是lgor Sysoev在2004年的时候为俄罗斯访问量第二大的rambler.ru站点设计开发的,发布至今,凭借开源的力量,已经接近成熟与完善.其功能丰富,可作为HTTP服务器,也可作为反 ...

  10. springCloud四:熔断器ribbon--Hystrix

    注:前文概念部分摘抄自博客园  纯洁的微笑 熔断器 雪崩效应 在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应.服务雪崩 ...