• 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看看作者的观点。

  • 相关阅读:
    贝叶斯模型
    java的移位和异或运算
    windows下xgboost安装到python
    bagging and boosting
    SVM处理多分类问题
    GO语言语法入门
    [转自SA]浅谈nginx的工作原理和使用
    多线程编程-- part 9 信号量:Semaphore
    多线程编程-- part 8 CyclicBarrier
    多线程编程-- part 7 CountDownLatch
  • 原文地址:https://www.cnblogs.com/Demon-Su/p/7258436.html
Copyright © 2020-2023  润新知