# 类成员
# 访问级别
在类定义中,字段,方法和属性都有自己的访问级别:
- public:成员可以由任何代码访问。
- private:成员只能由类中的代码访问,如果没有使用关键字,默认是这个级别。
- internal:成员只能由定义它的程序集内部的代码访问。
- protected:成员只能由类或派生类中的代码访问。
- protected internal:成员能由定义它的程序集内部任意代码访问,在其他程序集中只能由它的派生类访问。
# 定义字段
class Person{
public readonly int Age;
public const int MaxAge = 35;
public Person(int age)
{
Age = age;
}
}
class Program
{
static void Main(string[] strs)
{
Person p1 = new Person(20);
Console.WriteLine(p1.Age);
Console.WriteLine(Person.MaxAge);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
注意
- readonly字段只能在定义时赋值或在构造函数中赋值。
- const字段是静态的。
# 定义属性
属性的定义与字段类似,但要比字段复杂,它们在修改状态前执行一些操作。属性有get和set两个访问器,可以用于控制属性的读写,如果忽略其中一个,比如忽略get,那该属性就是只写。其格式如下:
[访问级别] [类型] 属性名
{
get{
//获取属性的代码
}
set{
//设置属性的代码
}
}
private int age;
public int Age{
get{
return age;
}
set{
age=value;
}
}
如果age字段是可以设置可空的,那么像如下这样设置:
private int? age;
public int? Age{
get{
return age;
}
set{
age = value ?? 0;
}
}
如果age字段只有在一定范围内才能赋值,可以像如下设置:
private int? age;
public int? Age{
get{
return age;
}
set{
if(value >=0 && value <=30){
age = value;
}
else
{
throw(new ArgumentOutOfRangeException("Prop Age", value, "Age input need to be in [0,30]!"));
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
属性可以使用virtual,override,abstract关键字就像方法一样,但是这几个关键字不能用于字段。访问器也可以有自己的访问级别,但是它的访问级别不可高于属性的访问级别,比如一个私有属性不能有公开的访问器。C#还提供了使用Lambda样式的属性:
int age=2;
public int Age => (age * 2);
2
Visual Studio可以使用快捷键将字段改成属性,在字段上右键-》快速操作和重构-》封装字段并使用属性,会变成如下:
int age=2;
public int Age { get => age; set => age = value; }
2
# 自动属性
属性是访问对象状态的首选方式,因为它们能控制外部代码访问对象内部的数据,为了简化操作,C#中有一种特殊的属性,称为自动属性,它以简化的语法声明属性,C#编译器会自动添加未键入的内容,也就是会声明一个存储属性的私有字段,并在属性的get和set中使用它。通常我们在编辑器中键入prop然后按两次Tab就会得到一个自动属性的模板。
public int MyProperty { get; set; }
public int Age{ get; } = 100;
2
使用自动属性时,只能通过属性访问数据,不能通过底层的私有字段来访问,因为我们不知道底层私有字段的名称,该名称是在编译期间定义的。注意自动属性要么都包含get和set访问器,要么只包含get访问器,但是可以改变访问器的访问权限。
# 定义方法
方法有几个关键字可描述:
- virtual:方法可重写。
- abstract:方法必须在非抽象的派生类中重写。
- override:方法重写了一个基类的方法。
- override sealed:在派生类中不能对这个方法做进一步的修改。
- extern:方法定义在其他地方。
# 重写方法
当从基类继承的成员是virutal或abstract的方法,可以用override关键字重写这段代码。
class Animal
{
public virtual void Run()
{
Console.WriteLine("animal run!");
}
}
class Tiger : Animal
{
public override void Run()
{
Console.WriteLine("tiger run!");
}
}
class Program
{
static void Main(string[] args)
{
Animal tiger = new Tiger();
tiger.Run();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 覆盖方法
覆盖方法可以覆盖基类中的虚拟的和非虚拟的方法,也可以不使用关键词new,但是编辑器会有警告,因为这可能是无意导致的操作,一般如果是有意重写方法的话最好还是使用new关键字声明。
class Animal
{
public void Run()
{
Console.WriteLine("animal run!");
}
public virtual void Eat()
{
Console.WriteLine("animal eat!");
}
}
class Tiger : Animal
{
public new void Run()
{
Console.WriteLine("tiger run!");
}
public new void Eat()
{
Console.WriteLine("tiger eat!");
}
}
class Program
{
static void Main(string[] args)
{
Tiger tiger = new Tiger();
tiger.Run();
tiger.Eat();
Animal animal = tiger;
animal.Run();
animal.Eat();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 重写和覆盖的区别
从上面的运行结果可以看出,如果使用基类引用来调用子类实例的方法,重写会调用子类的方法,覆盖会调用基类的方法。
# 基类和当前类
有两个关键词代表基类和当前类,它们分别是base和this。this引用的是当前的对象实例,base则代表引用的基类。这两个关键字都不能用于静态方法。
this有个常用的用法就是限定当前类的成员。
# 元组析构
前面介绍了元组,在函数返回一个元组时,接收结果必须要解析下才行,如果在类中使用元组,那么可以使用元组析构(tuple deconstruction)来避免被解析的过程:
class Position
{
public double axisX { get; }
public double axisY { get; }
public Position(double x, double y) => (axisX, axisY) = (x, y);
public void Deconstruct(out double x, out double y) => (x, y) = (axisX, axisY);
}
class Program
{
static void Main(string[] args)
{
Position p1 = new Position(20, 30);
(double x, double y) = p1;
Console.WriteLine($"axis x:{x},axis y:{y}");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
元组析构需要给类添加Deconstruct()函数即可。
# 嵌套类
我们可以在类中定义类,这样修饰嵌套类的不仅仅是public和internal,还可以是private和protected。通过嵌套类可以修改其包含类的私有字段。看下例:
class Animal
{
private string name = "BaoBao";
public string Name => name;
public void Run()
{
Console.WriteLine("animal run!");
}
public class Holer
{
public void SetName(Animal anim,string str)
{
anim.name = str;
}
public void Run()
{
Console.WriteLine("Holder let animal run!");
}
}
}
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
Console.WriteLine(animal.Name);
Animal.Holer holder = new Animal.Holer();
holder.SetName(animal, "little cat!");
Console.WriteLine(animal.Name);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 部分类和方法
如果一个类包含很多成员时,比较容易引起混乱,代码文件也比较长。此时有一种方法就是可以使用region将代码分隔开,比如:
public class TestClass{
#region Fields
private int IntVal;
#endregion
#region Methods
public void GetVoid(){
return IntVal;
}
#endregion
...
}
2
3
4
5
6
7
8
9
10
11
12
还有一种方法是使用部分类定义(partial class definition),就是将类的定义放在多个文件中,比如将字段,属性和构造函数放在一个文件中,将方法放在另一个文件中,只需在类的定义前面加限定词partial。同理,也可以定义部分方法(partial method)。部分方法在一个部分类中定义,在另一个部分类中实现,定义方法时也需要使用partial关键字。
public class Person
{
public int age;
}
public interface IRun
{
void PersonRun();
}
public interface IEat
{
void PersonEat();
}
public partial class Student:Person,IRun
{
public string name = "zhangsan";
public void PersonRun()
{
Console.WriteLine("Student run!");
}
partial void SetName(string str);
}
public partial class Student:IEat
{
public void PersonEat()
{
Console.WriteLine("Student eat!");
SetName("Eater");
}
partial void SetName(string str)
{
name = str;
}
}
class Program
{
static void Main()
{
Student st1=new Student();
st1.PersonEat();
Console.WriteLine(st1.name);
st1.PersonRun();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
注意:
- 部分类分开实现的接口相当于整类实现的全部接口。
- 基类在多个定义文件中指定时必须是同一个基类,因为C#中类只能继承一个基类。
- 部分方法可以是静态的,但它们总是私有的,且不能有返回值。
- 部分方法不能使用out参数,但可以使用ref参数,部分方法也不能使用virtual,abstract,override,new,sealed或extern修饰符修饰。
- 部分方法如果没有实现,编译代码时编译器会完全删除该方法,还会删除该方法的所有调用,这样如果不实现部分方法,就不会影响性能。
# 接口
# 接口的实现
其格式如下:
interface [interfaceName]
{
}
2
3
4
接口和类有几点不同:
- 不允许使用访问修饰符(private),所有接口成员默认都是公共的。
- 接口成员中的方法不能包含代码体,不能定义字段成员,类型成员。
- 不能用关键字static,virtual,abstract或sealed来定义接口成员。
- 接口也能用new覆盖基接口的成员。
具体实现看下面实例:
interface IMoveBiology
{
void Run();
void Eat();
}
class Animal
{
public void Run()
{
Console.WriteLine("Any animal all run in this way!");
}
}
class Tiger : Animal,IMoveBiology
{
void IMoveBiology.Eat()
{
Console.WriteLine("Tiger is Eatting!");
}
}
class Cat : IMoveBiology
{
public void Run()
{
Console.WriteLine("Cat is running!");
}
public void Eat()
{
Console.WriteLine("Cat is eatting!");
}
}
class Program
{
static void Main(string[] args)
{
Tiger tiger = new Tiger();
tiger.Run();
Console.WriteLine("-------------");
Cat cat = new Cat();
cat.Run();
cat.Eat();
Console.WriteLine("-------------");
IMoveBiology tigerI = new Tiger();
tigerI.Run();
tigerI.Eat();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
注意:
- 实现接口的类必须实现接口所有成员。可以使用virtual或abstract来实现接口成员。
- 继承一个实现接口的基类意味着隐式支持这个接口。
- 显式地实现接口就是在实现接口时成员以接口名限定。显式地实现接口只能以接口引用调用接口方法。
来看下在接口中定义属性的情况:
interface IMoveBiology
{
string Name
{
set;
}
}
class Animal : IMoveBiology
{
private string name;
//string IMoveBiology.Name { set => name = value; get => name; }
public string Name { set => name = value; get => name; }
}
class Program
{
static void Main(string[] args)
{
Animal anim = new Animal();
anim.Name = "baobao";
Console.WriteLine(anim.Name);
IMoveBiology biologyI = anim;
Console.WriteLine(anim.Name);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
关于接口中的属性应注意:
- 在接口中定义的属性可以省略get或set,形式很像类的自动属性,但实际不是。
- 如果是隐式实现接口,在实现类中可以添加没有的get或set访问器,但是如果是显式实现接口,那么就不能添加没有的访问器。