.net core精彩实例分享 -- 泛型和集合
文章目录
- 介绍
- 具体案例
- 限制泛型参数只能使用值类型
- 泛型参数的输入和输出
- 将抽象类作为类型约束
- 使用Span提升处理字符串的性能
- 多个Task同时操作ConcurrenBag集合
- 跨线程访问BlockingCollection集合
- 总结
介绍
随着.net core
越来越流行,对.net core
基础知识的了解,实际应用等相关的知识也应该有所了解。所以就有了这篇文章,案例都是来自阅读的书籍,或者实际工作中感觉比较有用的应用。分享亦总结。
本文主要介绍 .net core
相关的泛型和集合案例。
具体案例
限制泛型参数只能使用值类型
【导语】
因为泛型参数可以是任意类型,所以如果编译器仅仅将参数假定位 Object
类型(即按照 Object
类的成员进行访问),那么代码在访问某些特定类型时就会引发编译器错误。
为了避免引发编译错误,有时候需要对泛型参数进行限制,也就是泛型约束。常用的泛型约束如下。
(1)class:限制泛型参数的类型必须是类(引用类型)。
(2)struct:限制泛型参数的类型必须是结构(值类型)。
(3)new():要求类型必须包含公共的、无参数的构造函数。当使用多个约束时,new() 必须放到最后。
(4)unmanaged:要求类型是非托管类型。此于数不常用。
(5)接口或者基类:要求类型必须实现指定接口,或者必须从指定类型派生。
泛型约束可以组合使用,例如下列泛型类要求类型参数是引用类型(class
),而且要求实现 IService
接口。
public class `Main` Item<U> where U:calss,IService
{...
}
泛型约束的使用方法是在声明类型或类型成员之后应用 where
关键字,然后才是类型参数的约束条件。如果由多个泛型参数,也可以用多个 where
关键字,格式如下。
public class SomeOne<T,S>where T: class, new()where S: ITask
{}
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:声明一个类,命名位 Test
,带由一个泛型参数 T。
class Test<T>
{...
}
步骤3:对泛型参数 T 进行约束,限制只能是值类型,即类型为结构。
class Test<T> where T : struct
{...
}
步骤4:在类中声明一个 Start
方法,并接受一个 T 类型参数。
public void Start(T x)
{string CheckType(Type t) => t.IsValueType ? "是" : "不是";Type type = x.GetType();Console.WriteLine($"{type.Name} {CheckType(type)}值类型。");
}
CheckType
是一个内联方法,它的格式与方法类似,但是不需要指定方法修饰符,一般用于实现简单的逻辑处理。如本例中的 CheckType
,只是用于判断类型 T 是否为值类型,如果是返回字符串“是”,否则返回字符串“不是”。
步骤5:回到 Main
方法,用定义好的泛型类声明一个变量,然后创建一个新实例。随后调用 Start
方法,此时类型参数 T 为 int
类型。
Test<int> tv = new Test<int>();
tv.Start(100);
步骤6:类似的,在声明一个 Test
类的变量实例化,然后调用 Start
方法,此时类型参数 T 为 byte
类型。
Test<byte> tq = new Test<byte>();
tq.Start(152);
步骤7:运行应用程序项目,结果如下。
泛型参数的输入和输出
【导语】
如果泛型参数不带任何修饰符,那么在分配对象实例时,类型参数只能是固定的类型。例如以下形式。
Class<A> x = new Class<A>();
假设 B 类从 A 类派生,则分配以下对象实例时会报错。
Class<A> x = new Class<B>();
因为泛型参数为使用任何修饰符,使用参数类型是固定的。声明变量时使用的是 A 类型,而分配对象实例时使用的是 B 类型,前后不一致,会出现编译错误。
要使泛型中的类型参数成为变体,一般可以使用两个修饰符————in
和 out
。带 in
修饰符的是“输入类型”,此类型参数一般用于委托或方法的输入参数,属于逆变。带 out
修饰符的是“输出参数”,一般用于委托或方法的返回值,属于协变。
泛型的输入/输出类型参数只能用于委托和接口两种数据类型,不能用于类和结构,而且作为类型参数的类型不能是值类型。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:声明两个类,稍后用于测试
public class Ball { }
public class FootBall : Ball { }
FootBall
类从 Ball
类派生。按照隐式转换的要求,FootBall
类的实例可以赋值给使用 Ball
类型声明的变量。
步骤3:声明两个带可变类型参数的泛型接口。
public interface ITest1<in T> { }
public interface ITest2<out T> { }
在 ITest1
接口中,T 类型参数使用了 in
修饰符,表示它是一个输入类型,即逆变。在 ITest2
接口中,T 类型参数使用 out
修饰符,表示该类型将作为输出擦拭你和(返回值),即协变。
步骤4:声明两个类,分别实现 ITest1
和 ITest2
接口。
public class Test1<T> : ITest1<T> { }
public class Test2<T> : ITest2<T> { }
步骤5:在 Main
方法中用 ITest1
接口声明变量,类型参数为 FootBall
类,然后用 Test1
类的新实例进行赋值,类型参数为 Ball
类。
ITest1<FootBall> t1 = new Test1<Ball>();
上述情况属于逆变。t1 变量的泛型参数只能接受 FootBall
类型,而它所引用的实例的泛型参数则可以接受 Ball
和 FootBall
两个类型,可以看到,赋值之后实例能分配的兼容性变小了,当通过 t1 变量调用相关成员时,只能使用 FootBall
类。
步骤6:用 ITest2
接口声明变量,并指定泛型参数为 Ball
类,使用 Test2
实例赋值的泛型参数为 FootBall
类。
ITest2<Ball> t2 = new Test2<FootBall>();
变量 t2 能接收 Ball
和 FootBall
两个类型的返回值,而它所引用的实例只能返回 FootBall
类型的对象。当使用 t2 变量调用相关成员时,由于ITest2<Ball>
的分配兼容性变大,能够顺利引用Test2<FootBall>
实例所返回的对象,此情况属于协变。
将抽象类作为类型约束
【导语】
将泛型参数约束为抽象类或接口,可以有效规范对类型实例的访问,中在实际开发中比较实用。如果不给类型参数添加约束,那么在默认情况下编译器就会以 Object
类的成员进行规范,这个会带来诸多不便。
然而如果是应用抽象类或者接口来约束类型参数,那么在访问参数实例时就很方便了。例如声明一个接口,名为 IService
,接口中声明两个 方法————Open
和 Close
。在泛型参数中添加约束,要求类型必须实现 IService
接口。在这种情况下,访问泛型参数实例的代码不需要关心有多少个类实现了 IService
接口,因为不管是哪个类,只要它实现了该接口,必然会包含 Open
和 Close
这两个方法,如此一来,代码只需要带调用方法。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:为了便于稍后测试,先声明一个抽象类 Animal
,它表示所有动物,同时包含一个抽象的 CheckIn
方法。
/// <summary>
/// 公共基类
/// </summary>
public abstract class Animal
{public abstract void CheckIn();
}
所有从 Animal
类派生的类都必须实现 CheckIn
方法。
步骤3:声明 4 个新类,都实现 Animal
抽象类。
/// <summary>
/// 猫鼬
/// </summary>
public class Meerkat : Animal
{public override void CheckIn(){Console.WriteLine("这是猫鼬。\n");}
}
/// <summary>
/// 狐狸
/// </summary>
public class Fox : Animal
{public override void CheckIn(){Console.WriteLine("这是狐狸。\n");}
}
/// <summary>
/// 鸡
/// </summary>
public class Chicken : Animal
{public override void CheckIn(){Console.WriteLine("这是鸡。\n");}
}
/// <summary>
/// 鹌鹑
/// </summary>
public class Quail : Animal
{public override void CheckIn(){Console.WriteLine("这是鹌鹑。\n");}
}
步骤4:声明一个泛型接口,将类型参数标注为输入参数,这样可以在后续使用中支持传递不同派生程度的类。
public interface ITest<in T>
{void DoWork(T pr);
}
步骤5:声明泛型类,并是实现上面的泛型接口,将类型参数约束为必须是从 Animal
派生的类。
public class TestAnl<T> : ITest<T>where T : Animal
{public void DoWork(T pr){// CheckIn 方法是在抽象类中定义的// 所有实现该抽象类的类型都能访问pr.CheckIn();}
}
步骤6:使用 ITest
接口声明一个变量,然后使用 TestAnl
类的新实例对其初始化,类型参数使用 Animal
类,这样做能够增加调用 DoWork
方法的灵活性,能够向该方法传递各种 Animal
类的子类实例。
ITest<Animal> t = new TestAnl<Animal>();
步骤7:调用 DoWork
方法,依次将 Animal
类的 4 个派生类实例传递进去。
t.DoWork(new Fox());
t.DoWork(new Meerkat());
t.DoWork(new Quail());
t.DoWork(new Chicken());
步骤8:运行应用程序项目,结果如下。
使用Span提升处理字符串的性能
【导语】
.NET
中许多类型都是在托管堆中分配内存的,在对连续内存卡进行操作时,某些数据类型会比较花时间,其中最典型的是字符串。在处理字符串的过程中会不断创建新的实例,这些过程必将占用一定的时间。
.NET Core
类库提供了一种特殊的结构————Span<T>
,它可以用于操作各种连续分布的内存数据。可以通过以下来源初始化 Span<T>
。
- 常见的托管数组。
- 栈内存中的对象。
- 本地内存指针。
此外,Span<T>
支持 GC 功能,不需要显示释放内存。与 Span<T>
对应,还有一个只读版本————ReadOnlySpan<T>
。
本实例演示了用两种方法将某个字符串中的两个字符(“2”和“0”)转换为 int
数值 20,并且使用 Stopwatch
组件分布计算两种方法所消耗的时间。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:声明并初始化一个字符串实例,稍后测试使用。
string str = "我家里养了20只猫";
步骤3:第一种处理方法,调用 Substring
方法取出“2”和“0”两个字符串,在通过 Parse
方法产生 int
实例。
Stopwatch sw1 = Stopwatch.StartNew();
for(int x = 0; x < 10000000; x++)
{int v = int.Parse(str.Substring(5, 2));
}
sw1.Stop();
Console.WriteLine($"常规方法:耗时 {sw1.ElapsedMilliseconds} ms");
步骤4:第二种方法,使用 Span<T>
,T 为 char
类型。调用 Slice
方法取出“2”和“0”两个字符串,在通过自定义代码将其转换为 int
值。
Stopwatch sw2 = Stopwatch.StartNew();
ReadOnlySpan<char> span = str.ToCharArray();
for (int x = 0; x < 10000000; x++)
{int v = 0;var subspan = span.Slice(5, 2);for(int i = 0; i < subspan.Length; i++){char ch = subspan[i];v = (ch - '0') + v * 10;}
}
sw2.Stop();
Console.WriteLine($"使用 Span:耗时 {sw2.ElapsedMilliseconds} ms");
由于 Span
实例中取出来的元素是 char
类型,为了能得到与字符串相对应的整数值,应该将其减去字符“0”的ASCII码。字符“0”的 ASCII
码是 48,以此类推,字符“1”的 ASCII
码 49,字符“2”的 ASCII
码 50,如果要使字符“2”与整数 2 对应,就需要 50 减去 48。
步骤5:运行应用程序项目,结果如下。
很明显采用Span<T>
的效率比较高。
注意:为了能让对比的效果更直观,本实例在要给
for
循环中将每种给处理方法的代码重复执行了10000000
次。 在计时的时候,Stopwatch
组件自身也会消耗一定的CPU
资源,因此代码执行所耗费的准确时间与Stopwatch
所计算结果会有误差,仅用于参考。
多个Task同时操作ConcurrenBag集合
【导语】
ConcurrentBag<T>
是一个泛型集合,它比较明显的优点是可以在多个线程上同时访问。并且该集合是无序的,即从集合中取出元素的顺序可能与放入的顺序不一样。
要向集合中添加元素可以调用 Add
方法。而取出元素则有两个方法可用:TryTake
方法取出元素然后把元素从集中删除,TryPeek
方法取出元素但是不会删除元素。
要判断 ConcurrentBag<T>
集合中是否存在元素,一种方法是访问 Count
属性,它表示集合中包含元素的个数;另一种方法是访问 IsEmpty
属性,如果集合为空则返回 true
。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:实例化 ConcurrentBag<T>
集合,参数 T 为 int
类型。
ConcurrentBag<int> bag = new ConcurrentBag<int>();
步骤3:在项目生成的 Program.cs
文件头部的 using
指令块中,添加对以下命名空间的引用。
using System.Collections.Concurrent;
using System.Threading.Tasks;
步骤4:启动第一个 Task
,向集合中添加元素。
Task t1 = Task.Run(() =>
{for (int k = 45; k < 51; k++){Console.WriteLine("即将添加元素:{0}", k);bag.Add(k);}
});
步骤5:启动第二个 Task
,从集合中取出元素,此 Task
在第一个 Task
执行之后执行。
Task t2 = t1.ContinueWith((task) =>
{while (!bag.IsEmpty){if(bag.TryTake(out int item)){Console.WriteLine("已取出元素:{0}", item);}}
});
ContinueWith
方法指定在某个 Task
之后延续新的 Task
。如果两个 Task
同时执行,由于集合的初始化状态是空的,会导致第二个 Task
中无法获取到元素。所以要让第一个 Task
先执行,这样在执行第二个 Task
时,就能保证集合中是有元素的。
步骤6:调用 WaitAll
方法,让当前线程暂定执行,等待上面两个 Task
任务执行完成再继续。
Task.WaitAll(t1, t2);
步骤7:运行应用程序项目,结果如下。
跨线程访问BlockingCollection集合
【导语】
BlockingCollection<T>
集合允许多线程访问(添加或移除元素)。当集合中的元素数量达到容量上限时,添加操作会被阻止,直到集合中有元素被移除(重新获取到可用容量);同样的,当从集合中移除元素时,如果集合中无可用元素,那么移除操作会别阻止,直到集合中有新的元素加入。
多个线程可用同时调用 Add
或 TryAdd
方法向集合中添加元素,也可以同时调用 Take
或 TryTake
方法移除元素。当调用 CompleteAdding
方法后,就不能在向集合中添加元素了,否则会引发异常。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:创建 BlockingCollection<T>
实例,T 为 int
类型。
using (BlockingCollection<int> bc = new BlockingCollection<int>())
{...
}
BlockingCollection<T>
类实现了 IDisposable
接口,若不再使用要将其释放。将初始化代码放在 using
语句块中,当代码离 using
块时会自动调用 Dispose
方法。
步骤3:在 using
代码块内部,创建第一个 Task
,用于向集合中添加元素。
Task t1 = Task.Run(async () =>
{for (int k = 0; k < 5; k++){int item = k + 1;Console.WriteLine("即将添加元素:{0}", item);bc.Add(item);await Task.Delay(650);}// 标记添加操作已完成bc.CompleteAdding();
});
在调用 CompleteAdding
方法后,集合被标志位已完成添加操作,此时所有线程均无法在向集合中添加元素。
步骤4:创建第二个 Task
,用于从集合中移除元素。
Task t2 = Task.Run(() =>
{while (true){if (bc.TryTake(out int item)){Console.WriteLine("取出元素:{0}", item);}// 是否退出循环if (bc.IsCompleted) break;}
});
当集合中的所有元素都被移除了(空集合),IsCompleted
属性会变为 true
,此时就可以退出 while
循环了。
步骤5:调用 WaitAll
方法等待所有 Task
执行完成,然后释放上述两个 Task
。
Task.WaitAll(t1, t2);
t1.Dispose();
t2.Dispose();
步骤6:运行应用程序项目,结果如下。
总结
本文到这里就结束了,下一篇将介绍LINQ 的知识案例。
.net core精彩实例分享 -- 泛型和集合相关推荐
- .net core精彩实例分享 -- 字符串处理
文章目录 介绍 具体案例 处理超大整数 获取指定日期的农历日期 输出百分比 输出多个币种格式 数字的两种常用格式 自定义小数位数 总结 介绍 随着.net core越来越流行,对.net core 基 ...
- .net core精彩实例分享 -- 应用启动
文章目录 介绍 具体案例 配置Web服务器的URL 配置Web项目的调试方案 基于方法约定的Startup类 使用非预定义环境 总结 介绍 随着.net core越来越流行,对.net core 基础 ...
- .net core精彩实例分享 -- 反射与Composition
文章目录 介绍 具体案例 用Activator类创建类型实例 检查类型上所应用的自定义Attribute 通过协定来约束导出类型 导入多个类型 封装元数据 总结 介绍 随着.net core越来越流行 ...
- .net core精彩实例分享 -- LINQ
文章目录 介绍 具体案例 将对象转为字典集合 将原始序列进行分组 按员工所属部门 DefaultIfEmpty方法的作用 将分组后的序列重新排序 使用并行LINQ 总结 介绍 随着.net core越 ...
- .net core精彩实例分享 -- 应用配置和数据库访问
文章目录 介绍 具体案例 自定义环境变量的命名前缀 自定义命令行参数映射 使用JSON文件来配置选项类 在应用程序运行期间创建SQLite数据库 总结 介绍 随着.net core越来越流行,对.ne ...
- .net core精彩实例分享 -- 依赖注入和中间件
文章目录 介绍 具体案例 临时访问服务 以委托形式定义中间件 带参数中间件 IMiddleware中间件的用途 让 HTTP 管道"短路" 中间件的分支映射 文件服务 总结 介绍 ...
- .net core精彩实例分享 -- 网络编程
文章目录 介绍 具体案例 从Web服务器上下载图片 使用HttpClient类向Web服务器提交数据 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关 ...
- .net core精彩实例分享 -- 异步和并行
文章目录 介绍 具体案例 等待线程信号--ManualResetEvent 等待线程信号--AutoResetEvent 多个线程同时写一个文件 串联并行任务 使用Parallel类执行并行操作 为每 ...
- .net core精彩实例分享 -- 序列化
文章目录 介绍 具体案例 将类型实例序列号危机JSON格式 将数据协定序列化为JSON格式 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关的知识也应 ...
最新文章
- 【Android OpenGL ES】阅读hello-gl2代码(二)Java代码
- R语言使用ggplot2包使用geom_boxplot函数绘制基础分组箱图(设置异常值的形状、颜色)实战
- 打通docker api
- WebAPi接口安全之公钥私钥加密
- k8s:koolshare软路由安装及k8s基本环境配置
- 搭建一个VUE+Express前后端分离的开发环境
- 论文浅尝 | Data Intelligence 已出版的知识图谱主题论文
- 为view添加约束constraints
- C语言之程序中内存的来源:栈 堆 数据段
- (92)低速接口UART、IIC、SPI介绍,面试必问(十六)(第19天)
- java以32位运行,强制java applet以32位而不是64位JRE运行
- redis 缓存 淘汰
- python编程(深拷贝和浅拷贝)
- 想学IT的必看!深度解析跳槽从开始到结束完整流程万字长文!
- 2023南京工业大学计算机考研信息汇总
- 使用Python+百度AI把文字转成语音
- 好好说说互联网IT行业加班那点儿事
- 失恋后同学对我的劝告。。。。表谢意
- Android使用VideoView播放视频
- linux上远程文件传输工具 scp sz rz
热门文章
- ios 数字键盘左下角添加按钮_IOS数字键盘左下角添加完成按钮的实现方法
- bat产品经理能力模型_浅析产品经理能力模型
- mysql 日志文件 自动_教你自动恢复MySQL数据库的日志文件
- android+ndk+r9+x64下载,Win7 64位中文旗舰版上Cocos2d-x 3.0的Android开发调试环境架设
- oracle group by 取最新的一条_大国智能制造全文免费阅读_大国智能制造最新章节_乌溪小道的小说...
- 在python中获取当前工作目录可以通过_python-获取当前工作路径
- rstudio安装后打不开_R与RStudio最简单安装指南
- java se development kit可以卸载吗_首款纯电版MINI COOPER详细评测,或将国产,值得等吗?...
- UI线框图模板素材实际应用好帮手
- php使用常量cont,php常量介绍