字段

什么是字段

  • 字段(field)是一种表示与对象或类型(类与结构体)关联的变量
  • 字段是类型的成员,旧称”成员变量”
  • 与对象关联的字段亦称”实例字段”
  • 与类型关联的字段称为”静态字段”,由static修饰

字段的声明

  • 参考C#语言定义文档
  • 尽管字段声明带有分号,但它不是语句
  • 字段的名字一定是名词
  • 示例: ```csharp using System; using System.Collections.Generic;

namespace FieldExample { class Program { static void Main(string[] args) { Student stu1 = new Student(); stu1.Age = 40; stu1.Score = 90; //通过实例的两个字段,能表示当前实例的一个状态,年龄40,分数较高,说明学习辛苦,很努力

  1. Student stu2 = new Student();
  2. stu2.Age = 20;
  3. stu2.Score = 60;
  4. List<Student> stuList = new List<Student>();
  5. for (int i = 0; i < 100; i++)
  6. {
  7. Student stu = new Student();//每次new Student()时,Student的Amount会加1
  8. stu.Age = 24;
  9. stu.Score = i;
  10. stuList.Add(stu);
  11. }
  12. int totalAge = 0;
  13. int totalScore = 0;
  14. foreach (var stu in stuList)
  15. {
  16. totalAge += stu.Age;
  17. totalScore += stu.Score;
  18. }
  19. //静态字段使用
  20. Student.AverageAge = totalAge / Student.Amount;
  21. Student.AverageScore = totalScore / Student.Amount;
  22. //静态方法调用
  23. Student.ReportAmount();
  24. Student.ReportAverageAge();
  25. Student.ReportAverageScore();
  26. }
  27. }
  28. class Student
  29. {
  30. //实例字段,可以表示对象当前的状态
  31. public int Age;
  32. public int Score;
  33. //静态字段,可以表示类型当前的状态
  34. public static int AverageAge;
  35. public static int AverageScore;
  36. public static int Amount;
  37. //构造函数
  38. public Student()
  39. {
  40. Student.Amount++;
  41. }
  42. //静态构造器,只有程序加载时运行,且只执行一次
  43. static Student()
  44. {
  45. Student.Amount = 0;
  46. }
  47. //静态方法
  48. public static void ReportAmount()
  49. {
  50. Console.WriteLine(Student.Amount);
  51. }
  52. public static void ReportAverageAge()
  53. {
  54. Console.WriteLine(Student.AverageAge);
  55. }
  56. public static void ReportAverageScore()
  57. {
  58. Console.WriteLine(Student.AverageScore);
  59. }
  60. }

}


<a name="EgE7k"></a>
## 字段的初始值

- 无显式初始化时,字段获得其类型的默认值,所以字段"永远都不会未被初始化"
- 实例字段初始化的时机——对象创建时
- 静态字段初始化的时机——类型被加载(load)时

<a name="lLfl2"></a>
## 只读字段

- 实例只读字段
   - 实例只读字段的初始化,只能在实例构造器中进行或在声明时进行,且赋值后无法改变
   - 示例:
```csharp
using System;
using System.Collections.Generic;

namespace FieldExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student(1001);
            stu.Age = 25;
            stu.Score = 80;
            Console.WriteLine(stu.ID);//输出1001
        }
    }

    class Student
    {
        //实例只读字段
        public readonly int ID;

        //实例构造函数
        public Student(int id)
        {
            this.ID=id;
        }
    }
}
  • 静态只读字段
    • 静态只读字段的初始化,只能在静态构造器中进行或在声明时进行,且赋值后无法改变
    • 示例: ```csharp using System;

namespace FieldExample { class Program { static void Main(string[] args) { Console.WriteLine(Brush.DefalutColor.Red); Console.WriteLine(Brush.DefalutColor.Blue); Console.WriteLine(Brush.DefalutColor.Green); } }

struct Color
{
    public int Red;
    public int Green;
    public int Blue;
}
class Brush
{
    //静态只读字段
    public static readonly Color DefalutColor;
    //静态构造器
    static Brush()
    {
        Brush.DefalutColor = new Color() { Red = 0, Green = 0, Blue = 0 };
    }
}

}


<a name="hU85M"></a>
# 属性

<a name="zj9ZO"></a>
## 什么是属性

- 属性(property)是一种用于访问对象或类型的特征的成员,特征反映了状态
- 属性是字段的自然扩展
   - 从命名上看,filed更偏向于实例对象在内存中的布局,property更偏向于反映现实世界对象的特征
   - 对外:暴露数据,数据可以是存储在字段里的,也可以是动态计算出来的
   - 对内:保护字段不被非法值"污染"
- 属性由Get/Set方法对进化而来
- 又一个"语法糖"——属性背后的秘密,使用反编译器ildasm反编译一下,就可看到Get/Set的方法对
- 示例1:在java/c++中没有属性,是使用以下方法进行字段的保护的
```csharp
using System;

namespace PropertyExample
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Student stu1 = new Student();
                stu1.SetAge(20);

                Student stu2 = new Student();
                stu2.SetAge(20);

                Student stu3 = new Student();
                stu3.SetAge(200);//此处输错年龄,会触发异常

                int avgAge = (stu1.GetAge() + stu2.GetAge() + stu3.GetAge()) / 3;
                Console.WriteLine(avgAge);
            }
            catch (Exception ex)
            {

                Console.WriteLine(ex.Message); ;
            }

        }
    }

    class Student
    {
        private int age;

        public int GetAge()
        {
            return this.age;
        }

        public void SetAge(int value)
        {
            if (value >= 0 && value <= 120)
            {
                this.age = value;
            }
            else
            {
                throw new Exception("年龄值出错!");
            }
        }
    }
}
  • 示例2:用属性对以上代码进行改写 ```csharp using System;

namespace PropertyExample { class Program { static void Main(string[] args) { try { Student stu1 = new Student(); stu1.Age = 20;

            Student stu2 = new Student();
            stu2.Age = 20;

            Student stu3 = new Student();
            stu3.Age = 20;

            int avgAge = (stu1.Age + stu2.Age + stu3.Age) / 3;
            Console.WriteLine(avgAge);
        }
        catch (Exception ex)
        {

            Console.WriteLine(ex.Message); ;
        }

    }
}

class Student
{
    private int age;

    public int Age
    {
        get { return age; }
        set
        {
            if (value >= 0 && value <= 120)
            {
                age = value;
            }
            else
            {
                throw new Exception("年龄值出错!");
            }
        }
    }
}

}


<a name="g9bIB"></a>
## 属性的声明

- 运用VS代码提示声明属性:prop+两次tab,或propfull+两次tab
- 快速声明属性的另外一种方法:如果要把一个字段快速改造成一个属性,private int id;则只需把光标移到id处,然后按"ctrl+R,E"就能快速补齐属性。
- 完整声明——后台(back)成员变量与访问(注意使用code snippet和refactor工具)
   - 示例:静态属性声明完整
```csharp
using System;

namespace PropertyExample
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Student.Amount = -100;
                Console.WriteLine(Student.Amount);
            }
            catch (Exception ex)
            {

                Console.WriteLine(ex.Message); ;
            }

        }
    }

    class Student
    {
        //静态属性声明完整
        private static int amount;

        public static int Amount
        {
            get { return amount; }
            set
            {
                if (value >= 0 )
                {
                    Student.amount = value;
                }
                else
                {
                    throw new Exception("总数值出错!");
                }
            }
        }

    }
}
  • 简略声明——只有访问器(查看IL代码)
    • 功能上与一个公有的字段是完全一样的,值不受保护,有可能把错误值传给属性
    • 用途(好处):声明简单,代码短,一般用来传递数据用
    • 示例: ```csharp using System;

namespace PropertyExample { class Program { static void Main(string[] args) { Student stu = new Student(); stu.Age = 100;

    }
}

class Student
{
    //属性的简略声明
    public int Age { get; set; }
}

}


- 动态计算值的属性
   - 示例1:主动计算
```csharp
using System;

namespace PropertyExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student();
            stu.Age = 12;
            Console.WriteLine(stu.CanWork);
        }
    }

    class Student
    {

        //Age属性的完整声明
        private int age;

        public int Age
        {
            get { return age; }
            set { age = value; }
        }

        //CanWork只读属性并没有封装一个字段,它的值是根据age实时动态计算出来的
        //主动计算CanWork值,适用于Age经常被访问,而CanWork不经常被访问的地方,不造成资源浪费
        public bool CanWork 
        {
            get 
            {
                if (this.age>=16)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

    }
}
  • 示例2:被动计算 ```csharp using System;

namespace PropertyExample { class Program { static void Main(string[] args) { Student stu = new Student(); stu.Age = 12; Console.WriteLine(stu.CanWork); } }

class Student
{

    //Age属性
    private int age;

    public int Age
    {
        get { return age; }
        set
        {
            age = value;
            this.CalculateCanWork();
        }
    }

    //CanWork只读属性
    //被动计算CanWork值,适用于CanWork经常被访问,而Age不经常被访问的地方,不造成资源浪费
    private bool canWork;

    public bool CanWork
    {
        get { return canWork; }
    }


    private void CalculateCanWork()
    {
        if (this.age >= 16)
        {
            this.canWork = true;
        }
        else
        {
            this.canWork = false;
        }
    }
}

}


- 注意实例属性和静态属性
- 属性的名字一定是名词
- 只读属性——只有getter没有setter
   - 尽管语法上正确,几乎没有人使用"只写属性",因为属性的主要目的是通过向外暴露数据而表示对象/类型的状态
- set修饰符为private的属性,外部无法访问,只能内部访问
   - 示例:
```csharp
class Student
{
    private int age;

    public int Age
    {
        get { return age; }
        private set{ age = value;}//只能内部set
    }

    public void SomeMethod()
    {
        this.Age = 20;
    }
}

属性与字段的关系

  • 一般情况下,它们都用于表示实体(对象或类型)的状态
  • 属性大多数情况下是字段的包装器(wrapper)
  • 建议:永远使用属性(而不是字段)来暴露数据,即字段永远都是private或protected的

索引器

  • 什么是索引器
    • 索引器(indexer)是这样一种成员:它使对象能够与数组相同的方式(即使用下标)进行索引
  • 索引器的声明
    • 参见C#语言定义文档
    • 注意:没有静态索引器
  • 示例:把索引器用于非集合内(Student),实际应用较少 ```csharp using System; using System.Collections.Generic;

namespace IndexerExample { class Program { static void Main(string[] args) { Student stu = new Student(); var mathScore = stu[“Math”]; Console.WriteLine(mathScore==null);//输出true

        stu["Math"] = 60;
        var mathScore1 = stu["Math"];
        Console.WriteLine(mathScore1);//输出60
    }
}

class Student
{
    private Dictionary<string, int> scoreDictionary = new Dictionary<string, int>();

    //声明索引器(indexer+tab两次),返回可空的int类型,索引用string类型
    public int? this[string subject]
    {
        get 
        {
            if (this.scoreDictionary.ContainsKey(subject))
            {
                return this.scoreDictionary[subject];
            }
            else
            {
                return null;
            }
        }
        set 
        {
            if (value.HasValue==false)
            {
                throw new Exception("score can not be null");
            }
            if (this.scoreDictionary.ContainsKey(subject))
            {
                this.scoreDictionary[subject] = value.Value;
            }
            else
            {
                this.scoreDictionary.Add(subject, value.Value);
            }
        }
    }
}

}


<a name="ALBN0"></a>
# 常量

- 什么是常量
   - 常量(constant)是表示常量值(即,可以在编译时计算的值)的类成员
   - 常量隶属于类型而不是对象,即没有"实例常量"
      - "实例常量"的角色由只读实例字段来担当
   - 注意区分成员常量与局部常量
```csharp
using System;

namespace ConstantExample
{
    class Program
    {
        static void Main(string[] args)
        {
            double x =  GetArea(2.0);
            Console.WriteLine(x);

            //声明局部常量
            const int a = 100;

            Console.WriteLine(WAPSPEC.WebSiteURL);
        }

        static double GetArea(double r)
        {
            //PI就是一个成员常量
            double a = Math.PI * r * r;
            return a;

        }
    }
    class WAPSPEC
    {
        //声明成员常量
        public const string WebSiteURL = "http://www.waspec.org";
    }
}
  • 常量的声明
  • 各种”只读”的应用场景
    • 为了提高程序可读性和执行效率——常量
    • 为了防止对象的值被改变——只读字段
    • 向外暴露不允许修改的数据——只读属性(静态或非静态),功能与常量有一些重叠
    • 当希望成为常量的值其类型不能被常量声明接受时(类/自定义结构体)——静态只读字段