第二部分 设计类型:第10章 属性

属性无参属性 + 有参属性(索引器)

10.1 无参属性
强烈建议所有字段都设为private。通过属性的方式来封装字段,可对数据进行非法值判断。
封装了字段访问的方法称为访问器(accessor)方法。

可将属性理解为智能字段,背后有额外逻辑的字段。属性通过get和set方法可以设置读写。
私有字段通常称为支持字段(backing field)。

属性编译后,编译器在最后的托管程序集中生成以下两项或三项:
•代表属性的get访问器的一个方法。仅在属性定义了get访问器时生成。
•代表属性的set访问器的一个方法。仅在属性定义了set访问器方法时生成。
•托管程序集元数据中的一个属性定义。这一项一定生成。

编译器编译代码发现对字段进行访问和设置的时候,会自动生成get_或set_为前缀的访问器方法,生成对访问器的调用。

10.1.1 自动实现的属性
自动实现的属性(Automatially Implemented Property,后面称AIP)。仅仅为了封装支持字段。必须是可读可写的才可使用。
public string Name{get;set;}  //仅这样写,编译器会自动声明一个私有字段,并自动实现get_Name和set_Name方法。

任何想要序列化或反序列化的类中,都不要使用自动属性功能。因为AIP自动生成的支持字段名称不固定。

 

10.1.2 合理定义属性
属性本质上是方法。
如果需要线程同步,就不应使用属性。从MashalByRefObject派生的类永远都不应该使用属性。
属性和普通方法相比,对性能损耗更大,还会妨碍对代码的理解。

10.1.3 对象和集合初始化器
对象初始化器:
Employee e = new Employee(){Name = "Jeff", Age = 45}; //构造对象、调用无参构造器、设置属性
等价于以下代码:

Emploee e = new Employee();
e.Name = "Jeff";
e.Age = 45;

对象初始化器的好处:
允许在表达式的上下文中编码,允许组合多个函数,增强代码的可读性。如:
String s = new Employee(){Name="Jeff",Age=45}.ToString().ToUpper();

补充:
如果调用的是无参构造器,大括号可省略,可简写为:
string s = new Employee {Name="Jeff",Age=45}.ToString().ToUpper();

如果一个属性的类型实现了IEnumerable或IEnumerable<T>接口,属性就是一个集合。
集合的初始化时一种相加操作,而非替换操作。

集合初始化器:

public sealed class Classroom
{
private List<String> m_students = new List<String>();
public List<String> Students {get{ return m_students;}}

public Classroom(){}
}

构造一个Classroom对象,并像下面这样初始化Students集合:

public static void M()
{
 Classroom classroom = new Classroom{
 Students ={"Jeff","Kristin","Aidan","Grant"}
 }
}

Students ={"Jeff","Kristin","Aidan","Grant"}  //Students为List<String>类型
编译时,编译器发现Students属性的类型是List<String>类型,而这个类型实现了IEnumerable<String>接口。编译器就假定List<String>类型提供了一个名为Add的方法。然后编译器生成代码来调用集合的Add方法,上述代码编译转化为:

public static void M()
{
Classroom classrom = new Classroom();
classroom.Students.Add("Jeff");
classroom.Students.Add("Kristin");
classroom.Students.Add("Aidan");
classroom.Students.Add("Grant");
}


如果属性的类型实现了IEnumerable或IEnumerable<T>,但未提供Add方法,编译器报错:
error CS0117:"System.Collections.Generic.IEnumerable<string>"不包含"Add"的定义。

有些集合的Add方法要获取多个实参,比如Dictionary的Add方法:
public void Add(TKey key,TValue value);


通过在集合初始化器中嵌套大括号的方式,可向Add方法传递多个实参,如下所示:

var table = new Dictionary<String,Int32>{
  {"Jeffrey",1},{"Kristin",2},{"Aidan",3},{"Grant",4}
}

以上代码等价于下述代码:

var table = new Dictionary<String,Int32>();
table.Add("Jeffrey",1);
table.Add("Kristin",2);
table.Add("Aidan",3);
table.Add("Grant",4);

 

 10.1.4 匿名类型
C#利用匿名可以声明一个不可变的元组类型。元组类型是含有一组属性的类型,这些属性通常以某种方式相互关联。 //元组英文tuple 是对顺序的一个抽象:single,double,triple,quadruple,quintuple

//定义一个类型,构造他的一个实例,并初始化它的属性
var o1 = new {Name="Jeff",Year=1964};  
var利用C#的隐式类型局部变量功能。
//在控制台上显示属性:
Console.WriteLine("Name={0},Year={1}",o1.Name,o1.Year);

var o = new {propety1= expression1,...,propertyN=expressionN};
编译器会推断每个表达式的类型,创建推断类型的私有字段,为每个字段创建公共只读属性,并创建一个构造器来接收所有这些表达式。在构造器的代码中,会用传给它的表达式的求值结果来初始化私有只读字段。
另外,编译器还会重写Object的Equals,GetHashCode和ToString方法,并生成所有这些方法中的代码。
最终看起来像这样:

 

10.1.5 System.Tuple类型
Tuple组元一般用于方法返回多个类型数据,就可以不用out,ref输出参数了。组元是C#4.0引入的新特性,需要.NEF Framework4.0及以上版本。

简单例子:

public class Point
{
   public int X { get; set; }
   public int Y { get; set; }
 }
Point p = new Point() { X = 10, Y = 20 };
//use the predefine generic tuple type.
Tuple<int, int> p2 = new Tuple<int, int>(10, 20);

Console.WriteLine(p.X + p.Y);
Console.WriteLine(p2.Item1 + p2.Item2);

一个简单的包含两个Int类型成员的类,传统的方法定义point需要写很多代码,但是使用tuple却只有一句。

感觉比较像ArrayList:可以放不同类型的数据到集合中

ArrayList list = new ArrayList();
list.Add(3);
list.Add("Hello World");
Response.Write(list[0]+list[1].ToString());

 


 

 

复杂一些的:

Tuple<int> test = new Tuple<int>(1);
Tuple<int, int> test2 = Tuple.Create<int, int>(1,2);
Console.WriteLine(test.Item1);
Console.WriteLine(test2.Item1 + test2.Item2);

Tuple<int, int, int, int, int, int, int, Tuple<int>> test3 = new Tuple<int, int, int, int, int, int, int, Tuple<int>>(1, 2, 3, 4, 5, 6, 7, new Tuple<int>(8));
Console.WriteLine(test3.Item1 + test3.Item2 + test3.Item3 + test3.Item4 + test3.Item5 + test3.Item6 + test3.Item7 + test3.Rest.Item1);

第一个定义包含一个成员。

第二个定义包含两个成员,并且使用create方法初始化。

第三个定义展示了tuple最多支持8个成员,如果多于8个就需要进行嵌套。注意第8个成员很特殊,如果有8个成员,第8个必须嵌套定义tuple。

 

嵌套定义的例子:

Tuple<int, Tuple<int>> test4 = new Tuple<int, Tuple<int>>(1, new Tuple<int>(2));
Console.WriteLine(test4.Item1 + test4.Item2.Item1);

Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>> test5 = new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int>(8, 9, 10));

Console.WriteLine(test5.Item1 + test5.Item2 + test5.Item3 + test5.Item4 + test5.Item5 + test5.Item6 + test5.Item7 + test5.Rest.Item1 + test5.Rest.Item2 + test5.Rest.Item3);

 

10.2 有参属性

有参属性,是指属性中的get访问器方法接受一个或多个参数,set接受两个或多个参数。C#称有参属性为“索引器”。 理解:无参属性是设置单一数据的。索引器是设置数组字段的。

简单索引器例子:

public class IndexerClass
{
    private string[] name = new string[2];
    
    //索引器必须以this关键字定义,其实这个this就是类实例化之后的对象
    public string this[int index]
    {
        get //实现索引器的get方法
        {
            if (index < 2)
            {
                return name[index];
            }
            return null;
        }

        set //实现索引器的set方法
        {
            if (index < 2)
            {
                name[index] = value;
            }
        }
    }
}
public class Test { static void Main() { //索引器的使用 IndexerClass Indexer = new IndexerClass(); Indexer[0] = "张三"; //“=”号右边对索引器赋值,其实就是调用其set方法 Indexer[1] = "李四";//输出索引器的值,其实就是调用其get方法 Console.WriteLine(Indexer[0]); Console.WriteLine(Indexer[1]); } }

 以字符串作为下标,对索引器进行存取:

public class IndexerClass
{
    //用string作为索引器下标的时候,要用Hashtable
    private Hashtable name = new Hashtable();

    //索引器必须以this关键字定义,其实这个this就是类实例化之后的对象
    public string this[string index]
    {
        get { return name[index].ToString(); 
        set { name.Add(index, value); }
    }
}
public class Test
{
    static void Main()
    {
        IndexerClass Indexer = new IndexerClass();
        Indexer["A0001"] = "张三";
        Indexer["A0002"] = "李四";
        Console.WriteLine(Indexer["A0001"]);
        Console.WriteLine(Indexer["A0002"]);
    }
}
View Code

索引器的重载:

public class IndexerClass
{
    private Hashtable name = new Hashtable();

    //1:通过key存取Values
    public string this[int index]
    {
        get { return name[index].ToString(); }
        set { name.Add(index, value); }
    }

    //2:通过Values存取key
    public int this[string aName]
    {
        get
        {
            //Hashtable中实际存放的是DictionaryEntry(字典)类型,如果要遍历一个Hashtable,就需要使用到DictionaryEntry
            foreach(DictionaryEntry d in name)
            {
                if (d.Value.ToString() == aName)
                {
                    return Convert.ToInt32(d.Key);
                }
            }
            return -1;
        }
        set
        {
            name.Add(value, aName);
        }
    }
}
public class Test
{
    static void Main()
    {
        IndexerClass Indexer = new IndexerClass();

        //第一种索引器的使用
        Indexer[1] = "张三";//set访问器的使用
        Indexer[2] = "李四";
       Console.WriteLine("编号为1的名字:" + Indexer[1]);//get访问器的使用
        Console.WriteLine("编号为2的名字:" + Indexer[2]);

        Console.WriteLine();
        //第二种索引器的使用
        Console.WriteLine("张三的编号是:" + Indexer["张三"]);//get访问器的使用
        Console.WriteLine("李四的编号是:" + Indexer["李四"]);
       Indexer["王五"] = 3;//set访问器的使用
        Console.WriteLine("王五的编号是:" + Indexer["王五"]);
    }
}
View Code

多参索引器:

using System;
using System.Collections;

//入职信息类
public class EntrantInfo
{
    //姓名、编号、部门
    private string name;
    private int number;
    private string department;
    public EntrantInfo()
    {

    }
    public EntrantInfo(string name, int num, string department)
    {
        this.name = name;
        this.number = num;
        this.department = department;
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int Num
    {
        get { return number; }
        set { number = value; }
    }

    public string Department
    {
        get { return department; }
        set { department = value; }
    }
}

//声明一个类EntrantInfo的索引器
public class IndexerForEntrantInfo
{
    private ArrayList ArrLst;//用于存放EntrantInfo类
    public IndexerForEntrantInfo()
    {
        ArrLst = new ArrayList();
    }

    //声明一个索引器:以名字和编号查找存取部门信息
    public string this[string name, int num]
    {
        get
        {
            foreach (EntrantInfo en in ArrLst)
            {
                if (en.Name == name && en.Num == num)
                {
                    return en.Department;
                }
            }
            return null;
        }
        set
        {
            //new关键字:C#规定,实例化一个类或者调用类的构造函数时,必须使用new关键
            ArrLst.Add(new EntrantInfo(name, num, value));
        }
    }

    //声明一个索引器:以编号查找名字和部门
    public ArrayList this[int num]
    {
        get
        {
            ArrayList temp = new ArrayList();
            foreach (EntrantInfo en in ArrLst)
            {
                if (en.Num == num)
                {
                    temp.Add(en);
                }
            }
            return temp;
        }
    }

    //还可以声明多个版本的索引器...
}

public class Test
{
    static void Main()
    {
        IndexerForEntrantInfo Info = new IndexerForEntrantInfo();
        //this[string name, int num]的使用
        Info["张三", 101] = "人事部";
        Info["李四", 102] = "行政部";
        Console.WriteLine(Info["张三", 101]);
        Console.WriteLine(Info["李四", 102]);

        Console.WriteLine();

        //this[int num]的使用
        foreach (EntrantInfo en in Info[102])
        {
            Console.WriteLine(en.Name);
            Console.WriteLine(en.Department);
        }
    }
}
View Code

 

索引器与属性的比较:

索引器与属性都是类的成员,语法上非常相似。索引器一般用在自定义的集合类中,通过使用索引器来操作集合对象就如同使用数组一样简单;而属性可用于任何自定义类,它增强了类的字段成员的灵活性。

 

10.3 调用属性访问器方法时的性能


对于简单的get和set访问器方法,JIT编译器会将代码内联(inline)。这样使用属性就没有性能上的损失。内联是指将一个方法(或当前情况下的访问器方法)的代码直接编译到调用它的方法中。这便避免了再运行时发出调用所产生的开销,代价是编译好的方法的代码会变得更大。由于属性访问器方法通常只包含极少量代码,所以对它们进行内联,反而会使最终生成的代码变得更小,而且执行得更快。

注意,JIT编译器在调试代码时不会内联属性方法,因为内联的代码会变得难以调试。所以程序在发布版本中访问属性时的性能比较快,在调试版本中比较慢。字段访问无论调试或发布都很快。

 

10.4 属性访问器的可访问性
对get和set设置不同的可访问性:

public class SomeType{
  private string m_name;
  public string Name{
   get{return m_name;}
   protected set{m_name=value;} //只能设置比整体的public限制更大的可访问性
  }
}


10.5 泛型属性访问器方法
属性虽然本质上是方法,但属性还是不能引入自己的泛型类型参数,引文从概念上讲不通。属性本应该表示读写的对象特征。如果引入泛型参数,读写行为可能发生改变。
但从概念上说,属性是不应该具有行为的。要公开对象的行为,不管是不是泛型,应该定义方法而非属性。

 

posted @ 2014-05-13 16:50  IT浪潮之巅  阅读(290)  评论(0编辑  收藏  举报
   友情链接: 淘宝优惠券