C#语言入门详解 第十七讲 (字段、属性、索引器、常量)
在C#语言中,类(或结构体)包含以下成员:
什么是字段
- 字段(field)是一种表示与对象或类型(类与结构体)关联的变量
字段是用来储存数据的,多个字段就可以描述对象的状态 - 字段是类型的成员,旧称“成员变量”
- 与对象关联的字段亦称“实例字段”
- 与类型关联的字段称为“静态字段”,有static修饰
internal 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(); // >>>2
}
}
class Student
{
// 定义实例字段(只有通过实例化之后才能被调用,不能通过类名调用)
public int Age;
public int Score;
// 定义静态字段(通过类名调用,不能通过实例调用)
public static int AverageAge;
public static int AverageScore;
public static int Amount;
public Student()
{
Student.Amount++;
}
public static void ReportAmount()
{
Console.WriteLine(Student.Amount);
}
}
internal class Program
{
static void Main(string[] args)
{
List<Student> students = new List<Student>();
for (int i = 0; i < 100; i++)
{
Student student = new Student();
student.Age = 24;
student.Score = i;
students.Add(student);
}
int totalAge = 0;
int totalScore = 0;
foreach (Student student in students)
{
totalAge += student.Age;
totalScore += student.Score;
}
Student.AverageAge = totalAge / Student.Amount;
Student.AverageScore = totalScore / Student.Amount;
Student.ReportAmount(); // >>>100
Student.ReportAverageAge(); // >>>24
Student.ReportAverageScore(); // >>>49
}
}
class Student
{
// 定义实例字段(只有通过实例化之后才能被调用,不能通过类名调用)
public int Age;
public int Score;
// 定义静态字段(通过类名调用,不能通过实例调用)
public static int AverageAge;
public static int AverageScore;
public static int Amount;
public Student()
{
Student.Amount++;
}
public static void ReportAmount()
{
Console.WriteLine(Student.Amount);
}
public static void ReportAverageAge()
{
Console.WriteLine(Student.AverageAge);
}
public static void ReportAverageScore()
{
Console.WriteLine(Student.AverageScore);
}
}
字段的声明
- 参见C#语言定义文档
字段必须包含在类型(类和和结构体)中,不能直接写在命名空间内,不能写在方法内(局部变量)
字段声明最常用的两种形式
只读字段的作用:用于保存一旦实例化后就不希望被修改的数据
public int Age; // 访问级别 变量类型 变量名;
public static int Amount; // 访问级别 static 变量类型 变量名;
// 建议在声明字段时就进行初始化
// 只读实例字段readonly
internal class Program
{
static void Main(string[] args)
{
Student student = new Student(1);
Console.WriteLine(student.ID); // >>>1
student.ID = 2; // >>>无法编译,不能修改只读字段
}
}
class Student
{
// 定义实例字段(只有通过实例化之后才能被调用,不能通过类名调用)
public readonly int ID; // 只读实例字段
public int Age;
public int Score;
// 定义静态字段(通过类名调用,不能通过实例调用)
public static int AverageAge;
public static int AverageScore;
public static int Amount;
public Student(int ID) // 动态初始化器,每次实例化都会执行
{
this.ID = ID; // 初始化只读字段
}
}
// 静态只读字段
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine(Brush.DefaultColor.Red); // >>>0
Console.WriteLine(Brush.DefaultColor.Green); // >>>0
Console.WriteLine(Brush.DefaultColor.Blue); // >>>0
}
}
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 };
// 上面的语句和下面的作用是一致的
public static readonly Color DefaultColor;
static Brush()
{
Brush.DefaultColor = new Color() { Red = 0, Green = 0, Blue = 0 };
}
}
- 尽管字段声明带有分号,但不是语句
- 字段的名字一定有名词
字段的初始值
- 无显式初始值时,字段获得其类型的默认值,所以字段“永远都不会未被初始化”
- 实例字段初始化的时机——对象创建时(每次创建实例都会执行一次)
- 静态字段初始化的时机——类型被加载时(只执行一次)
- 类的初始化也有静态初始化和动态初始化
class Student
{
// 定义实例字段(只有通过实例化之后才能被调用,不能通过类名调用)
public int Age;
public int Score;
// 定义静态字段(通过类名调用,不能通过实例调用)
public static int AverageAge;
public static int AverageScore;
public static int Amount;
public Student() // 动态初始化器,每次实例化都会执行
{
}
static Student() // 静态初始化器,只在类加载时被执行
{
}
}
只读字段
- 实例只读字段
- 静态只读字段
什么是属性
- 属性(property)是一种用于访问对象或类型的特征的成员,特征反映了状态
- 属性是字段的自然扩展
// 字段怎样向属性进化的??
// 将字段的访问级别由public改为private
// 变量名使用小写字母age
// 定义GetAge()和SetAge()方法,分别获取和设置属性值
internal class Program
{
static void Main(string[] args)
{
try
{
Student stu1 = new Student();
Student stu2 = new Student();
Student stu3 = new Student();
stu1.SetAge(20);
stu2.SetAge(20);
stu3.SetAge(200);
double AverageAge = (stu1.GetAge() + stu2.GetAge() + stu3.GetAge()) / 3;
Console.WriteLine(AverageAge);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
class Student
{
private int age;
public int GetAge()
{
return this.age;
}
public void SetAge(int age)
{
if (age > 0 & age < 100)
{
this.age = age;
}
else
{
throw new Exception("Age value has error!!");
}
}
}
// 在C#中属性怎么创建??
// 定义名为Age的public变量
// 在变量名后加入{get{} set{}}
internal class Program
{
static void Main(string[] args)
{
try
{
Student stu1 = new Student();
Student stu2 = new Student();
Student stu3 = new Student();
stu1.Age = 20;
stu2.Age = 20;
stu3.Age = 20;
double AverageAge = (stu1.Age + stu2.Age + stu3.Age) / 3;
Console.WriteLine(AverageAge);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
class Student
{
public int Age
{
get
{
return this.Age;
}
set
{
if (value > 0 & value < 100) // value是上下文关键字,在使用set时,value代指用户输入的指
{
this.Age = value;
}
else
{
throw new Exception("Age value has error!!");
}
}
}
}
-
- 从命名上看,字段(field)更偏向于实例对象在内存中的布局,属性(property)更偏向于反映现实世界对象的特征
-
- 对外:暴露数据,数据可以是存储在字段里的,也可以是动态计算出来的
-
- 对内:保护字段不被非法值“污染”
- 属性由Get/Set方法对进化而来
- 又一个==“语法糖”==——属性背后的秘密
属性的声明
- 完整声明——后台(back)成员变量与访问器(注意使用code snippet和refactor工具)
-
- 在vs中完整声明的快捷方法,在VS中输入“propfull”并按两下tab键,会自动弹出下面代码,并可以通过tab键继续修改
private int myVar;
public int MyProperty
{
get { return myVar; }
set { myVar = value; }
}
- 简略声明——只有访问器(查看IL代码)
-
- 在vs中完整声明的简略声明,在VS中输入“prop”并按两下tab键,会自动弹出下面代码,并可以通过tab键继续修改
public int MyProperty { get; set; }
- 动态计算值的属性
- 注意实例属性的静态属性
- 属性的名字一定是名词
- 只读属性——只用getter没有setter
-
- 尽管语法上正确,几乎没有人使用“只写属性”,因为属性的主要目的是通过向外暴露数据而表示对象/类型的状态
属性与字段的关系
- 一般情况下,他们都用于实体的状态
- 属性大多数情况下是字段的包装器
- 建议:永远使用属性(而不是字段)来暴露数据,即字段永远都是private或protected的
什么是索引器
- 索引器(indexer)是这样一种成员:它使对象能够用与数组相同的方式(即使用下标)进行索引
internal class Program
{
static void Main(string[] args)
{
Student student = new Student();
var mathScore = student["Math"];
Console.WriteLine(mathScore==null); // >>>ture
student["Math"] = 90;
var newMathScore = student["Math"];
Console.WriteLine(newMathScore); // >>>90
}
}
class Student
{
// 声明一个名为scoreDictionary的字典
private Dictionary<string, int> scoreDictionary = new Dictionary<string, int>();
// 创建索引器,快捷方式indexer+两下tab
// int?表示可以是空值
public int? this[string subject]
{
get {
/* return the specified index here */
if (this.scoreDictionary.ContainsKey(subject))
{
return scoreDictionary[subject];
}
else
{
return null;
}
}
set {
/* set the specified index to value here */
if (value.HasValue == false)
{
throw new Exception("Score cannot be null");
}
if (this.scoreDictionary.ContainsKey(subject))
{
scoreDictionary[subject] = value.Value;
}
else
{
this.scoreDictionary.Add(subject, value.Value);
}
}
}
}
索引器的声明
- 参见C#语言定义文档
- 注意:没有静态索引器
什么是常量
- 常量(constant)是表示常量值(即,可以在编译时计算的值)的类成员
- 常量隶属于类型而不是对象,即没有“实例常量”
-
- “实例常量”的角色由只读实例字段来担当
常量的声明
public const double PI;
各种“只读”的应用场景
- 为了提高程序的可读性和执行效率——常量
- 为了防止对象的值被改变——只读字段
- 向外暴露不允许修改的数据——只读属性(静态或非静态),功能与常量有一些重叠
- 当希望成为常量的值其类型不能被常量声明接受时(类/自定义结构体)——静态只读字段