C#与C++的发展历程第二 - C#4.0再接再砺

系列文章目录html

1. C#与C++的发展历程第一 - 由C#3.0起程序员

2. C#与C++的发展历程第二 - C#4.0再接再砺编程

 

开始本系列的第二篇,这篇文章中将介绍C#4.0中一些变化,如C++有相似功能也将一并介绍。我的感受C#4.0中增长的语言方面的特性不是不少,多是这个时期都在着力完成随之发布的新的4.0版的CLR。整体来讲C#4.0中有4个方面的特性。下面依次介绍:json

 

C#4.0 (.NET Framework 4.0, CLR 4.0)c#

 

C# 动态类型

在诸如Javascript这样的脚本语言中咱们能够随时给对象添加成员 :数组

1
2
3
4
5
6
7
8
9
10
11
var  student = {};
student.Name=  '张三' ;
student.StudyDay=110;
student.PlayDay=50;
student.CalcMark =  function (talent)
{
     var  study = Math.pow(1.01,student.StudyDay)/4.9*100;
     var  play=Math.pow(0.99,student.PlayDay)/4.9*100;
     var  talentmark=student.PlayDay/160*100;
     return  talent?study+talentmark:study+play;
};

如今C#中经过动态类型的支持,咱们也能编写相似的代码。以下:安全

1
2
3
4
5
6
7
8
9
10
dynamic student =  new  ExpandoObject();
student.Name =  "李四" ;
student.StudyDay = 120;
student.PlayDay=40;
student.CalcMark = (Func< bool , double >)(talent=>{
              var  study=Math.Pow(1.01,student.StudyDay)/4.9*100;
              var  play=Math.Pow(0.99,student.PlayDay)/4.9*100;
              var  talentMark=student.PlayDay/160*100;
              return  talent?study+talentMark:study+play;
              });

调用动态添加和方法和调用普通方法同样。并发

1
var  mark = student.CalcMark( false );

给动态类型添加事件也是能够的,如:app

1
2
3
student.MakeMistakes =  null ;
student.MakeMistakes +=  new  EventHandler((sender, e)=>
                         Console.WriteLine( "{0}被请家长。" , ((dynamic)sender).Name));

触发事件看起来和调用函数差很少:框架

1
student.MakeMistakes(student, new  EventArgs());

这种动态类型的一个很大做用就是在咱们想传递一个对象但不想为此定义一个类时采用。如ASP.NET MVC 3起ControllerBase类新增的的ViewBag属性就是dynamic对象适用场景的典型例子。

1
public  dynamic ViewBag {  get ; }

有了ViewBag能够灵活的在Controller和View之间传递数据,而不要事先定义Model。固然使用ViewBag的效率也不如强类型的Model类。

咱们能够定义一个返回dynamic对象的方法:

1
2
3
4
5
6
public  dynamic GetStudent()
{
     dynamic student =  new  ExpandoObject();
     //略去添加成员的代码..
     return  student;
}

调用这个方法咱们能够获得一个动态对象,并访问其中定义的值:

1
dynamic student = GetStudent();

一样咱们也能够定义一个接受dynamic对象的函数:

1
2
3
4
public  void  GetMark(dynamic student)
{
     double  mark = student.CalcMark( false );
}

使用这种接收或返回动态类型的函数要很是当心,每每错误到了运行时才会出现。

这里dynamic声明的对象是一类实现了IDynamicMetaObjectProvider接口类型的对象(也必须如此),这个接口提供的功能能够归纳为"延迟绑定",即在运行时才肯定类型。IDynamicMetaObjectProvider类型的对象执行在DLR上完成。

DLR是CLR4.0开始新增的专门处理动态类型的部分,全称Dynamic Language Runtime。经过DLR对动态类型的支持,能够在支持 DLR 互操做性模型的各类语言(如IronPython)之间共享 IDynamicMetaObjectProvider接口对象的实例。如咱们用C#实例化一个DynamicObject实例,并传递给IronPython函数。 

上文中介绍的ExpandoObject是一个IDynamicMetaObjectProvider接口的简单实现,其提供了基本的在运行时添加和删除实例的成员以及设置和获取这些成员的值的功能。

ExpandoObject 类还实现 IDictionary<String, Object> 接口,使用这个接口的方法能够在运行时判断某个动态成员是否存在。下面的例子来自MSDN:

示例代码中将 ExpandoObject 类的实例强制转换为 IDictionary<TKey, TValue> 接口,并枚举该实例的成员。

1
2
3
4
5
6
7
8
dynamic employee =  new  ExpandoObject();
employee.Name =  "John Smith" ;
employee.Age = 33;
 
foreach  ( var  property  in  (IDictionary<String, Object>)employee)
{
     Console.WriteLine(property.Key +  ": "  + property.Value);
}

经过将 ExpandoObject 实例强制转换到 IDictionary<String, Object> 接口,还能够经过IDictionary接口以删除键值对的方式来删除动态成员。 以下例子:

1
2
3
dynamic employee =  new  ExpandoObject();
employee.Name =  "John Smith" ;
((IDictionary<String, Object>)employee).Remove( "Name" );

ExpandoObject类还实现了INotifyPropertyChanged 接口,在成员添加、删除或修改时会引起 PropertyChanged 事件。在一些使用MVVM模式的环境中,以ExpandoObject做为ViewModel对象可以方面的通知View层其内容更改以实现数据绑定。

 

使用动态对象的代码在第一次给动态对象赋值后,在运行时赋值代码执行完成后,动态类型也就肯定了。后续使用就应该注意类型要匹配。以下面的代码在运行时就会因为类型不匹配而出现异常。

1
2
dynamic year=  "2014" ;
year++;

从新给动态对象赋值能够改变其运行时的类型,下面的代码在运行时不会报错:

1
2
3
dynamic year=  "2014" ;
year = 2014;
year++;

 

若是须要更复杂的运行时动态行为,可使用DynamicObject对象,可是须要注意不能直接实例化DynamicObject类型的对象,须要继承DynamicObject类型建立本身的对象,而后再实例化这个自定义DynamicObject类型的对象。DynamicObject类预约义一些高级的延迟绑定功能,经过实现它能够很轻松的建立本身的动态类型,从而自定义能够在动态对象上执行哪些操做以及如何执行这些操做。下面一样是一个来自MSDN的例子。

示例代码实现的类继承了DynamicObject类,并给出了新的实现,其将动态类的属性保存在一个内部字典中,并提供了查询动态设置的属性数量的Count属性。

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
// DynamicDictionary继承自DynamicObject
public  class  DynamicDictionary : DynamicObject
{
     //内部字典对象
     Dictionary< string object > dictionary =  new  Dictionary< string object >();
      
     //返回动态添加的属性的数量,即内部字典元素的个数
     public  int  Count
     {
         get
         {
         return  dictionary.Count;
         }
     }
      
     //当试图获取一个不是定义于类的属性的值时,将调用这个方法
     public  override  bool  TryGetMember(GetMemberBinder binder,  out  object  result)
     {
         //将属性名变为小写,实现属性名大小写无关
         string  name = binder.Name.ToLower();
          
         //若是在字典中找到访问的属性名,将属性值经过result返回,并返回true,反之返回false
         return  dictionary.TryGetValue(name,  out  result);
     }
      
     //当试图设置一个不是定义于类的属性的值时,将调用这个方法
     public  override  bool  TrySetMember(SetMemberBinder binder,  object  value)
     {
         //将属性名变为小写,实现属性名大小写无关
         dictionary[binder.Name.ToLower()] = value;
          
         //任什么时候候均可以添加新的属性,方法老是返回true
         return  true ;
     }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class  Program
{
     static  void  Main( string [] args)
     {
         //实例化一个自定义动态类对象
         dynamic person =  new  DynamicDictionary();
          
         //添加动态属性(TrySetMember方法被调用)
         person.FirstName =  "Ellen" ;
         person.LastName =  "Adams" ;
          
         //获取动态属性的值(TryGetMember方法被调用)
         //属性名大小写无关
         Console.WriteLine(person.firstname +  " "  + person.lastname);
          
         //获取Count属性的值(没有调用TryGetMember方法,Count属性是定义于类中的)
         Console.WriteLine( "动态属性的数量为:"  + person.Count);
          
         //下面的语句会在运行时抛出异常
         //因为不存在"address"属性,TryGetMember方法返回false,从而致使RuntimeBinderException
         Console.WriteLine(person.address);
     }
}

 

更多关于实现DynamicObject类型的讨论,能够见园友JustRun的这篇文章

其中实现了一个DynamicJson类,能够将json字符串转为一个能够像Javascript中同样使用的json动态对象。

若是DynamicObject也没法实现某些须要的功能,能够直接实现IDynamicMetaObjectProvider接口来控制动态类型对象的延迟绑定行为。

 

最后来看下动态类型的一些其它做用。经过动态类型的支持,咱们能够把以前一些须要反射来实现的功能交由动态类型来实现。一样是以前使用的Student类做为例子:

先定义一个Student类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public  class  Student
{
     public  string  Name {  get set ; }
     public  int  StudyDay {  get set ; }
     public  int  PlayDay {  get set ; }
     public  double  CalcMark( bool  talent)
     {
         var  study = Math.Pow(1.01, StudyDay) / 4.9 * 100;
         var  play = Math.Pow(0.99, PlayDay) / 4.9 * 100;
         var  talentMark = PlayDay / 160 * 100;
         return  talent ? study + talentMark : study + play;
     }
}

假如这个Student类位于一个没有在当前程序编译时引用的程序集中,即你须要动态加载一个程序集并经过反射实例化Student类型,设置其属性并调用方法:

1
2
3
4
5
6
7
8
9
Assembly myAssembly = Assembly.LoadFrom( "StudentAssembly.dll" );
Type studentType = myAssembly.GetType( "Student" );
object  student = Activator.CreateInstance(studentType);
  
studentType.GetProperty( "Name" ).SetValue(student,  "王五" null );
studentType.GetProperty( "StudyDay" ).SetValue(student, 120,  null );
studentType.GetProperty( "PlayDay" ).SetValue(student, 30,  null );
object  markobj = studentType.GetMethod( "CalcMark" ).Invoke(student,  new  object [] {  false  });
double  mark = Convert.ToDouble(markobj);

如今用了动态类型的支持,咱们能够编写下面这样赏心悦目的代码:

1
2
3
4
5
6
dynamic student = Activator.CreateInstance(studentType);
  
student.Name = "赵六" ;
student.StudyDay = 120;
student.PlayDay = 30;
double  mark = student.CalcMark( false );

 

.NET社区的开源项目Clay将.NET的动态特性发挥到极致,使用Clay能够彻底摆脱C#的类型限制。关于Clay再次推荐JustRun的一篇文章

C++方面没有相似特性,没的写。

 

C# 可选参数与命名参数

可选参数

可选参数这个特性提及来很简单, 来看一个带有可选参数方法的声明:

1
2
3
4
public  void  Log( string  log, LogLevel level = LogLevel.Alarm ,LogTo destination = LogTo.Database)
{
     //...
}

调用这个函数有三种方式:

1
2
3
Log( "hello world" );
Log( "hello world"  , LogLevel.Info);
Log( "hello world"  , LogLevel.Info, LogTo.File);

须要注意的是可选参数必须定义在全部非可选参数的后面。这个特性的加入使之前不少须要定义重载函数的场景能够用一个支持可选参数的函数代替。

若是有一个函数重载和带可选参数方法的一种调用方式相同,则会优先调用没有可选参数的重载。即若是有下面的函数重载:

1
2
3
4
public  void  Log( string  log, LogLevel level)
{
     //...
}

则对于上文中第二种调用方式会优先选择这个重载。

关于可选参数的实现内幕能够看Artech大神的这篇文章

命名参数

命名参数功能可让咱们在传入实参时,在前面加上形参的名字,用以标识实参对应那个形参。

对于上面方法的调用能够这样:

1
Log( "hello world" , level: LogLevel.Info, destination:LogTo.File);

使用命名参数时,能够改变实参传入的顺序:

1
Log( "hello world" , destination:LogTo.File, level: LogLevel.Info);

这样的调用效果和以前是同样的。

和可选参数定义相似的是,命名参数必须放在调用参数列表的最后。

 

C++既没有可选参数也不支持命名参数,C++11对参数的改进就是在原来可变参数的基础上增长了可变参数模板。这实际上是很强大的一个功能,让变长的参数也能够强类型化。这个特性留到后面的文章再说。

 

C# 改进的COM交互

C#中COM互操做的改进彻底是得益于前两条新特性的加入。固然这块也是博主最不熟悉的领域,以前从没写过和COM交互的代码。因此仍是从国外程序员的博客上借(piao)用(qie)一个例子(出处)吧。

在C#4以前调用COM的代码经常会出现不少ref参数等。

1
2
3
4
5
6
static  void  Main()
{
     Word.Application app =  new  Word.Application();    
     object  m = Type.Missing;
     app.Documents.Add( ref  m,  ref  m,  ref  m,  ref  m);
}

C#4.0出现使这些都方便了不少:

1
2
Word.Application app =  new  Word.Application();
app.Documents.Add();

就是这样简单、任性!

另外结合动态类型,一些返回值的获取、参数传递以及赋值也大大简化了。

关于COM交互的支持在未来可能就真没什么用了,如今COM已经完美转型为WinRT,WinRT平台上微软给各类语言的交互提供了很完美的支持。听说是性能不太好,但代码颜值很高

 

这条目就更没C++什么事了。

 

C# 逆变与协变

C#4.0和.NET Framework 4.0开始支持的一个很重要的特性就是逆变与协变。为此C#引入了2个新的关键字in和out,分别表示逆变和协变。那什么是逆变和协变呢。仍是以例子开始说明,如今有两个拥有继承关系的类型:

为何关键字是in和out呢,能够这样理解,in表示泛型的参数被传入一个更小的做用域内,而这个范围内的方法知道如何去处理一个更具体的类型,这样操做是安全的。out表示泛型的参数会被返回到一个更大的做用域,因为自己就能够用父类去调用一个子类中从父类继承的属性或方法因此这种操做也是安全的。

1
2
3
4
5
6
7
8
9
10
11
public  class  Panda : Animal
{
     public  string  NickName {  get set ; }
}
 
public  class  Animal
{
     public  string  Name {  get set ; } 
     
     public  int  Age {  get set ; }
}

在这以前的.NET版本中,下面的代码是没法被支持的,由于Animal和Panda是不一样的类型:

1
2
var  pandas =  new  List<Panda>();
IEnumerable<Animal> animals = pandas;

在C#4.0和.NET Framework 4.0以后这样代码能够顺利被执行,缘由就是协变的支持,来看一下.NET 4.0中IEnumerable接口的定义:

1
public  interface  IEnumerable< out  T> : IEnumerable

能够看到在泛型类型以前多了表示协变的out关键字,咱们来给协变一个具体的定义,经过定义你们就能够了解为啥上面的赋值能够成立了。

协变:协变容许与泛型类型参数所定义的类型相比,派生程度更大的类型。相反,逆变就是容许与泛型形参所指定的实参类型相比,派生程度更小的实参类型。

.NET中支持逆变与协变的主要有如下几个地方:部分接口,委托还有数组。下表整理了下这些支持逆变或协变的类型(自行经过in仍是out判断是逆变仍是协变),主要参考的MSDN文档,可能有不全,欢迎补充:

分类 类名

接口

主要是集合接口和比较接口

IEnumerable<out T>
IEnumerator<out T>
IQueryable<out T>
IGrouping<out TKey, out TElement>
IComparer<in T>
IEqualityComparer<in T>
IComparable<in T>
 

Action<in T>

Action<in T1, in T2>

... ...

委托

Func<out TResult>

Func<in T, out TResult>

Func<in T1, in T2, out TResult>

... ...

Predicate<in T>
Comparison<in T>
Converter<in TInput, out TOutput>

前面的例子展现了协变,再来看一个逆变的例子,如今咱们把Animal改为支持比较的,比较的是两只动物年龄的大小:

1
2
3
4
5
6
7
8
9
10
11
12
public  class  Animal: IComparable<Animal>
{
     public  string  Name {  get set ; }
 
     public  int  Age {  get set ; }
 
     public  int  CompareTo(Animal other)
     {
         if  (other ==  null return  1;
         return  Age.CompareTo(other.Age);
     }
}

基于IComparable<T>逆变的支持,下面的代码能够正常工做:

1
2
3
4
5
6
var  pandas =  new  List<Panda>()
{
      new  Panda() {NickName =  "HuanHuan"  ,Age = 12 },
      new  Panda() {NickName =  "LeLe" , Age = 11 }
};
var  pandaSmallToBig = pandas.OrderBy(p=>p).ToList();

在排序后的列表中,LeLe在HuanHuan前面。

看过接口对协变和逆变的支持,再来看一些委托中的逆变与协变,因为如今基本上能够用Action和Func表示一切委托,因此示例也以它们为表明:

1
2
3
4
5
6
//逆变
Action<Animal> animalAction = ani => ani.Age = 15;
Action<Panda> pandaAction = animalAction;
//协变
Func<Panda> pandaFunc = () =>  new  Panda() { NickName =  "HuanHuan" , Age = 12 };
Func<Animal> animalFunc = pandaFunc;

相信经过这四行代码足矣了解委托中的协变和逆变。

最后数组的逆变也能够用一行代码来归纳:

1
2
var  pandas =  new  List<Panda>();
Animal[] animal = pandas.ToArray();

这行代码会致使Resharper给出一个警告,说可能会由于协变出现运行时异常。

 

上面所介绍的都是.NET中一些内置类型对逆变和协变的支持。在咱们本身的接口中也能够支持逆变或协变。

先来看看协变的使用,以代码为例,能够应用协变的场景有以下两种,注意代码注释:

1
2
3
4
5
6
7
8
9
10
//1.泛型类型做为返回值类型
interface  ICovariant< out  R>
{
     R GetSomething();
}
//2.做为委托类型参数的泛型类型
interface  ICovariant< out  R>
{    
     void  DoSomething(Action<R> callback);
}

对于逆变通常只用于内部函数的参数类型:

1
2
3
4
interface  IContravariant< in  A>
{    
     void  SetSomething(A sampleArg);
}

逆变和协变能够混合使用,但也要各自遵循上面的规则。

另一个值得一提的,协变或逆变都布不能“继承”,以协变为例来讲明:

1
2
interface  ICovariant< out  T> { }
interface  IDerived<T> : ICovariant<T> { }

IDerived<T>不具有协变特性,若是须要协变,须要显示指定:

1
interface  IDerived< out  T> : ICovariant<T> { }

注意这里IDerived也只能是协变,指定逆变是不行的。

1
2
//下面代码没法编译
//interface IDerived<in T> : ICovariant<T> { }

 

最后特别注意,逆变和协变只针对引用类型有效,值类型没法进行逆变和协变。标记为逆变或协变的泛型类型也不能用做ref或out参数。不能给协变的泛型类型添加泛型约束,可是能够给逆变的泛型参数在泛型方法中添加泛型约束。

 

.NET Framework 4.0 Tuple类型

.NET 4.0中新增的Tuple这个类型颇有用,固然这个类使用很简单,因此也很容易介绍。一句话归纳Tuple的做用就是把不一样类型的对象组合在一块儿。Tuple类有九个兄弟,最多的有8个泛型参数。

建立一个Tuple对象有两种方法,第一使用构造函数,如:

1
var  student =  new  Tuple< int , string int int >(3,  "小明" , 12, 95);

或者可使用Tuple非泛型类上的Create方法:

1
var  student = Tuple.Create(3,  "小明" , 12, 95);

第二种方法能够省略泛型类型,Create泛型方法的泛型类型会自动推断。

有了Tuple对象以后,能够经过Item1,Item2这样的属性依次访问存入Tuple的值,注意这些属性都是只读的。

在博主接触的场景中,Tuple的做用主要有二。

其一,用作返回多个对象的函数的返回类型。在Tuple出现以前,若是一个函数须要返回多个值/对象,只能经过ref或out参数这种方式。而有了Tuple,能够把要返回的对象保证在Tuple中。这个很简单就不给代码示例了。

其二,使用Dictionary<K,V>时,有时候V是一个包含多个对象的对象。如今除了用一个类来封装全部属性以外,对于很简单的状况,能够直接放在Tuple中做为字典值。

总之,有了Tuple,代码的可读性会大大提升。

 

C++11 STL std::tuple类型

不知道是否是跟C#学的,在C++11标准下的STL也增长了tuple类型。这是一个模板类型,其对象的建立和成员的访问都与.NET Framework中的Tuple很是相似。

不一样的是借助C++11新增的可变参数模板特性,C++11STL中的tuple能够接受任意多了个类型参数,而不像.NET Framework中的Tuple仅有九种不一样的实现,一次性接受的参数个数有限。这也再次体现了C++的模板功能方面的强大。

C++11 STL中的tuple的定义和初始化方式以下:

定义,须要在模板中指明成员的类型,以下(使用C#中的场景):

1
std::tuple< int , std::string,  int int > student;

若是在声明时直接初始化,必须使用直接初始化语法(tuple的构造函数为explicit,因为拷贝初始化方式隐式使用构造函数,因此没法进行拷贝初始化):

1
2
3
std::tuple< int , std::string,  int int > student(3,  "小明" , 12, 95); //正确,显示调用构造函数
std::tuple< int , std::string,  int int > student{3,  "小明" , 12, 95}; //正确,使用直接初始化
//std::tuple<int, std::string, int, int> student = {3, "小明", 12, 95};//错误,不能使用拷贝初始化方式

STL中也提供了make_tuple函数来构造一个tuple对象,这点与.NET中Tuple的Create方法很想,经过make_tuple,能够不用去写那一串模板类型:

1
auto  student = make_tuple(3,  "小明" , 12, 95);

STL也提供了一个模板函数get用于访问tuple中的成员,模板参数用于传入要访问第几个成员:

1
2
3
auto  no = get<0>(student);
auto  name = get<1>(student);
auto  age = get<2>(student);

STL的中的tuple重写了<以及==等运算符,因此能够方便比较两个tuple对象。

一样,在C++中tuple类型最重要的一个做用也是在函数中使用tuple一次返回多个值。这个使用上也很简单也没有什么特殊之处,篇幅缘由就不给出例子了。

 

.NET Framework 4.0 并行LINQ

并行LINQ来自于.NET Framework 4.0新增的TPL(Task Parallel Library,即任务并行库)。TPL带来了.NET 4中对并行和异步两部分的新特性,其覆盖范围至关普遍,异步部分在C#5.0又有了大改变将在下一篇文章中重点介绍,并行部分这小节只讨论PLINQ,其余话题很少介绍,园子里也相关文章太多太多。有兴趣的同窗能够分别去了解异步和并行两方面TPL的新特性。奉上一张有关.NET对异步并发编程的支持的思惟导图。园友们能够参考其中的分支对不了解的区域重点学习。

图1 .NET中的并发/异步编程

本节介绍其中的并行LINQ主要是考虑到前文介绍过LINQ,为了保证完整性,这里把并行LINQ也提一下。

PLINQ主要是使以前顺序执行的LINQ查询能够以并行方式来执行,从而充分利用多核CPU的计算能力。

这里一样准备一个图片帮助你们理解下PLINQ的相关方法的做用,能够将其做为一个纲要来学习其中的相关细节。

图2

一图胜千言,下面对照上面的图来大概介绍下PLINQ的某些方面。PLINQ最为.NET中最容易使用的一种并行编程技术,其“入口”只有一个AsParallel()方法。经过这个方法能够将一个普通的LINQ查询转为并行LINQ即PLINQ查询(若是经执行环境评估后面的查询不能并行执行,则依然会顺序执行)。这样LINQ的查询操做将会并行执行。除了并行执行,PLINQ支持几乎所有的LINQ查询如Select()、Where()以及那些聚合操做如Average()和Sum()等。与AsParallel()相对还能够经过AsSequence()方法将一个PLINQ转回LINQ即中止后续查询的并行执行。

PLINQ查询默认不保持原集合中元素的顺序,若是想让获得的结果保持原顺序,请在AsParallel()后使用AsOrdered()而后再编写查询操做。

对于PLINQ执行结果的使用,若是能够并行使用就尽可能使用ForAll(),这样效率最好,若是使用foreach等方式使用PLINQ的集合,集合将被处理为一个序列。

在取消和异常处理方面,PLINQ使用CancellationToken和AggregateException,这与.NET环境中其余并行和异步组件的模式是相同的,很容易使用。

具体使用代码就再也不举例了。此段内容仅做为抛砖引玉。

 

预告

下篇文章将重点介绍C#5.0和.NET 4.5带来的异步编程的巨大改进。同时也会附加介绍WinRT框架下使用C++和C#实现异步编程的方法。另外还有一些C#5.0其余方面的小改进。

相关文章
相关标签/搜索