1、字段
1.1、什么是字段
(1)字段(field)是一种表示与对象或类型(类与结构体)关联的变量
field:田地,地方的意思;表示在内存中开辟一块空间存放数据
(2)字段是类型的成员,旧称“成员变量”
例子:C语言程序——ID和Name就是字段
声明的时候一定要卸载类体里面,不能写在函数体中(函数体中叫局部变量)
(3)与对象关联的字段亦称“实例字段”
帮助实例或者对象保存数据的,实例字段的组合是表示对象当前的状态
(4)与类型关联的字段称为“静态字段”,由static修饰
- 静态字段表示某个数据类型当前的状态
- 例子:实例字段和静态字段的功能
class Program { static void Main(string[] args) { //Student stu1 = new Student(); //stu1.Age = 40; //stu1.Score = 90; //实例的状态,年龄大,学习好 //Student stu2 = new Student(); //stu2.Age = 24; //stu2.Score = 60; //实例的状态,年龄小,学习差 //Student.ReportAmount(); //类型的状态,学生的数量 List<Student> stuList = new List<Student>(); for (int i = 0; i < 100; i++) { Student stu = new Student(); stu.Age = 24; stu.Score = i; stuList.Add(stu); } int totalAge = 0; int totalScore = 0; foreach (var stu in stuList) { totalAge += stu.Age; totalScore += stu.Score; } Student.AverageAge = totalAge / Student.Amount; Student.AverageScore = totalScore / Student.Amount; Student.ReportAmount(); //类型的状态,学生的数量 Student.ReportAverageAge(); //类型的状态,学生的平均年龄 Student.ReportAverageScore(); //类型的状态,学生的平均成绩 } } class Student { public int Age; //实例字段 public int Score; //实例字段 public static int AverageAge; public static int AverageScore; public static int Amount; public Student() { Student.Amount++; //每次创建实例Amount都+1 } public static void ReportAmount() { Console.WriteLine(Student.Amount); } public static void ReportAverageAge() { Console.WriteLine(Student.AverageAge); } public static void ReportAverageScore() { Console.WriteLine(Student.AverageScore); } }
1.2、字段的声明
(1)参见C#语言定义文档
field_declaration:
(可选) (字段修饰符,可选) type variable_declarators(变量声明器) ;field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| field_modifier_unsafe
;variable_declarators
: variable_declarator (',' variable_declarator)*
;variable_declarator
: identifier ('=' variable_initializer)?
;variable_initializer
: expression
| array_initializer
;
(2)尽管字段声明带分号,但它不是语句
(3)字段的名字一定是名词
1.3、字段的初始值
(1)无显式初始化时,字段获得其类型的默认值,所以字段“永远都不会未被初始化”
(2)实例字段初始化的实际——对象创建时
(3)静态字段初始化的时机——类型被加载(load)时
两种设置初值的方法的等价的
当一个数据类型被加载时,静态构造器会被调用,只调用这一次。
1.4、只读字段
(1)实例只读字段
只读字段只有一次赋值机会,在实例构造器中初始化,之后无法被修改
class Program { static void Main(string[] args) { Student stu1 = new Student(1); Console.WriteLine(stu1.ID); } } class Student { public readonly int ID; public int Age = 0; public int Score; public Student(int id) { this.ID = id; } }
(2)静态只读字段
类的静态只读字段。
class Program { static void Main(string[] args) { Console.WriteLine(Brush.DefaultColor.Red); Console.WriteLine(Brush.DefaultColor.Green); Console.WriteLine(Brush.DefaultColor.Blue); } } struct Color { public int Red; public int Green; public int Blue; } class Brush { public static readonly Color DefaultColor = new Color() { Red = 0, Green = 0, Blue = 0 }; } static Brush //静态构造器 { public static readonly Color DefaultColor = new Color() { Red = 0, Green = 0, Blue = 0 }; }
2、属性
2.1、什么是属性
(1)属性(property)是一种用于访问对象或类型的特征的成员,特征反映了状态
(2)属性是字段的自然扩展
-
从命名上看,field更偏向于实例对象在内存中的布局,property更偏向于反映现实世界对象的特征
-
对外:暴露数据,数据可以是存储在字段里的,也可以是动态计算出来的
-
对内:保护字段不被非法值“污染”
(3)属性由Get/Set方法对进化而来(三个例子讲解进化过程)
- 举例1:学生3年龄200污染了字段,但是从结果(平均年龄=80)看不出异常
class Program { static void Main(string[] args) { Student stu1 = new Student(); stu1.Age = 20; Student stu2 = new Student(); stu2.Age = 20; Student stu3 = new Student(); stu3.Age = 200; //污染字段 int avgAge = (stu1.Age + stu2.Age + stu3.Age) / 3; Console.WriteLine(avgAge); } } class Student { public int Age; }
- 举例2:为了防止异常值污染,隐藏字段,用方法来设置。此时值不规范则会报错
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("Age value has error"); } } }
运行结果:
- 举例3:微软提供的便捷写法,get/set方法对
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 GetAge() { return this.age; } public int Age //微软提供的快捷写法 { get { return this.age; } set { if (value >= 0 && value <= 120) //微软准备的value,叫上下文关键字 { this.age = value; } else { throw new Exception("Age value has error"); } } } }
(4)又一个“语法糖”——属性背后的秘密
“语法糖”:在编程语言中可能有一小段非常简单的逻辑,这段逻辑背后隐藏了比较复杂的逻辑;之所以做隐藏,是为了方便程序员编写程序;前面学过的foreach循环和这里的属性都是语法糖。
使用反编译器观察程序(没找到vs2019的反编译器)。视频50分钟。
2.2、属性的声明
(1)完整声明——后台(back)成员变量与访问器(注意使用code snippet和refactor工具)
property_declaration
: type member_name property_body
;property_modifier
: 'new'
| 'public' 最常见的修饰符
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| property_modifier_unsafe
;property_body
: '{' accessor_declarations '}' property_initializer?
| '=>' expression ';'
;property_initializer
: '=' variable_initializer ';'
;
(2)简略声明——只有访问器(查看IL代码)
完整声明和简略声明写法对比;
propfull,按两下tab键;prop,按两下tab键
class Student { private int myVar; public int MyProperty { get { return myVar; } set { myVar = value; } } }
class Student { public int MyProperty { get; set; } }
(3)动态计算值得属性
属性比字段更优越的地方就是这个。
//主动计算CanWork值 class Program { static void Main(string[] args) { try { Student stu1 = new Student(); stu1.Age = 12; Console.WriteLine(stu1.CanWork); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } class Student { private int age; public int Age { get { return age; } set { age = value; this.CalculateCanWork(); } } private bool canWork; public bool CanWork { get { return canWork; } } private void CalculateCanWork() { if (this.age>=16) { this.canWork = true; } else { this.canWork = false; } } }
(4)注意实例属性和静态属性
静态属性使用static修饰,属于类型而不是实例。
(5)属性的名字一定是名词
(6)只读属性——只有getter没有setter
-
尽管语法上正确,几乎没有人使用“只写属性,因为属性得主要目的是通过向外暴露数据而表示对象/类型得状态”
2.3、属性与字段的关系
(1)一般情况下,它们都用于表示实体(对象或类型)的状态
(2)属性大多数情况下是字段的包装器(wrapper)
(3)建议:永远使用属性(而不是字段)来暴露数据,即字段永远是private或protected的
3、索引器(概述)
3.1、什么是索引器
- 索引器(indexer)是这样一种成员:它使对象能够用与数组相同的方式(即使用下标)进行索引
索引器一般情况用在集合类型;
初学者很少有机会写索引器。
class Program { static void Main(string[] args) { Student stu = new Student(); stu["Math"] = 90; var mathScore = stu["Math"]; Console.WriteLine(mathScore); } } class Student { private Dictionary<string, int> scoreDictionary = new Dictionary<string, int>(); //int? 可空类型 public int? this[string subject] { get { if (this.scoreDictionary.ContainsKey(subject)) //查找是否包含此科目 { return this.scoreDictionary[subject]; //如果包含就返回科目成绩 } else { return null; //不包含返回null } } set { if (value.HasValue == false) { throw new Exception("Score cannot be null"); // 这一部是防止用户输入一个null值 } if (this.scoreDictionary.ContainsKey(subject)) { this.scoreDictionary[subject] = value.Value; } else { this.scoreDictionary.Add(subject, value.Value); //如果不包含就创建科目并赋值 } } } }
3.2、索引器的声明
(1)参见C#语言定义文档
(2)注意:没有静态索引器
4、常量
4.1、什么是常量
(1)常量(constant)是表示常量值(即,可以在编译时计算的值)的类成员
Math.PI就是一个常量;使用常量可以提高程序运行效率
int.MaxValue也是一个常量
(2)常量隶属于类型而不是对象,即没有“实例常量”
-
“实例常量”的角色由只读实例字段来担当
(3)注意区分成员常量与局部常量
成员常量:Math.PI和int.MaxValue
局部常量:
static void Main(string[] args) { const int x = 100; }
4.2、常量的声明
class Program { static void Main(string[] args) { Console.WriteLine(WASPEC.WebsiteURL); } } class WASPEC { public const string WebsiteURL = "https://www.baidu.com"; }
4.3、各种“只读”的应用场景
(1)为了提高程序可读性的执行效率——常量
(2)为了防止对象的值被改变——只读字段
(3)向外暴露不允许修改的数据——只读属性(静态或非静态),功能与常量有一些重叠
(4)当希望成为常量的值其类型不能被常量声明接受时(类/自定义结构体)——静态只读字段