现在的位置: 首页 > 综合 > 正文

C#3.0新特性(三)-详说自动属性和匿名类型

2013年03月06日 ⁄ 综合 ⁄ 共 4060字 ⁄ 字号 评论关闭

一、自动实现的属性

  全称应该叫自动实现的属性(Auto-implemented properties),在上一篇中,给了简单的例子,说的是当属性访问器中不需要其他逻辑时,自动实现的属性可使属性声明变得更加简洁。如前边的例子,在C# 2.0中:

private int m_one;
public int One
{
  get { return m_one; }
  set { m_one = value; }
}

这个属性只有存(set)取(get)逻辑,没有其它诸如动态分配、或是按条件存取的逻辑,在C# 3.0中,完全可以写成:

public int One { get; set; }

从而不需要创建与该属性对应的私有变量。
  自动实现的属性必须同时声明get和set访问器。若要创建readonly自动实现属性,请给予它private set访问器。如:

public string Name { get; private set; }

这样,这个属性只读,不能对其进行存操作。那有人肯定要问,要给Name赋值,怎么办。仔细理解这个private set的含义,在面向对象的概念中,private表示在自己类的内部还是可以访问的,也就是,这里说的只读属性,在定义这个属性的类本身中,还是可以访问的,如下:

public class EDClass
{
  public string Name { get; private set; }
  public EDClass()
  {
    Name = "qq";
  }
  public void setName()
  {
    Name = "qq";
  }
}

这样是完全没有问题的,但是,如果通过obj.Name = value肯定不行的,要不然就不叫readonly了。例如:

EDClass cls = new EDClass();
cls.Name = "qq";

报错:The property or indexer 'CSharpStudy.EDClass.Name' cannot be used in this context because the set accessor is inaccessible
  还有一点需要说明,自动实现的属性(Property) 不允许具有属性 (Attribute)。如果您必须在属性(Property) 的后备字段上使用属性(Attribute),则应该只创建常规属性(Property)。好绕口,仔细分析这些概念,在面向对象的概念中,类的成员(member)又称为属性(Attribute),其实,在一般来讲,在C++中称为成员(Member)变量,在C#中称为属性(Attribute),或字段。而这里的自动属性用的是Property,试想,定义了一个自动属性:

public string Name { get; set; }

又想定义一个私有成员(属性,Attribute)与之关联:

private string _name;

不会报错,但是肯定关联不了,它只被认为是类的一个私有成员变量。

二、匿名类型
  在第一篇中,给了简单的匿名类型的例子,这里再详细说。
  匿名类型提供了一种方便的方法,可用来将一组只读属性封装到单个对象中,而无需首先显式定义一个类型。
  类型名由编译器生成,并且不能在源代码级使用。
  这些属性的类型由编译器推断。如:
  匿名类型通常用在查询表达式的select子句中,以便返回源序列中每个对象的属性子集。
  匿名类型是使用new 运算符和对象初始值设定项(初始化器)创建的。
  匿名类型是由一个或多个公共只读属性组成的类类型。不允许包含其他种类的类成员(如方法或事件)。如:

var noname = new { name = "yyao", age = 24 };//匿名类型

也就是说,匿名类型由编译器来推断出一个严格的类型,编译器是可以对其类型进行判定的,只是其类型的名字不由编程者所知而已。如,有如下定义:

var noname = new { namea = "yyao", age = 24 };//匿名类型
var qname = new { namea = "selfcherish", age = 25 };
var gname = new { username = "selfcherish", age = 25 };
qname = noname;
gname = qname;

这三个定义,第一个赋值没有任何问题,说明编译器自动判断了它们两者的类型是相同的,从而可以赋值。而第二个赋值,则报错:Cannot implicitly convert type 'AnonymousType#1' to 'AnonymousType#2'。定义匿名类型的时候,还需要注意,不能用null赋初值。如:

var vara = new { hei = null, id = 5 };

将报错:Cannot assign <null> to anonymous type property。另外,匿名类型是由一个或多个只读属性组成的类类型,如,gname可以通过gname.username和gname.age来访问,但是想通过:

gname.age = 26;

则报错:Property or indexer 'AnonymousType#1.age' cannot be assigned to -- it is read only。
  匿名类型,最常见的使用方式是用其他类型的一些属性初始化匿名类型:

MyClass[] ojbset = new MyClass[12];
var vaobj =
  from obj in ojbset
  select new { obj.Name, obj.Number };
foreach (var v in vaobj)
{
  Console.WriteLine("Name={0}, Number={1}", v.Name, v.Number);
}

这里的MyClass可能不只两个属性,只选了Name和Number这两个属性来初始化vaobj。另外,在将匿名类型分配给变量时,必须使用var构造初始化该变量,因为匿名类型不能用某类型来定义,只能用var,也就是说,只有编译器能够访问匿名类型的基础名称,编译器通过var来访问匿名类型的基础变量vaobj。

三、匿名类型的解析:
  匿名类型的基础是对象初始化器,匿名类型从对象初始化器(object initializer)自动推断和生成的元组类型。下面我们来看看匿名类型到底怎么生成的和我们原来的定义方式有什么区别:

var noname = new { namea = "dd", age = 24 };//匿名类型
var qname = new { namea = "ff", age = 25 };

第一句,给noname赋了一个匿名类型,在编译时,编译器使用对象初始化器推断的属性来创建见一个新的匿名类型,该类型拥有aname啊和age的属性,在运行时,会创建新类型的一个实例同时namea和age属性将会被设置为对象初始化器中指定的值“dd”、24;和上面几节里描述的一样这里大家一定会想到,肯定又是在编译器里封装了一些处理;确实是这样,下面这段代码描述编译器针对匿名类型语句具体做了哪些工作:

class __Anonymous1
{
  private string name;
  private int age;
  public string Name { get { return name; } set { name = value; } }
  public int Age { get { return age; } set { age = value; } }
}
__Anonymous1 noname = new __Anonymous1();
noname.Name="dd";
noname.Age=24;

这段代码就是我们非常熟悉的写法,编译器就是在后台依据匿名类型解析类型,创建新类,初始化对象;如果你创建了多个相似的匿名类型,C#编译器会聪明的发现这一点,只生成一个类和它的多个实例;
  上边说到,这两句中,noname和qname的类型相同,可以通过Visual Studio 2008的工具ILDasm来验证。这个工具能对一个程序或者它的类库执行反汇编处理,显示由C#编译器生成的CIL代码,通过反汇编可以列出封装在程序集中的类型信息。为此,先给出完整的类代码:

public class EDClass
{
  public string Name { get; private set; }
  public EDClass()
  {
    Name = "qq1";
  }
  public void setName()
  {
    Name = "qq";
    var noname = new { namea = "dd", age = 24 };//匿名类型
    var qname = new { namea = "ff", age = 25 };
    //var vara = new { hei = null, id = 5 };
    if (qname == noname)
    {
      //!=,所以不能输出
      Console.WriteLine("qname == noname");
    }
    if (qname.Equals(noname))
    {
      //Equal,能够输出
      Console.WriteLine("qname equal noname");
    }
    if (qname.GetHashCode() == noname.GetHashCode())
    {
      //能够输出
      Console.WriteLine("same hashcode");
    }
  }
}

当然,需要添加运行该代码的主函数。这里省略。
  在Visual Studio 2008的Tools菜单下,选择ILDasm,即可打开这个工具:然后在这个工具的File菜单下点Open,选择刚刚运行的程序,并打开。如图:

然后,先点击View菜单下的Show source lines菜单项,目的是,反汇编后,可以在看CIL代码的同时,查看相应的源代码。然后,双击上图中的setName方法,得到其反汇编后的详细信息,双击后,关于noname和qname,可以得到如下代码段:

抱歉!评论已关闭.