首页 > 编程开发 > Objective-C编程 >
-
C#3.0新特性之详说自动属性和匿名类型
写在前边:
不知道怎么的,上一篇说放到首页看看,今天来看才发现没有放上去,做事情还真不细心,改、改、改!其实,早知道C# 4.0都有了,但是连C# 3.0都还没有弄清楚,使用还是坚持这个系列,做完再写C# 4.0吧。技术前辈们肯定早都知道这些东西了,不过呢,我也只是学习,只是把我的学习同大家分享而已!
一、自动实现的属性
全称应该叫自动实现的属性(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,可以得到如下代码段:
//000108: var noname = new { namea = "dd", age = 24 };//????????
IL_000d: ldstr "dd"
IL_0012: ldc.i4.s 24
IL_0014: newobj instance void class '<>f__AnonymousType0`2'<string,int32>::.ctor(!0,
!1)
IL_0019: stloc.0
//000109: var qname = new { namea = "ff", age = 25 };
IL_001a: ldstr "ff"
IL_001f: ldc.i4.s 25
IL_0021: newobj instance void class '<>f__AnonymousType0`2'<string,int32>::.ctor(!0,
!1)
IL_0026: stloc.1
可以看出,noname和qname都是类类型<>f__AnonymousType0`2,所以,它们可以相互赋值。再看,上图中,最下方恰好有一项<>f__AnonymousType0`2,点击前边的加号,就可以看到其详细信息。如图:
另外,使用ILDasm这个工具,也可以查看自动属性的信息,如这个类中的自动属性Name,在第一个图中,有这样一项:<Name>k_BackingField:private string,也即是说,编译器通过'<Name>k__BackingField'的方式对Name(和与之相关的私有变量)进行了声明。
三、匿名类型的总结
匿名类型是直接从对象派生的引用类型。尽管应用程序无法访问匿名类型,但编译器仍会为其提供一个名称。
如果两个或更多个匿名类型以相同的顺序具有相同数量和种类的属性,则编译器会将这些匿名类型视为相同的类型,并且它们共享编译器生成的相同类型信息。如上边赋值的那个例子。
匿名类型具有方法范围。也就是说,声明或是定义的匿名类型,只能在声明或定义它的那个方法中使用,实际上,想定义匿名成员类型,根本是做不到的,如:private var ame;private var hehe = new { namea = "yyao", age = 24 };想通过这样的方式,将他们定义为某类的成员,则报相同的错:The contextual keyword 'var' may only appear within a local variable declaration。
匿名类型不能像属性一样包含不安全类型。
由于匿名类型上的Equals 和 GetHashCode 方法是根据属性的Equals 和GetHashcode 定义的,因此仅当同一匿名类型的两个实例的所有属性都相等时,这两个实例才相等。如:
var noname = new { namea = "yyao", age = 24 };//匿名类型
var qname = new { namea = "yyao", age = 24 };
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");
}
四、总结
今天的很多东西,在第一篇和第二篇都已经用到过,今天再详细的讲讲其原理和内部机制,加深印象,只是,学习中,过得太少了,以后,时时来看看自己的随笔吧,也不失为复习的好方法。
相关阅读:
C#3.0新特性(一)-隐式类型的局部变量和数组
C#3.0新特性(二)-初始值设定项
C#3.0新特性(三)-详说自动属性和匿名类型
C#3.0新特性――隐含类型局部变量和匿名类型
C#3.0新特性――自动生成属性
C#3.0新特性之隐式类型局部变量的使用
C# 3.0中新匿名类型特性初体验
这样是完全没有问题的,但是,如果通过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 运算符和对象初始值设定项(初始化器)创建的。
匿名类型是由一个或多个公共只读属性组成的类类型。不允许包含其他种类的类成员(如方法或事件)。如: