通过运行期类型检查实现泛型算法

简介: 通过运行期类型检查实现泛型算法

零、第一次优化

虽然我们可以通过指定不同的类型参数来实现泛型类的复用,但是在某些情况下通用就意味着我们无法利用具体类型的优势。针对这一点 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);
}

三、总结

我们在开发中不仅可以对泛型增加少量合理的限制,还可以在它所表示的类型具备很多功能时提供更好的实现方式,但是我们需要在算法的效率和泛型的复用程度之间找到平衡点。

目录
相关文章
|
4月前
|
算法 前端开发 调度
贪心算法适用于解决什么类型的问题?
贪心算法适用于解决什么类型的问题?
164 1
|
1月前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
3月前
|
算法
数据结构和算法学习记录——线性表之双向链表(上)-结点类型定义、初始化函数、创建新结点函数、尾插函数、打印函数、尾删函数
数据结构和算法学习记录——线性表之双向链表(上)-结点类型定义、初始化函数、创建新结点函数、尾插函数、打印函数、尾删函数
37 0
|
1月前
|
数据采集 前端开发 算法
基于朴素贝叶斯算法的新闻类型预测,django框架开发,前端bootstrap,有爬虫有数据库
本文介绍了一个基于Django框架和朴素贝叶斯算法开发的新闻类型预测系统,该系统具备用户登录注册、后台管理、数据展示、新闻分类分布分析、新闻数量排名和新闻标题预测等功能,旨在提高新闻处理效率和个性化推荐服务。
|
3月前
|
分布式计算 算法 Java
阿里云ODPS PySpark任务使用mmlspark/synapseml运行LightGBM进行Boosting算法的高效训练与推理
阿里云ODPS PySpark任务使用mmlspark/synapseml运行LightGBM进行Boosting算法的高效训练与推理
|
3月前
|
存储 机器学习/深度学习 算法
使用决策树算法预测隐形眼镜类型
使用决策树算法预测隐形眼镜类型
31 2
|
3月前
|
机器学习/深度学习 分布式计算 算法
在机器学习项目中,选择算法涉及问题类型识别(如回归、分类、聚类、强化学习)
【6月更文挑战第28天】在机器学习项目中,选择算法涉及问题类型识别(如回归、分类、聚类、强化学习)、数据规模与特性(大数据可能适合分布式算法或深度学习)、性能需求(准确性、速度、可解释性)、资源限制(计算与内存)、领域知识应用以及实验验证(交叉验证、模型比较)。迭代过程包括数据探索、模型构建、评估和优化,结合业务需求进行决策。
46 0
|
3月前
|
存储 算法
数据结构和算法学习记录——特殊线性表之队列-队列的概念、队列结构体类型定义 、基本接口函数、初始化函数、销毁队列函数、入队列函数、判断队列是否为空、出队列函数、读取队头队尾的数据 、计算队列数据个数
数据结构和算法学习记录——特殊线性表之队列-队列的概念、队列结构体类型定义 、基本接口函数、初始化函数、销毁队列函数、入队列函数、判断队列是否为空、出队列函数、读取队头队尾的数据 、计算队列数据个数
28 0
|
4月前
|
算法 调度
基于多目标粒子群算法冷热电联供综合能源系统运行优化(matlab代码)
基于多目标粒子群算法冷热电联供综合能源系统运行优化(matlab代码)
|
4月前
|
算法
【免费】基于ADMM算法的多微网电能交互分布式运行策略(matlab代码)
【免费】基于ADMM算法的多微网电能交互分布式运行策略(matlab代码)