C#语言入门详解(17)字段、属性、索引器、常量
内容来自刘铁猛C#语言入门详解课程。
参考文档:CSharp language specification 5.0 中文版
前言
类的成员是静态成员 (static member) 或者实例成员 (instance member)。 静态成员属于类, 实例成员属于对象(类的实例) 。下表提供了类所能包含的成员种类的概述。
成员 | 说明 |
---|---|
常量 | 与类关联的常量值 |
字段 | 类的变量 |
方法 | 类可执行的计算和操作 |
属性 | 与读写类的命名属性相关联的操作 |
索引器 | 与以数组方式索引类的实例相关联的操作 |
事件 | 可由类生成的通知 |
运算符 | 类所支持的转换和表达式运算符 |
构造函数 | 初始化类的实例或类本身所需的操作 |
析构函数 | 在永久丢弃类的实例之前执行的操作 |
类型 | 类所声明的嵌套类型 |
本节所讲的字段、属性、索引器、常量为类的四个成员。
一、字段 Field
- field原意为田地,在此处意指为数据存放空间。以Student类为例,包含ID、Name两个字段,每个字段为4个字节。则Student的对象占8个字节,这8个字节分为两块田地,分别为ID、Name放不同的数据。
- 使用 static 修饰符声明的字段定义静态字段 (static field)。 一个静态字段只标识一个存储位置。无论对一个类创建多少个实例, 它的静态字段永远都只有一个副本,可用来表示类当前的状态,即静态字段的初始化在运行环境加载类时,后面不再初始化。声明静态字段时设置初始化值与在静态构造器里面初始化静态字段其实是一样的。
- 不使用 static 修饰符声明的字段定义实例字段 (instance field)。 类的每个实例都为该类的所有实例字段创建一个单独副本,每次创建实例都会初始化字段,实例字段的初始化在创建时。声明实例字段时初始化值与在实例构造器里面初识化实例字段是一样的。
- readonly修饰的字段只有一次赋值机会,可在声明时或构造器中赋值。
class Program
{
static void Main(string[] args)
{
var stuList = new List<Student>();
for (int i = 0; i < 100; i++)
{
var stu = new Student
{
Age = 24,
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.Amout;
Student.AverageScore = totalScore / Student.Amout;
Student.ReportAmount();
Student.ReportAverageAge();
Student.ReportScore();
}
class Student
{
//普通字段
public readonly int ID;
public int Age;
public int Score;
//静态字段
//public、static为修饰符,修饰符在使用中最少使用一个,且必须是有意义的
//访问级别 static修饰符 变脸类型 成员名
public static int AverageAge;
public static int AverageScore;
public static int Amout;
// readonly修饰的字段只有一次赋值机会
public Student(int id)
{
this.ID = id;
}
public Student()
{
Amout++;
}
//静态方法
public static void ReportAmount()
{
Console.WriteLine(Amout);
}
public static void ReportAverageAge()
{
Console.WriteLine(AverageAge);
}
public static void ReportScore()
{
Console.WriteLine(AverageScore);
}
}
}
二、属性
- 属性为字段的包装器,为避免字段被污染,需要对字段的赋值制定一定的规则,这个规则通过属性来实现。
- prop + 2 * TAB:属性的简略声明
- propfull + 2 * TAB:属性的完整声明
- 使用 Get/Set 方法对之前:
class Program
{
static void Main(string[] args)
{
var stu1 = new Student()
{
Age = 20
};
var stu2 = new Student()
{
Age = 20
};
var stu3 = new Student()
{
// 非法值,污染字段
Age = 200
};
var avgAge = (stu1.Age + stu2.Age + stu3.Age) / 3;
Console.WriteLine(avgAge);
}
}
class Student
{
public int Age;
}
- 在C++/Java等编程语言中演化为 Get/Set 方法:
class Program
{
static void Main(string[] args)
{
try
{
var stu1 = new Student();
stu1.SetAge(20);
var stu2 = new Student();
stu2.SetAge(20);
var stu3 = new Student();
stu3.SetAge(200);
var 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 age;
}
public void SetAge(int value)
{
if (value >= 0 && value <= 120)
{
age = value;
}
else
{
throw new Exception("Age value has error.");
}
}
}
- C++、JAVA 里面是没有属性的,使用 Get/Set 来保护字段的方法至今仍在 C++、JAVA 里面流行。因为 Get/Set 写起来冗长,微软应广大程序员请求,给 C# 引入了属性。
class Program
{
static void Main(string[] args)
{
try
{
var stu1 = new Student();
stu1.Age=20;
var stu2 = new Student();
stu2.Age = 20;
var stu3 = new Student();
stu3.Age = 200;
var 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("Age value has error.");
}
}
}
}
- 动态计算值的属性。主动计算,每次获取 CanWork 时都计算,适用于 CanWork 属性使用频率低的情况。
class Program
{
static void Main(string[] args)
{
try
{
var 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;
}
}
public bool CanWork
{
get
{
return age > 16;
}
}
}
- 被动计算,只在 Age 赋值时计算一次,适用于 Age 属性使用频率低,CanWork 使用频率高的情况。
class Program
{
static void Main(string[] args)
{
try
{
var 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;
CalculateCanWork();
}
}
private bool canWork;
public bool CanWork
{
get { return canWork; }
}
private void CalculateCanWork()
{
canWork = age > 16;
}
}
- 建议:永远使用属性(而不是字段)来暴露数据,即字段永远是 private 或 protected 的。 字段只在类内部使用,类之间交换数据,永远只用属性。
三、索引器
- indexer + 2 * TAB:快速声明索引器
- 索引器一般用于集合中,下面示例仅为展示,一般很少用在非集合中。
class Program
{
static void Main(string[] args)
{
var stu = new Student();
stu["Math"] = 90;
stu["Math"] = 100;
var mathScore = stu["Math"];
Console.WriteLine(mathScore);
}
}
class Student
{
//键值对,string为科目名称,int为socre
private Dictionary<string, int> scoreDictionary = new Dictionary<string, int>();
public int? this[string subject]
{
get
{
if (scoreDictionary.ContainsKey(subject))
{
return scoreDictionary[subject];
}
else
{
return null;
}
}
set
{
if (value.HasValue == false)
{
throw new Exception("Score cannot be null");
}
if (scoreDictionary.ContainsKey(subject))
{
// 可空类型的 Value 属性才是其真实值。
scoreDictionary[subject] = value.Value;
}
else
{
scoreDictionary.Add(subject, value.Value);
}
}
}
}
四、常量
- 示例为第四种场景:类不可以用作常量,需使用静态只读字段
class Program
{
static void Main[string[] args)
{
Console.WriteLine[WASPEC.WebsiteURL);
}
}
class WASPEC
{
public const string WebsiteURL = "http://www.waspec.org";
//类 不能用作常量
//public const Building Location = new Building[) { Address = "Some Address” };
public static readonly Building Location = new Building("Some Address")
class Building
}
class Building
{
public Building[string address)
{
this.Address = address;
}
public string Address { get; set; }
}