字段、属性、索引器、常量都是用来表达数据的
C#的类和结构体可能会有如下成员:常量、字段、方法、属性、索引器、事件、运算符、构造函数、析构函数、类型
字段(field)
字段是一种表示与对象或类型(类与结构体)相关联的变量,字段是类型的成员
字段是为一个对象或类型存储数据,字段的值可以表现这个对象或类型当前的状态。字段旧称是“成员变量”
- 与对象关联的字段称为“实例对象”
internal class Program
{
static void Main(string[] args)
{
People p1 = new People();
p1.Age = 20;
}
}
class People
{
public int Age;//实例字段
}
- 与类型关联的字段称为“静态字段”,由static修饰
internal class Program
{
static void Main(string[] args)
{
People p1 = new People();
p1.Age = 20;
People.PeoNum();
}
}
class People
{
public int Age;
public static int Num;//静态字段,记录人数
public People()
{
People.Num++;
}
public static void PeoNum()
{
Console.WriteLine(People.Num);
}
}
注:字段的名字一定是一个名词
声明字段
字段的声明要在类型里面
特性(可删除) + 修饰符组合(需要有意义,可删除) + 字段的数据类型 + 变量声明器(可删除) + “;”
字段声明不是一个语句,语句只出现在函数体中,而字段是出现在类型中
字段的默认值就是字段数据类型的默认初始值
字段也可以在构造器中初始化值
实例类型每次都会初始化,静态类型只有在第一次调用时会初始化
- 实例字段初始化是对象被创建时
- 静态字段初始化是类型被加载时
只读字段
修饰符中有一个“readonly”(只读),在声明时初始化后无法赋值,静态只读字段可在静态构造器中初始化
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine(Brush.DefaultColor.Red);
Console.WriteLine(Brush.DefaultSize);
}
}
struct Color
{
public int Red;
public int Green;
public int Blue;
}
class Brush
{
public static readonly Color DefaultColor = new Color() { Blue = 0, Green = 0, Red = 0 };
public static readonly int DefaultSize;
static Brush()//静态构造器
{
Brush.DefaultSize = 5;
}
}
属性(property)
用于访问对象或类型的特征的成员,特征反映了状态
属性是字段的自然拓展
- 字段更偏向于实例对象在内存中的布局;属性更偏向于反映现实世界对象的特征
- 数据存储在字段中时,数据的状态由字段中存储的值而定;属性可以实时动态地计算出数据的特征
- 字段存储的值可能是错误的(被非法值污染),而属性可以通过一些逻辑保护字段不被非法值污染
- 建议永远使用属性来向外暴露数据,字段永远用 private 或 protected 修饰
属性是由Get/Set方法进化而来
internal class Program
{
static void Main(string[] args)
{
People p1 = new People();
p1.Age = 1000;//对于人的年龄来说,1000就是个非法值
//而字段就可能被非法值污染
}
}
class People
{
public int Age;
}
Get/Set方法:
internal class Program
{
static void Main(string[] args)
{
try
{
People p1 = new People();
p1.SetAge(20);
People p2 = new People();
p2.SetAge(250);//250是非法值
int avgAge = (p1.GetAge() + p2.GetAge()) / 2;
Console.WriteLine(avgAge);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
class People
{
private int age;//字段变成private时,使用驼峰法命名
//使用一对方法保护这个字段。一个方法用来获取这个字段的值;一个为这个字段设置值
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.");
}
}
}
属性:
internal class Program
{
static void Main(string[] args)
{
try
{
People p1 = new People();
p1.Age = 20;
People p2 = new People();
p2.Age = 25;
int avgAge = (p1.Age + p2.Age) / 2;
Console.WriteLine(avgAge);
}
catch (Exception error)
{
Console.WriteLine(error.Message);
}
}
}
class People
{
private int age;
public int Age//Age属性
{
//括号内为访问器
get//获取值
{
return age;
}
set//设置值
{
if(value >=0&&value<=120)//C#中准备了一个默认的变量value
{//value是上下文关键字,在某个特定的代码上下文中其是关键字
age = value;
}
else
{
throw new Exception("Age value has error.");
}
}
}
}
属性是一种“语法糖”,上段代码中的get和set会自动调用get_Age()和set_Age(int32)
属性的声明
注:属性名字需要是名词或名词短语
属性有完整声明和简略声明,区别主要是 get 和 set 的使用
- 完整的声明
特性(可删除) + 修饰符组合(需要有意义,可删除) + 属性的数据类型 + 属性的名字 + { get 和 set }
private int myVar;
public int MyProperty
{
get { return myVar; }
set { myVar = value; }
}
如果一个属性只有 get 没有 set 则是只读属性;而如果一个属性只有 set 没有 get 则是只写属性
VS中输入propfull
+敲击两下tab就能自动出现完整的声明
使用静态属性时,对应保护的字段也需要被static修饰
- 简略的声明
简略的属性声明与字段类似,不受保护,可能会被非法值污染
public int MyProperty { get; set; }
VS中输入prop
+敲击两下tab就能自动出现完整的声明
简略声明会在后台封装一个私有变量
- 重构声明
输入一个私有字段,光标置于字段上,Ctrl+R -> Ctrl+E
只读属性
只读属性只有get,没有set
private int myVar;
public int MyProperty
{
get { return myVar; }
}
还有一类特殊的“只读”属性,是外界无法访问 set 方法,只能在内部使用 set
private int myVar;
public int MyProperty
{
get { return myVar; }
private set { myVar = value; }
}
public void MethodSet()
{
MyProperty = 10;
}
动态计算值
internal class Program
{
static void Main(string[] args)
{
People p1 = new People();
p1.Age = 15;
Console.WriteLine(p1.CanWork);
}
}
class People
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
public bool CanWork
{ //这里的 CanWork 就是动态计算的属性
get
{
if (age >= 16)
{
return true;
}
else
{
return false;
}
}
}
}
索引器(indexer)
索引器能使对象能够用以数组相同的方法进行索引(即是使用下标)
一般有索引器这个成员的类是集合类型
VS中输入indexer
+敲击两下tab就能自动出现索引器
索引器应用于非集合类型:
internal class Program
{
static void Main(string[] args)
{
WorkWages wages = new WorkWages();
wages["Teacher"] = 3000;//为教师工作工资赋值
var TeacherWages = wages["Teacher"];//获取工资值
Console.WriteLine(TeacherWages);
}
}
class WorkWages//用来检测不同工作工资
{ //键 -- 值
private Dictionary<string, int> wagesDictinoary = new Dictionary<string, int>();
public int? this[string work]//索引器
{
get
{ //若输入的工作存在,则返回工资
if (wagesDictinoary.ContainsKey(work))
{
return wagesDictinoary[work];
}
else
{ //若输入的工作不存在,则返回NULL
return null;
}
}
set
{ //判断用户设定的工资值是否为NULL
if (value.HasValue == false)
{ //若为NULL,则报错
throw new Exception("Wages is null.");
}
if (wagesDictinoary.ContainsKey(work))
{
wagesDictinoary[work] = value.Value;//value为可空类型
} //可空类型.Value则是这个类型真正的值
else
{ //若字典中不存在这个工作,则在字典中加入一条“键和值”
wagesDictinoary.Add(work, value.Value);
}
}
}
}
常量(constant)
用来表示常量值的类成员,使用常量可以提高程序运行效率
比如Math.PI
就是个常量,使用时会用一个常量代替这个标识符
常量需要用 const 修饰
常量隶属于类型而不是对象,即没有“实例常量”,“实例常量”可以用只读字段来代替
- 成员常量:类的成员中的常量
- 局部常量:在声明变量时,在前面用 const 修饰
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine(NASA.WebURL);//调用成员常量需要使用类型 + . + 常量名称
}
}
class NASA
{
public const string WebURL = "https://www.nasa.gov/";
}
各种“只读”作用
- 常量:提高程序可读性和执行效率
- 只读字段:防止对象的值被改变
- 只读属性(静态或非静态):向外暴露不允许修改的数据
- 静态只读字段:让成为常量的值其类型不能被常量声明接受。比如常量不能接收结构体类型,此时就只能用静态只读字段