文章目录

  • 介绍
  • 具体案例
    • 限制泛型参数只能使用值类型
    • 泛型参数的输入和输出
    • 将抽象类作为类型约束
    • 使用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 方法,此时类型参数 Tint 类型。

Test<int> tv = new Test<int>();
tv.Start(100);

步骤6:类似的,在声明一个 Test 类的变量实例化,然后调用 Start 方法,此时类型参数 Tbyte 类型。

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 类型,前后不一致,会出现编译错误。

要使泛型中的类型参数成为变体,一般可以使用两个修饰符————inout。带 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:声明两个类,分别实现 ITest1ITest2 接口。

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 类型,而它所引用的实例的泛型参数则可以接受 BallFootBall 两个类型,可以看到,赋值之后实例能分配的兼容性变小了,当通过 t1 变量调用相关成员时,只能使用 FootBall 类。

步骤6:用 ITest2 接口声明变量,并指定泛型参数为 Ball 类,使用 Test2 实例赋值的泛型参数为 FootBall 类。

ITest2<Ball> t2 = new Test2<FootBall>();

变量 t2 能接收 BallFootBall 两个类型的返回值,而它所引用的实例只能返回 FootBall 类型的对象。当使用 t2 变量调用相关成员时,由于ITest2<Ball>的分配兼容性变大,能够顺利引用Test2<FootBall>实例所返回的对象,此情况属于协变。

将抽象类作为类型约束

【导语】

将泛型参数约束为抽象类或接口,可以有效规范对类型实例的访问,中在实际开发中比较实用。如果不给类型参数添加约束,那么在默认情况下编译器就会以 Object 类的成员进行规范,这个会带来诸多不便。

然而如果是应用抽象类或者接口来约束类型参数,那么在访问参数实例时就很方便了。例如声明一个接口,名为 IService,接口中声明两个 方法————OpenClose。在泛型参数中添加约束,要求类型必须实现 IService 接口。在这种情况下,访问泛型参数实例的代码不需要关心有多少个类实现了 IService 接口,因为不管是哪个类,只要它实现了该接口,必然会包含 OpenClose 这两个方法,如此一来,代码只需要带调用方法。

【操作流程】

步骤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>Tchar 类型。调用 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”的 ASCII49,字符“2”的 ASCII50,如果要使字符“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> 集合,参数 Tint 类型。

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> 集合允许多线程访问(添加或移除元素)。当集合中的元素数量达到容量上限时,添加操作会被阻止,直到集合中有元素被移除(重新获取到可用容量);同样的,当从集合中移除元素时,如果集合中无可用元素,那么移除操作会别阻止,直到集合中有新的元素加入。

多个线程可用同时调用 AddTryAdd 方法向集合中添加元素,也可以同时调用 TakeTryTake 方法移除元素。当调用 CompleteAdding 方法后,就不能在向集合中添加元素了,否则会引发异常。

【操作流程】

步骤1:新建控制台应用程序项目。

步骤2:创建 BlockingCollection<T> 实例,Tint 类型。

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精彩实例分享 -- 泛型和集合相关推荐

  1. .net core精彩实例分享 -- 字符串处理

    文章目录 介绍 具体案例 处理超大整数 获取指定日期的农历日期 输出百分比 输出多个币种格式 数字的两种常用格式 自定义小数位数 总结 介绍 随着.net core越来越流行,对.net core 基 ...

  2. .net core精彩实例分享 -- 应用启动

    文章目录 介绍 具体案例 配置Web服务器的URL 配置Web项目的调试方案 基于方法约定的Startup类 使用非预定义环境 总结 介绍 随着.net core越来越流行,对.net core 基础 ...

  3. .net core精彩实例分享 -- 反射与Composition

    文章目录 介绍 具体案例 用Activator类创建类型实例 检查类型上所应用的自定义Attribute 通过协定来约束导出类型 导入多个类型 封装元数据 总结 介绍 随着.net core越来越流行 ...

  4. .net core精彩实例分享 -- LINQ

    文章目录 介绍 具体案例 将对象转为字典集合 将原始序列进行分组 按员工所属部门 DefaultIfEmpty方法的作用 将分组后的序列重新排序 使用并行LINQ 总结 介绍 随着.net core越 ...

  5. .net core精彩实例分享 -- 应用配置和数据库访问

    文章目录 介绍 具体案例 自定义环境变量的命名前缀 自定义命令行参数映射 使用JSON文件来配置选项类 在应用程序运行期间创建SQLite数据库 总结 介绍 随着.net core越来越流行,对.ne ...

  6. .net core精彩实例分享 -- 依赖注入和中间件

    文章目录 介绍 具体案例 临时访问服务 以委托形式定义中间件 带参数中间件 IMiddleware中间件的用途 让 HTTP 管道"短路" 中间件的分支映射 文件服务 总结 介绍 ...

  7. .net core精彩实例分享 -- 网络编程

    文章目录 介绍 具体案例 从Web服务器上下载图片 使用HttpClient类向Web服务器提交数据 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关 ...

  8. .net core精彩实例分享 -- 异步和并行

    文章目录 介绍 具体案例 等待线程信号--ManualResetEvent 等待线程信号--AutoResetEvent 多个线程同时写一个文件 串联并行任务 使用Parallel类执行并行操作 为每 ...

  9. .net core精彩实例分享 -- 序列化

    文章目录 介绍 具体案例 将类型实例序列号危机JSON格式 将数据协定序列化为JSON格式 总结 介绍 随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关的知识也应 ...

最新文章

  1. 【Android OpenGL ES】阅读hello-gl2代码(二)Java代码
  2. R语言使用ggplot2包使用geom_boxplot函数绘制基础分组箱图(设置异常值的形状、颜色)实战
  3. 打通docker api
  4. WebAPi接口安全之公钥私钥加密
  5. k8s:koolshare软路由安装及k8s基本环境配置
  6. 搭建一个VUE+Express前后端分离的开发环境
  7. 论文浅尝 | Data Intelligence 已出版的知识图谱主题论文
  8. 为view添加约束constraints
  9. C语言之程序中内存的来源:栈 堆 数据段
  10. (92)低速接口UART、IIC、SPI介绍,面试必问(十六)(第19天)
  11. java以32位运行,强制java applet以32位而不是64位JRE运行
  12. redis 缓存 淘汰
  13. python编程(深拷贝和浅拷贝)
  14. 想学IT的必看!深度解析跳槽从开始到结束完整流程万字长文!
  15. 2023南京工业大学计算机考研信息汇总
  16. 使用Python+百度AI把文字转成语音
  17. 好好说说互联网IT行业加班那点儿事
  18. 失恋后同学对我的劝告。。。。表谢意
  19. Android使用VideoView播放视频
  20. linux上远程文件传输工具 scp sz rz

热门文章

  1. ios 数字键盘左下角添加按钮_IOS数字键盘左下角添加完成按钮的实现方法
  2. bat产品经理能力模型_浅析产品经理能力模型
  3. mysql 日志文件 自动_教你自动恢复MySQL数据库的日志文件
  4. android+ndk+r9+x64下载,Win7 64位中文旗舰版上Cocos2d-x 3.0的Android开发调试环境架设
  5. oracle group by 取最新的一条_大国智能制造全文免费阅读_大国智能制造最新章节_乌溪小道的小说...
  6. 在python中获取当前工作目录可以通过_python-获取当前工作路径
  7. rstudio安装后打不开_R与RStudio最简单安装指南
  8. java se development kit可以卸载吗_首款纯电版MINI COOPER详细评测,或将国产,值得等吗?...
  9. UI线框图模板素材实际应用好帮手
  10. php使用常量cont,php常量介绍