飘遥的Blog

C/C++/.NET
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Linq 学习(2) .NET 3.X新特性回顾

Posted on 2008-08-05 05:00  Zzx飘遥  阅读(1721)  评论(1编辑  收藏  举报
前面介绍了《C# 3.0 新特性》,对其注意事项没有过多的介绍,在这补充一下,回顾一下.NET 3.X的新特性。

自动属性(Automatic Properties)

不妨称自动属性之前的属性为传统属性。自动属性简化了语法,但也失掉了属性设置获取时进行操作的功能,也无法设置初始值。
若想只读或只写可在setget前加上访问修饰符,设置的访问修饰符必须比属性本身的可访问性低,并且不能同时设置getset的访问修饰符;internalprotected存在交集,因此不能同时设置属性和get或set分别为internalprotected
下面的代码演示自动属性和传统属性:
public class Test1
{
    
public int Int0 { get; set; }
    
public int Int1 { private get; set; }
    
private int int2;
    
private int int3;

    
public int Int2
    {
        
get { return int2; }
        
set { int2 = value; }
    }

    
public int Int3
    {
        
get { return int3; }
    }
}
编译后用ILDasm查看如图:


如自动属性Int1生成了私有字段"<Int1>k__BackingField",尽管在C#里这不是合法的标识符,但在IL里是合法的。
生成了两个方法:set_Int1(int32 'value')和get_Int1(),由于上面Int1的set设置器声明为私有的,因此set_Int1(int32 'value')为私有方法。
而相对应的传统的属性如果编码只有set或get,则get_XXX或set_XXX方法是不存在的。
如果在代码里写入具有相同签名的get_XXX或set_XXX方法编译会出错:"Type 'CS30NEW.Test1' already reserves a member called 'get_Int2' with the same parameter types"
属性在IL用方法来实现的目的是跨语言,有些语言不支持属性,可以使用相应的方法来实现相同的操作。IL生成的get_XXX或set_XXX尽管是公开的,但C#不允许显式访问这些方法,如果访问这些方法编译会出错:"'CS30NEW.Test1.Int2.get': cannot explicitly call operator or accessor"。

隐含类型局部变量(Local Variable Type Inference)

如定义一个局部整型数组时:
int[] a = new int[10];
赋值符号后面已经知道是数组类型了赋值符号前面还要指明是数组类型,是不是显得特别傻。
隐含类型可以这样定义:
var a = new int[10];
a.GetType().ToString()输出可以看到a为System.Int32[]类型。
貌似跟javascript的var很像,其实差别很大。javascript为弱类型语言,定义变量后可以对其任意赋值,而C#中用var定义局部变量有几点注意事项:
顾名思义,var只能定义局部变量。
var声明的同时必须初始化。
初始化时变量的类型已经确定,如果对其赋值其它类型编译会出错,提示类型不匹配。
var变量不能用于方法的形参。
在编译时变量的类型已经确定,这也是编译器做的工作。

对象初始化器(Object Initializers)

如有以下对象: 
public class Test1
{
    
public string String1 { get; set; }
    
public int Int1;

    
public Test1() { }
    
public Test1(int i) { Int1 = i; }
}
构造实例对其初始化。
var t0 = new Test1 { String1 = "http://xianfen.net", Int1 = 0 };
var t1
= new Test1(0) { String1 = "http://xianfen.net" };
IL内部对其初始化首先调用无参构造方法,然后调用的是属性的set或设置公共字段来实现的。
因此使用省略构造方法的对象初始化器是必须含有无参构造方法。
对象初始化器也可以显式调用构造方法,执行顺序是构造方法优先执行。
集合初始化器(Collection Initializers)

集合初始化器可以初始化List<T>等集合。
如下示例:
public class Test1
{
    
public string String1 { get; set; }
    
public int Int1 { get; set; }
}

List
<Test1> l = new List<Test1>
{
    
new Test1{ String1 = "http://xianfen.net", Int1 = 0 },
    
new Test1{ String1 = "http://www.xianfen.net", Int1 = 1 },
    
new Test1{ String1 = "http://zxjay.cn", Int1 = 2 }
};

IL代码:
  .locals init ([0] class [mscorlib]System.Collections.Generic.List`1<class CS30NEW.Test1> l,
           [
1] class [mscorlib]System.Collections.Generic.List`1<class CS30NEW.Test1> '<>g__initLocal0',
           [
2] class CS30NEW.Test1 '<>g__initLocal1',
           [
3] class CS30NEW.Test1 '<>g__initLocal2',
           [
4] class CS30NEW.Test1 '<>g__initLocal3')

从IL代码可以看出,编译器生成了局部变量,然后添加到集合中。

匿名类型(Anonymous Types)

匿名类型是最有争议的特性,说白了就像鸡肋,因为使用匿名类型有很多限制。
示例:
var t3 = new { String1 = "http://xianfen.net", Int1 = 0 }; 
其类型为:
t3.GetType().ToString()结果是:<>f__AnonymousType0`2[System.String,System.Int32]
检查IL可看到:


编译器生成了一些方法、属性、字段。
其类型不可见(使用反射可使其类型可见),使用受到诸多限制。
如果改变初始化字段的顺序,将生成新的类型
如:
var t4 = new { Int1 = 0, String1 = "http://xianfen.net" };
则t4.GetType().ToString()结果是:<>f__AnonymousType1`2[System.Int32,System.String]
当然改变属性类型、数量肯定会生成新的类型。
但设置相同属性但不同的值生成的类型相同。
var t3 = new { String1 = "http://xianfen.net", Int1 = 0 };
var t4
= new { String1 = "http://www.xianfen.net", Int1 = 10 };
t3.GetType()==t4.GetType()值为true
匿名类型的示例在声明周期中的Hash值是稳定不变的,也就是示例初始化后其内容不会改变。
如果试图改变其值,编译会出错:"Property or indexer 'AnonymousType#1.String1' cannot be assigned to -- it is read only"。
匿名类型不可见,那么如何使用方法返回匿名类型?好在.NET 2.0以后版本提供了泛型机制。
定义泛型方法:
public static T ReturnT<T>(Func<T> getType)
{
    
return getType();
}
调用方法:
var t3 = ReturnT(delegate { return new { String1 = "http://xianfen.net", Int1 = 0 }; });
t3.GetType().ToString()结果为:<>f__AnonymousType0`2[System.String,System.Int32]
貌似生成的类型有一定的规律,格式为:"<>f__AnonymousType匿名类型的序号`匿名类型属性或字段或构造方法参数的数目",但无法精确确定接下来生成的匿名类型的名称。

Lambda表达式(Lambda Expressions)

Lambda表达式应该是匿名方法的升级,更灵活,在接受委托的地方使用及其方便。
语法为:
(参数签名列表)=>{操作}
等价于方法:
[其他修饰] 方法名(方法参数签名列表){方法实现}
Func<string> f = () => { return "abc"; };
Console.Write(f());
输出:abc
Func<[...,]T> 最后一个参数为返回值类型,其他的为方法参数类型,相当于:T function([...]){return T的实例;}
Action<[T,...]> T为参数类型,相当于void function([T,...]){}
Predicate<[T,...]> T为参数类型,相当于 bool function([T,...]){return true或false;}
以上是系统定义的泛型委托,这些委托跟Lambda表达式结合,灵活的实现各种功能。
上面的代码检查IL可看到生成了以下方法:
.method private hidebysig static string  '<Main>b__0'() cil managed
{
  
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )
  
// Code size       11 (0xb)
  .maxstack  1
  
.locals init ([0] string CS$1$0000)
  
IL_0000:  nop
  
IL_0001:  ldstr      "abc"
  
IL_0006:  stloc.0
  
IL_0007:  br.s       IL_0009
  
IL_0009:  ldloc.0
  
IL_000a:  ret
}
// end of method Program::'<Main>b__0'

扩展方法(Extension Methods)

有点类似于JavaScript的prototype。
在不改变原类型的基础上扩展原类型。
当扩展方法的方法签名与原类型的方法冲突时,原类型自身方法优先。
扩展方法不能访问原类型的受保护的字段等等。
扩展方法可以对类及接口进行扩展。
.NET 3.X基础代码仍然是.NET 2.0的代码,用扩展方法对其扩展了很多功能,如对List<T>的扩展增加了很多功能。
示例:
public class Test1
{
    
public string String1 { set; get; }
    
public int Int1 { set; get; }
}

public static class Test2
{
    
/*
     * 实现扩展方法的类及扩展的方法都必须是静态的,待扩展的类作为参数,前面加this关键字
    
*/
    
public static string AddDefaultPage(this Test1 t)
    {
        
return t.String1 + "/Default.aspx";
    }
}

var t4
= new Test1 { String1 = "http://xianfen.net", Int1 = 0 };
t4.AddDefaultPage();

IL对扩展方法的声明及调用
.method public hidebysig static string  AddDefaultPage(class CS30NEW.Test1 t) cil managed
call string CS30NEW.Test2::AddDefaultPage(class CS30NEW.Test1)

查询语法(Query Syntax)

查询语法是Linq的核心,后面详细介绍。
这些新特性对CLR及IL相对与.NET2.0没有任何改变,编译器替开发人员做了相当多的工作,大大减少了编码量,以便使开发人员专注与逻辑的实现,难怪安装了.NET 3.X,运行Visual studio 2008命令提示行的clrver命令,显示的是2.0.XXX。