CLR类型设计之属性

          在之前的随笔中,我们探讨了参数,字段,方法,我们在开始属性之前回顾一下,之前的探讨实际上串联起来就是OOP编程的思想,在接下来的文章中,我们还会讨论接口(就是行为),举个例子:我们如果要做一个学生档案,我们需要先抽象出来有那些对象实体,比如有一个学生类,里面有学生id,姓名,年龄,班级等字段。 但是这不能满足我们的需求,我们要做学生档案管理,需要知道学生的每科成绩,所以我们还需要一个成绩类,里面定义了学生的学生Id,科目,科目分数,下面是两个类的代码示例

public sealed  class Student
    {
        //学员id
        public int StudentId;
        //姓名
        public string Name;
        //年龄
        public int Age;
        //班级名
        public string classname;
    }
    public sealed class Score {
        //学员id
        public int StudentId;
        //科目
        public string SubjectName;
        //成绩
        public int Achievement;
    }
View Code

       有了这两个类以后,我们就可以创建一个获取学员成绩的方法,方法的代码示例就不写了,OOP的思想最重要的在于尽可能的模块化可复用,当然为了实现这些,还有继承,多态等去实现目的,但是在继续实现过程中你可能会发现一些问题,当我们需要使用上面类型的时候,可以通过实例化直接使用,如下:

     Student stu = new Student();
            stu.Name = "苏云";
            stu.Age = 22;
            stu.classname = "fantasy";
View Code

      但是如果我输入一个Age为-15,程序也可以通过,字段值就会被改变为-15,年龄是不存在负数的,所以这个值是不应该通过的,这就是今天的主题,属性设置,面向对象一条很重要的原则就是数据封装,意味类型字段永远不应该公开,否则很容易因为不恰当使用而破坏对象的状态,如上文我们输入的负值,当然还有一些其他原因,比如线程安全,字段为逻辑字段,其值存在于内存中的字节,通过某个算法获取得到。但是这样做就会导致一个问题,外部方法想要访问时,由于内部不公开,所以外部无法访问

      CLR中提供了属性机制,我们完全可以不用担心上面的问题,我们改写一下例子一的代码示例,在实例化学生的时候,如果Age<1,就会抛出异常,可以看到是那个参数报出的异常,值是多少。

  public sealed  class Student
    {
        private int studentId;
        private string name;
        private int age;
        private string classname;
        //学员id
        public int StudentId { get { return (studentId); }set { studentId = value; } }
        //姓名
        public string Name{ get { return (name); }set { name = value; } }
        //年龄
        public int Age
        {
            get { return (age); }
            set
            {
                if (value<1)  throw new ArgumentOutOfRangeException("value", value.ToString(),"学生的年龄不能小于1岁");
                age = value;
                 }
        }
        //班级名
        public string Classname { get { return (classname); }set { classname = value; } }
    }
View Code

    属性机制使用起来很简单,每个属性都有名称和类型(类型不能为void),并且一个类中同一个字段名称只能出现一次,只需要get,set两个关键字,如果只有get那就是只读字段,只有set是只写字段。也可以在get上写计算方法获取到值,但是上述方法写起来是否觉得很麻烦?C#还支持自动属性实现,我们改写成绩类,示例代码如下,

  public sealed class Score {
        //学员id
        public int StudentId { get; set; }
        //科目
        public string SubjectName { get; set; }
        //成绩
        public int Achievement { get; set; }
    }
View Code

   在C#中声明get;set但是却未提供对应方法,C#会自动声明一个私有字段,这样就可以很快定义一个属性,和写字段是一样的,但需要注意的是,属性不能作为out或ref参数传给方法,而字段可以

     对象和初始化器

      在之前的代码中,我们初始化学生类需要分两步,第一实例化,第二赋值,但实际上我们可以使用更简单的语法,对象初始化器初始化一个对象,只需要像下面这样一句话就可以初始化一个对象并且赋值,他做的事情和例子2是相同的。在集合中也可以使用初始化器初始化集合。

 

重点: 如果类没有无参的构造函数就会出现编译时错误

 

Student stu1 = new Student() {
                Name="admain",Age=15,Classname="fantasy"
            };
View Code

    我们提到集合也可以用初始化器的方法初始化,但是集合的初始化和对象并不一样,首先要求对象或字段继承了IEnummerable<T>接口,我们示例常见的Dictionary集合如何初始化

1 Dictionary<int, string> dic = new Dictionary<int, string> {
2                 { 1,"张三"}, { 2,"李四"}
3             };
4             //等价于
5             dic.Add(1, "张三");
6             dic.Add(1, "李四");
View Code

     有参属性:索引器

     一个属性的get访问器方法不接收参数,则称为无参属性,用起来就和访问字段一样,除了这些与字段相似的属性,还有一种有参属性,C#里称其为索引器,下文中所有有参属性都用索引器替代,C#使用数组风格的语法来公开索引器,看下面的示例:

 1 class MyListBox
 2 {
 3     public ArrayList data = new ArrayList();
 4     public object this[int idx]  //this作索引器名称,idx是索引参数
 5     {
 6         get
 7         {
 8             if (idx > -1 && idx < data.Count)
 9             {
10                 return data[idx];
11             }
12             else
13             {
14                 return null;
15             }
16         }
17         set
18         {
19             if (idx > -1 && idx < data.Count)
20             {
21                 data[idx] = value;
22             }
23             else if (idx <= data.Count)
24             {
25                 data.Add(value);
26             }
27             else
28             {
29                 throw new ArgumentOutOfRangeException("idx", idx, "超出数组索引范围");
30             }
31         }
32     }
33 }
View Code

      我们定义了一个类MyListBox,其中有一个ArrayList字段,在构造器中为其默认初始化了,在下面的代码中我们看到了如何声明一个索引器,我们返回的类型是object,索引器的返回类型一样不可以void,c#使用this[...]表达索引器,并且C#不支持静态索引器,尽管CLR支持静态有参属性,C#允许一个类型定义多个索引器,但是索引器参数集不能相同,其他一些语言中支持定义多个相同签名的索引器,因为其他一些语言中索引器可以自定命名,但是C#不允许这样做,因为C#中不允许开发人员指定索引器名称,C#为一个类型中的所有索引器都默认提供了一个叫做Item的名称,所以在C#中使用索引器只能通过不同签名来区分选择的索引器。

       CLR并不区分有参属性和无参属性,对CLR来说,每个属性都只是类型中定义的一对方法,和一些元数据。下面的示例是如何调用索引器。使用起来也很简单吧

 1      //初始化MyListBox
 2             MyListBox ba = new MyListBox {
 3                 //集合初始化器初始化值
 4                 data = { "张三",20,30,40},
 5             };
 6             //调用添加方法为其添加值
 7             ba.data.Add("5");
 8             ba.data.Add(6);
 9             for (int i = 0; i < ba.data.Count; i++)
10             {
11                 //使用索引器打印出指定值,具体实现请查看类中get方法
12                 Console.WriteLine(ba.data[i]);
13             }
View Code

      无参属性,初始化器,有参属性,有了这些你可以在你的方法中更好的使用字段,并且让你的数据封装更加安全,但是CLR作者本人却持有另外一种观点,作者觉得属性不如封装的方法。有兴趣的朋友可以自己翻阅CLR看看作者的观点。

 

posted @ 2017-07-30 10:29  苏云  阅读(337)  评论(2编辑  收藏  举报