通过运行期类型检查实现泛型算法
Tip:本文首发于喵叔的 CSDN 博客,转载于喵叔的 InfoQ 博客,本人未授权任何网站、公众号以及其他任何形式的转载。发布不等于免费、开源不等于无所顾忌,请遵守职业道德。
零、第一次优化
虽然我们可以通过指定不同的类型参数来实现泛型类的复用,但是在某些情况下通用就意味着我们无法利用具体类型的优势。针对这一点 C# 允许在发现类型参数所表示的对象具有更多的功能时编写更具体的代码。这一点是利用了泛型依据对象的编译器类型来进行实例化的这一特点,如果我们在开发时没有想到这一点就有很大的可能降低程序的性能。为了能讲清楚这一点,我们先来看一段代码,这段代码要做的是倒序输出序列中的内容。
public sealed class DemoEnumerable<T> : IEnumerable<T>
{
private class DemoEnumerator:IEnumerator<T>
{
int index;
IList<T> collection;
public DemoEnumerator(IList<T> source)
{
collection = source;
index=collection.Count;
}
public T Current=>collection[index];
public void Dispose()
{
// more code
}
object System.Collections.IEnumerator.Current=>this.Current;
public bool MoveNext() =>--index>=0;
public void Reset()=>index=collection.Count;
}
Ienumerable<T> srcSequence;
IList<T> orgSequence;
public DemoEnumerable(IEnumerable<T> sequence)
{
sreSequence = sequence;
}
public IEnumerator<T> GetEnumerator()
{
if(orgSequence == null)
{
orgSequence = new List<T>();
foreach (T item in sreSequence)
{
orgSequence.Add(item);
}
}
return new DemoEnumerator(orgSequence);
}
}
上述代码中只针对 DemoEnumerable 构造函数做了限制,要求它的参数必须支持 IEnumerable ,因此我们要实现序列中元素的倒叙访问就必须采用 GetEnumerator 种的方式。首次调用这个方法时会把输入的序列访问一遍,然后让嵌套类可以在这个列表上反向访问元素。但是这里存在一个问题,大部分序列都支持随机访问,那么如果输入的序列支持 IList 这种写法就是多此一举,因为这种写法会创建出一份和源序列一摸一样的序列。要解决这个问题我们只需要修改一下 DemoEnumerable 的构造函数然后增加一个参数为 IList 类型的构造函数即可:
public DemoEnumerable(IEnumerable<T> sequence)
{
sreSequence = sequence;
orgSequence = sequence as IList<T>;
}
public DemoEnumerable(IList<T> sequence)
{
sreSequence = sequence;
orgSequence = sequence;
}
Tip:这里之所以要修改源构造函数并增加一个参数类型为 IList 的构造函数,是因为只有参数的编译器类型是 IList 的时候新的构造函数才会生效。有时尽管参数实现了 IList 但是它的编译期类型仍然是 IEnumerable,因此我们必须提供新的构造函数的同时修改旧的构造函数。
一、第二次优化
上述代码基本上囊括了大部分情况,但有时我们还会遇到一些集合只实现了 ICollection 而没有实现 IList 的情况,这种情况下我们代码中的 GetEnumerator 方法性能就不是很高了,因为它可以利用 Count 属性将 IList 的大小确定下来。因此我们需要修改一下代码。
public IEnumerator<T> GetEnumerator()
{
if(orgSequence == null)
{
if(orgSequence is ICollection<T>)
{
ICollection<T> src = orgSequence as ICollection<T>;
orgSequence = new List<T>(src.Count);
}
else
{
orgSequence = new List<T>();
}
foreach (T item in sreSequence)
{
orgSequence.Add(item);
}
}
return new DemoEnumerator(orgSequence);
}
二、终极优化
到这里我们现在基本上覆盖了大部分的情况,但是我们还需要注意的是前面代码中 DemoEnumerable 都是执行的运行期测试,测试的是参数在运行期的状态,因此为了确定参数所表示的对象是否具有一些功能,我们的程序必须消耗一定的时间去判断,在绝大多数情况下这种做法消耗的性能不是很多。但是当 T 是 string 时性能就会大打折扣,因为我们的代码本身并没有实现 IList ,因此我们需要在泛型类中编写更具体的代码才能解决这个问题,我们需要在 DemoEnumerable 类中加入如下的嵌套类。
private class DemoStringEnumerator:IEnumerator<char>
{
int index;
string collection;
public DemoEnumerator(string source)
{
collection = source;
index=source.Length;
}
public char Current=>collection[index];
public void Dispose()
{
// more code
}
object System.Collections.IEnumerator.Current=>this.Current;
public bool MoveNext() =>--index>=0;
public void Reset()=>index=collection.Length;
}
下面我们还要修改 GetEnumerator 的代码,这样 DemoEnumerable 就可以正常使用我们定义的 DemoStringEnumerator 了。
public IEnumerator<T> GetEnumerator()
{
if(sreSequence is string)
{
return new DemoStringEnumerator(sreSequence as string);
}
if(orgSequence == null)
{
if(orgSequence is ICollection<T>)
{
ICollection<T> src = orgSequence as ICollection<T>;
orgSequence = new List<T>(src.Count);
}
else
{
orgSequence = new List<T>();
}
foreach (T item in sreSequence)
{
orgSequence.Add(item);
}
}
return new DemoEnumerator(orgSequence);
}
三、总结
我们在开发中不仅可以对泛型增加少量合理的限制,还可以在它所表示的类型具备很多功能时提供更好的实现方式,但是我们需要在算法的效率和泛型的复用程度之间找到平衡点。
- Fiddler中显示IP方法
- readlink: command not found 解决方案
- Java 并发编程系列: CountDownLatch (上厕所的案例)
- 详解ANGULAR2组件中的变化检测机制(对比ANGULAR1的脏检测)
- 如何快速查看github代码库中第一次commit的记录
- 更换包管理工具npm为yarn
- 【精选】使用Cryptory分析影响加密货币价格的因素(区块链系列3)
- React -- 组件间通信
- 图片和视频防盗链简单介绍
- 对比cp和scp命令 将数据从一台linux服务器复制到另一台linux服务器
- laravel—用Migration的操作数据库
- 有货移动Web端性能优化探索实践
- webpack打包速度和性能再次优化
- MySQL 清除表空间碎片
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 一文详解 Ansible 自动化运维!
- 手把手教你搭建织女星开发板RISC-V开发环境
- R语言多臂试验 - 我们应该考虑多重性吗?
- Stata估算观测数据的风险比
- R语言利用基线协变量提高随机对照试验的效率
- R语言使用倾向评分提高RCT(随机对照试验)的效率
- R语言调整随机对照试验中的基线协变量
- R语言用多重插补法估算相对风险
- R语言分析协变量之间的非线性关系
- stata如何处理结构方程模型(SEM)中具有缺失值的协变量
- stata对包含协变量的模型进行缺失值多重插补分析
- 互联网直播点播平台go语言搭建重定向和反向代理的区别及使用
- UNIX时间戳和北京时间的相互转换
- R语言对苏格兰独立民意调查的Meta分析
- 案例:归档自动清理脚本失效及连带影响