本篇文章主要介绍泛型的应用。
泛型是.NET Framework 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性、类型安全性和效率。
泛型的定义
下面定义了一个普通类和一个泛型类,我们可以明确看到泛型类和普通类最大的区别就是多了一个<T>。
所以,这个<T>就标记了,这个类是泛型类。其中这个T,也可以写成A,B,C,D或其他字符。
1 2 3 4 | public class Generic { public String Name; } |
1 2 3 4 | public class Generic<T> { public T Name; } |
泛型,顾名思义,就是泛指的类型。好比男人,女人,白人,黑人,可以泛称为【人】。
但类型只能是一个类型。 那么泛型和类型之间是什么关系呢?
其实很简单,泛型在定义的时候,是泛指类型;在使用的时候,就需要被指定,到底使用哪个类型。
即,使用时,就不在是泛指类型,而是特定类型。
好比,定义时,定义了一个人。但在使用时,必须明确指定,到底是黑人还是白人。
泛型的使用
泛型类跟普通类的使用方式一样,都需要实例化对象,再由对象来调用内部的属性或方法。
下面代码实例化了泛型Generic,实例化时,还指定了该泛型Generic的指定类型为String。
所以要给泛型Generic的属性Name赋值,就需要赋值字符串类型的值。
1 2 3 4 5 | public static void Excute() { Generic<String> gs = new Generic<String>(); gs.Name = "Kiba518" ; } |
下面代码定义了一个Int类型的泛型Generic。
1 2 3 4 5 | public static void Excute() { Generic< int > gs = new Generic< int >(); gs.Name = 518; } |
泛型的默认值
泛型的默认值,如下面代码所示。需要使用default(T)来赋值。
不管泛型到底是String,int,bool或者是一个Class类型,都可以被自动赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static void Excute() { Generic< int > gs = new Generic< int >(); gs.Name = 518; Generic<Task> gsTask = new Generic<Task>(); gsTask.Name = new Task(()=> { Console.WriteLine( "Kiba518" ); }); } public class Generic<T> { public T Name = default (T); } |
泛型的约束
在泛型类中,有个特别的约束可供我们使用。
当我们不显示的声明时,这个约束不存在。但当我们显示的声明的时候,这个约束就会执行。
下面,我们来看看这个特别的约束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public static void Excute() { Generic<FanXing> gFanXing = new Generic<FanXing>(); Generic<Base> gFanXingBase = new Generic<Base>(); //Generic<string> gs = new Generic<string>(); 这样定义会报错 } public class Generic<T> where T : Base { public T Name = default (T); } public class Base { public string Name { get ; set ; } } public class FanXing : Base { public new string Name { get ; set ; } } |
如上面代码所示,【where T : Base】就是这个特别的约束。
当显示声明这个约束的时候,定义会限制泛型的类型。
什么是限制泛型的类型呢?
很简单,泛型T,是泛指某一个类型。我们在定义泛型类时,还需显示的指定类型,此时我们显示指定的类型,要受这个限制。
这个限制就是指【where T : Base】。
它的限制是,要求我们指定的类型T必须是Base,或者该类型继承自Base,如FanXing类。
泛型的函数
在C#中,泛型不仅可以用于类,还可以直接用于函数。
具体使用方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 | public static void Excute() { GenericFunc gf = new GenericFunc(); gf.FanXingFunc<FanXing>( new FanXing() { Name= "Kiba518" }); } public class GenericFunc { public void FanXingFunc<T>(T obj) { Console.WriteLine(obj.GetType()); } } |
很简单,调用泛型函数的时候,指定泛型函数的[指定类型]即可。
但是,这里我们发现一个问题,那就是,在泛型函数里,使用泛型对象的时候,我们发现对象都是object类型的。
那我们如果想使用泛型对象里的属性和方法时,要怎么办呢?
也很简单,反射就可以了。
下面我们添加一个反射函数GetPropertyValue,专门用来获取属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class GenericFunc { public void FanXingFunc<T>(T obj) { var name = GetPropertyValue(obj, "Name" ); Console.WriteLine(name); } public object GetPropertyValue( object obj, string name) { object drv1 = obj.GetType().GetProperty(name).GetValue(obj, null ); return drv1; } } |
输出结果如下:
这样我们就得到了我们想要的结果,如果想使用泛型类里的函数,道理也一样,只需要用反射来调用即可。
结语
看到这里,有些同学可能会觉得泛型很复杂,连使用其对象下的属性,都得反射,太繁琐了,还不如不用呢。
有这样想法的同学,心里想想就好了,如果对老司机这么说,他肯定会内心默默的微笑,然后对你说,你想的没错。
然后,你就没有然后了。
泛型的应用,开篇已经说了,主要用在提高代码的可重用性、类型安全性和效率上。
如果只是定义一个类,调用一个属性,那泛型的存在就是鸡肋。
但事实上,我们的系统永远只有更复杂,更复杂,更复杂。因此泛型才有了用武之地。
----------------------------------------------------------------------------------------------------
注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错,请点击下右下角的【推荐】,非常感谢!
C#5.0推出了新语法,await与async,但相信大家还是很少使用它们。关于await与async有很多文章讲解,但有没有这样一种感觉,你看完后,总感觉这东西很不错,但用的时候,总是想不起来,或者不知道该怎么用。
为什么呢?我觉得大家的await与async的打开方式不正确。
正确的打开方式
1、await 只能在标记了async的函数内使用。
2、await 等待的函数必须标记async。
有没有感觉这是个循环?没错,这就是个循环。这也就是为什么大家不怎么用他们的原因。这个循环很讨厌,那么怎么破除这个循环呢?
【很简单,await等待的是线程,不是函数。】
不理解吗?没关系,接着看下去。
下面从头来讲解,首先看这么一组对比
1 2 3 4 5 6 7 8 | public static int NoAsyncTest() { return 1; } public static async Task< int > AsyncTest() { return 1; } |
async Task<int>等于int
这意味着我们在正常调用这两个函数时,他们是等效的。那么用async Task<int>来修饰int目的是什么呢?
目的是为了让这个方法这样被调用 await AsyncTest(),但直接这样调用,并不会开启线程,那这样费劲的修饰是不是就没什么意义了呢。
当然不是,那什么时候会让 await AsyncTest()有意义呢?
我们接着往下看,修改AsyncTest如下。然后,此时再调用await AsyncTest(),你会神奇的发现,依然没有卵用。。。
Excute方法正常执行,而AsyncTest内运行的线程,自己执行自己的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static async void Excute() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 开始 Excute " + DateTime.Now); await AsyncTest(); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 结束 Excute " + DateTime.Now); } public static async Task< int > AsyncTest() { Task.Run(() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now); Thread.Sleep(1000); }); return 1; } |
别着急,我们稍作调整,在线程后面增加.GetAwaiter().GetResult()。这句话是干什么用的呢?是用来获取线程返回值的。
这个逻辑是这样的,如果想要获取线程返回结果,就自然要等待线程结束。
运行一下,我们将看下面的结果。
1 2 3 4 5 6 7 8 9 | public static async Task< int > AsyncTest() { Task.Run(() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now); Thread.Sleep(1000); }).GetAwaiter().GetResult(); return 1; } |
但是,好像await AsyncTest();还是没启作用。没错,事实就是,他真的不会起作用。。。
那么怎么才能让他起作用呢?
首先,我们定义一个普通函数,他的返回值是一个Task,然后我们得到Task后,运行它,再用await等待这个Task。
于是我们就得到这样的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public static async void Excute() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 开始 Excute " + DateTime.Now); var waitTask = AsyncTestRun(); waitTask.Start(); int i = await waitTask; Console.WriteLine(Thread.CurrentThread.GetHashCode() + " i " + i); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 结束 Excute " + DateTime.Now); } public static Task< int > AsyncTestRun() { Task< int > t = new Task< int >(() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now); Thread.Sleep(1000); return 100; }); return t; } |
如图,这样写await AsyncTest();就起作用了。
所以,还是那句话,await等待的是线程,不是函数。
但在图里,我们发现很奇怪的一点,结束Excute也是线程3,而不是线程1。也就是说,Await会对线程进行优化。
下面看下两组代码的对比,让我们就更清楚的了解下Await。
第一组,使用await等待线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public static async void Excute() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 开始 Excute " + DateTime.Now); await SingleAwait(); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 结束 Excute " + DateTime.Now); } public static async Task SingleAwait() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " AwaitTest开始 " + DateTime.Now); await Task.Run(() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now); Thread.Sleep(1000); }); await Task.Run(() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run2 " + DateTime.Now); Thread.Sleep(1000); }); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " AwaitTest结束 " + DateTime.Now); return ; } |
第二组,使用等待线程结果,等待线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public static async void Excute() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 开始 Excute " + DateTime.Now); await SingleNoAwait(); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 结束 Excute " + DateTime.Now); } public static async Task SingleNoAwait() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " SingleNoAwait开始 " + DateTime.Now); Task.Run(() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now); Thread.Sleep(1000); }).GetAwaiter().GetResult(); Task.Run(() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run2 " + DateTime.Now); Thread.Sleep(1000); }).GetAwaiter().GetResult(); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " SingleNoAwait结束 " + DateTime.Now); return ; } |
可以明确的看到,第二组,线程重新回到了主线程1中,而第一组,已经被优化到了线程4中。
await是一种很便捷的语法,他的确会让代码简洁一些,但他主动优化线程的功能,如果不了解就使用,可能会导致一些奇怪的BUG发生。
这也是官方为什么只提供了await调用服务的例子,因为,在程序内调用,await还是要了解后,再使用,才安全。
----------------------------------------------------------------------------------------------------
这是线程安全的最后一篇了,主要介绍CancellationToken的多种应用。
1,ThreadPool直接启动线程,传递CancellationToken。
2,Task启动线程,传递CancellationToken。Task传递方式分为两种,一种通过Task的参数进行传递,另一种通过向线程内传递对象的方式传递CancellationToken。
3,CancellationToken的回调函数应用。
话不多说,请看代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | class Program { static void Main( string [] args) { Console.WriteLine( "当前线程{0},当前状态{1}" , Thread.CurrentThread.GetHashCode(), Thread.CurrentThread.ThreadState); //使用线程池创建线程,然后取消线程 CancelWithThreadPoolMiniSnippet(); } static CancellationTokenSource cts = new CancellationTokenSource(); static CancellationToken token = cts.Token; static void CancelWithThreadPoolMiniSnippet() { Console.WriteLine( "当前线程{0},当前状态{1}" , Thread.CurrentThread.GetHashCode(), Thread.CurrentThread.ThreadState); #region 使用QueueUserWorkItem的构造函数,传递cts.Token,但我不喜欢这个模式 跟踪不了状态 //ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), ctn); #endregion #region 使用传递参数的模式 传递CancellationToken,这里的cts.Token是作为Action的参数传递的 //var action = new Action<object>(DoSomeWork); //Task t = new Task(action, ctn); //t.Start(); //Console.WriteLine("开始,当前线程{0},当前状态{1}", t.GetHashCode(), t.Status); #endregion #region 使用Task的构造函数,传递cts.Token,但CancellationTokenSource要弄成全局变量,否则方法找不到,就取消不了。 //Task t = new Task(Work, cts.Token); //t.Start(); #endregion #region 注册回调函数,当CancellationTokenSource.Cancel()执行后,调用回调函数 token.Register(CallBack, true ); //注册回调函数 Task t = new Task(Work); t.Start(); #endregion Thread.SpinWait(5000000); cts.Cancel(); Console.WriteLine( "结束,当前线程{0},当前状态{1}" , t.GetHashCode(), t.Status); Console.Read(); } static void DoSomeWork( object obj) { CancellationToken token = (CancellationToken)obj; for ( int i = 0; i < 100000; i++) { Console.WriteLine(i); // Simulating work. //Thread.SpinWait(5000000); if (token.IsCancellationRequested) { break ; } } } static void Work() { for ( int i = 0; i < 100000; i++) { Console.WriteLine(i); if (token.IsCancellationRequested) { break ; } } } static void CallBack() { Console.WriteLine( "I'm call back!" ); } } |
代码内执行结果如下,该结果为CancellationToken的回调函数应用:
到此NET Framework4.0里的线程安全就都讲完了。。。。。。。
虽然第一篇文章是2013年,虽然历时近五年,但请相信我,代码早在五年前就已经写完啦。只是我一直一直一直没配文字发出来。。。。。。
不过,也可能是最近写文字的能力有所提升,所以就完成了四和五。
不然这线程安全的文章可能还要拖。。。。。。。。哈哈
在NET Framework4.6里,微软提供了async和await语法,也是有关线程安全,我将会在新的语法相关文章里讲解async和await的用法。
----------------------------------------------------------------------------------------------------
我们现在使用的C#语法已经可以满足日常的开发需求,但C#语法还在进行版本的更新,在创造更多更优秀的语义来让我们使用。这里介绍一下C#5.0里的提供的语法——元组。
在C#中定义Tuple对象,转到定义查看,我们会看到如下代码
#region 程序集 mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6\mscorlib.dll #endregion
即该语法在.Net Framework4框架中已经可以支持了。
元组Tuple是一种数据结构,具有特定数量和元素序列。什么意思呢?就是元组是可以存贮多种类型的对象,可以想象一下当一个函数拥有多个不同类型的返回值时,我们除了定义了一个返回值以外,还要定义多个out或ref类型返回值才能解决这个需求;当然我们也可以定义一个对象保存多个返回值。但现在我们多了一个解决方案,定义返回值为一个元组,就解决了一切。
元组Tuple是可以存贮多种类型的数据的。NET Framework 直接支持具有 1 到 7 元素的元组。 此外,您可以创建由嵌套中的元组对象的元组的八个或多个元素Rest属性Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>对象。
元组常用四种方法︰
1,用来表示一组数据。 例如,一个元组可以表示的数据库记录,并且其组件可以表示每个字段的记录。
2,若要提供轻松访问和数据集的操作。
3,out参数 (在 C# 中) 或ByRef参数 (在 Visual Basic 中)。
4,若要将多个值传递给通过单个参数的方法。 例如,Thread.Start(Object)方法只有一个参数,允许你提供一个线程在启动时执行的方法的值。如果你提供Tuple<T1, T2, T3>对象作为方法自变量,则可以提供有三个项的数据的线程的启动例程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class Program { static void Main( string [] args) { var tuple = new Tuple< string , int , int , int >( "Kiba" , 00001, 00002, 00003); Console.WriteLine(tuple.Item1); Console.WriteLine(tuple.Item2); Console.WriteLine(tuple.Item3); Console.WriteLine(tuple.Item4); var tupleCalss = new Tuple<A, B>( new A(), new B()); Console.WriteLine(tupleCalss.Item1.Name); Console.WriteLine(tupleCalss.Item2.Name); Console.ReadKey(); } } public class A { public string name = "A" ; public string Name { get => name; set => name = value; } } public class B { public string Name = "B" ; }<br>}<br><br><br> |
输出结果
Kiba123AB
【PS:这里使用的目标框架是.net framework 4.0 ,我们可以看到属性的声明如下,即4.0已经支持=>模式的属性设置了。】
1 2 3 | public string name = "A" ; public string Name { get => name; set => name = value; } |
观点一:
1、Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等;
2、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储;
3、--Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘;
4、过期策略--memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10;
5、分布式--设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从;
6、存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化);
7、--memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复;
8、Redis支持数据的备份,即master-slave模式的数据备份;
9、mongodb和memcached不是一个范畴内的东西。mongodb是文档型的非关系型数据库,其优势在于查询功能比较强大,能存储海量数据。mongodb和memcached不存在谁替换谁的问题。
观点二:
如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:
1 Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。2 Redis支持数据的备份,即master-slave模式的数据备份。3 Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。在Redis中,并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别(我个人是这么认为的)。Redis 只会缓存所有的key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作,Redis根据“swappability = age*log(size_in_memory)”计算出哪些key对应的value需要swap到磁盘。然后再将这些key对应的value持久化到磁 盘中,同时在内存中清除。这种特性使得Redis可以保持超过其机器本身内存大小的数据。当然,机器本身的内存必须要能够保持所有的key,毕竟这些数据 是不会进行swap操作的。同时由于Redis将内存中的数据swap到磁盘中的时候,提供服务的主线程和进行swap操作的子线程会共享这部分内存,所以如果更新需要swap的数据,Redis将阻塞这个操作,直到子线程完成swap操作后才可以进行修改。可以参考使用Redis特有内存模型前后的情况对比:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace 正确打开方式{ class Program { public static async void Excute() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 开始 Excute " + DateTime.Now); var waitTask = AsyncTestRun(); //表示一个可以返回值的异步操作。 waitTask.Start(); //int i = 0; int i = await waitTask; Console.WriteLine(Thread.CurrentThread.GetHashCode() + " i " + i); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 结束 Excute " + DateTime.Now); Console.ReadKey(); } public static Task AsyncTestRun() { Task t = new Task (() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now); Thread.Sleep(1000); return 100; }); return t; } public static async Task AsyncTest() { Task.Run(() => //将在线程池上运行的指定工作排队,并返回该工作的任务句柄。 { //创建了新线程 Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now); Thread.Sleep(1000); }).GetAwaiter().GetResult(); return 1; } static void Main(string[] args) //错误 1 “正确打开方式.Program.Main(string[])”: 入口点不能用“async”修饰符标记 { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 开始 Excute " + DateTime.Now); //AsyncTest(); //正常调用不会创线程 //AsyncTest(); //出现三行 说明 创建了 一个线程 、、出现四行 说明 创建了 两个个线程 五行 //但是,好像await AsyncTest();还是没启作用。没错,事实就是,他真的不会起作用。。。 //Excute(); Console.WriteLine("--"); Console.WriteLine("--"); //Excute2(); Excute3(); Console.ReadKey(); } public static async void Excute2() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 开始 Excute2 " + DateTime.Now); await SingleAwait(); //等待线程结束 然后执行后面的 Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 结束 Excute2 " + DateTime.Now); } public static async Task SingleAwait() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " AwaitTest开始 " + DateTime.Now); await Task.Run(() => //await 等待线程完成 { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now); Thread.Sleep(1000); }); await Task.Run(() => //Await会对线程进行优化。 { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run2 " + DateTime.Now); Thread.Sleep(1000); }); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " AwaitTest结束 " + DateTime.Now); return; } public static async void Excute3() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 开始 Excute3 " + DateTime.Now); await SingleNoAwait(); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " 结束 Excute3 " + DateTime.Now); } public static async Task SingleNoAwait() { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " SingleNoAwait开始 " + DateTime.Now); Task.Run(() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run1 " + DateTime.Now); Thread.Sleep(1000); }).GetAwaiter().GetResult(); //用来获取线程返回值的。这个逻辑是这样的,如果想要获取线程返回结果,就自然要等待线程结束。 Task.Run(() => { Console.WriteLine(Thread.CurrentThread.GetHashCode() + " Run2 " + DateTime.Now); Thread.Sleep(1000); }).GetAwaiter().GetResult(); Console.WriteLine(Thread.CurrentThread.GetHashCode() + " SingleNoAwait结束 " + DateTime.Now); return; } }}