我是靠谱客的博主 干净羽毛,这篇文章主要介绍C#面向对象进阶:十二大修饰符一网打尽,现在分享给大家,希望可以做个参考。

文章目录

    • 访问修饰
    • 类修饰符
    • 接口
    • virtual
    • 其他修饰符

前文提要:
????零基础掌握C#开发中最重要的概念 ????面向对象快速上手
????懂了委托,才算真正入门 ????学会泛型,迈向高手之路
????Winform,最友好的GUI ????Winform+OpenGL+MathNet处理Gauss光斑

在快速入门教程中已经疏解了一些面向对象的基本概念,但并不深入,只对类的构造和继承做了简单的介绍。下面将深入理解C#的面向对象,文集中针对12个修饰符以及接口,从内到外对类进行展开讲解。

访问修饰private, public, protected, internal
特殊类static, sealed, abstract; 接口interface
成员修饰static, sealed, abstract, overade, virtual
其他const, readonly, partial

由于面向对象本来就大肠包小肠,所以本节所有示例均不使用顶级语句。

访问修饰

在声明类成员和方法的时候,往往在类型前面加一个public或者private,比如public int add这种,用以标识外部是否可以访问,此即访问修饰符。

C#一共有6种访问修饰符,这些修饰符当然在类内都是可以访问的,但是在类外,根据是否为派生类、是否在相同程序集,共分为四种特殊情况。

  • publicprivate是两种极端情况,前者是公共属性,任何时候都可以访问;后者是私有属性,除了类内,其他人都不可以访问。
  • protected为保护类型,只有派生类可以访问;internal为内联类型,只有相同的程序集可以访问。
  • private protectedprotected的基础上缩减一步,只允许相同程序集的派生类访问。
  • protected internalprotected的基础上拓展一步,还允许不同程序集的派生类访问。

下面新建一个项目oopTest,在命名空间oopTest中新建一个TestClass,分别用这六种不同的访问修饰符做一个函数。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestClass { public void pubPrint(){ Console.WriteLine("public"); } private void priPrint(){ Console.WriteLine("private"); } protected void proPrint(){ Console.WriteLine("protected"); } internal void IntPrint(){ Console.WriteLine("internal"); } private protected void priProPrint(){ Console.WriteLine("private protected"); } protected internal void proIntPrint(){ Console.WriteLine("protected internal"); } }

下面在不同的应用场景调用这六个函数,首先是main函数中调用,结果如下,其中private, protected, private protected标红了。

在这里插入图片描述

然后新建一个子类,在子类中调用这六个函数,效果如下

在这里插入图片描述

接下来右键解决方案,新建一个项目LibTest,将TestClass移动到这个项目的命名空间下。然后右键oopTest的依赖项,在引用管理器中勾选LibTest,然后在oopTestusing LibTest,这样名义上就可以调用LibTest库中的内容了。

这次,Main函数和子类的表现如下,不考虑public,在子类中,protected, protected internal还依旧坚挺,而在Main函数中则全员阵亡,此时在Main函数中调用TestClass,相当于既不在相同程序集,也不是派生类。

在这里插入图片描述

类修饰符

C#除了质朴的class,还有三种被修饰的类,分别是静态类、密封类以及抽象类,三者分别用修饰符static, sealed, abstract来修饰,这三者互不兼容,说明如下

  • static 不能被实例化为对象,且只能从基类派生
  • sealed 密封类不能被继承
  • abstract 抽象类只能被继承,不能被实例化

对于习惯了面向过程或者函数式编程的人来说,使用静态类有种找到家的感觉。静态类不能实例化的同时,支持不开箱即用,静态类就像是一个函数库,可直接调用,像下面这种。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class StaticClass { public static void printStatic() { Console.WriteLine("static class"); } } class Program { static void Main(string[] args) { StaticClass.printStatic(); } }

密封类和抽象类互为相反,前者不能被继承,后者只能被继承。密封类禁止派生,除了能够带来一些安全性上的优势之外,有时也能略微提高一点成员调用速度。

抽象类用于提供一个基类定义,并让多个派生类共享。抽象类中的方法,既可以是抽象的,也可以不是抽象的,通过abstract修饰的方法,只能声明,不能实现其具体内容。

复制代码
1
2
3
4
5
6
7
8
9
10
11
public abstract class AbstractClass { // 用abstract修饰之后,不可实现 public abstract void printSubAbstract(); // 不用abstract修饰,必须写细节 public void printAbstract() { Console.WriteLine("Abstract"); } }

如果一个普通类或者密封类继承了抽象类,那么这个类必须实现抽象类中所有的抽象方法,其中需要用到overide;而抽象类继承抽象类则不必。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
public class SubAbstract : AbstractClass { public override void printSubAbstract() { Console.WriteLine("Abstract"); } } // 如果没有abstract修饰,是会报错的 public abstract class AbstractAbstract : AbstractClass { }

接口

抽象类其实已经够抽象的了,但还有更加抽象的类,即接口interface。接口太特殊了,一般在命名的时候多以I开头。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface ITest { public void printI(); } public class Test : ITest { public void printI() { Console.WriteLine("interface"); } }

抽象类和接口都是便于被继承的类,二者的区别大致为,抽象类大而全、接口小而专,关于二者的区别,网上说的很精辟了

飞机会飞,鸟会飞,二者都继承了“飞”这个接口;但是F22属于飞机抽象类,鸽子属于鸟抽象类。
另一方面,如果要去买飞机,你必须说明白要买什么飞机(派生类),如果光说我要买飞机,那没人知道你要买什么飞机(抽象类不能实例化)。

virtual

诸如abstract, static, sealed除了可以修饰类之外,也可以修饰类中的方法,而且其含义也是高度相似的:

  • 密封方法像密封类一样,无法被重写
  • 抽象方法只能在抽象类或接口中使用,这在前面已经举例说明了,而已经abstract修饰之后,想要重写就需要用到overide修饰符。
  • 静态方法也和静态类一样,可以在其所在类没有实例化的情况下,直接调用,非常便捷。

静态方法和实例方法在调用上存在差异,暗示着二者在编译期间就存在不同的行为。由于静态方法在类未经实例化的时候就已经可以调用了,所以程序一经编译,就已经分配好了静态方法的内存,这块领地既不可更改也不可销毁;而普通的实例方法则在对象实例化之后紧跟着被创建,对象被回收之后,也就紧跟着被销毁。

除了这几个老面孔之外,基类成员还有一个修饰符virtual,为了理解这个修饰符,先考虑这样一件事,假设B是A的子类,那么我先声明一个A的对象b,然后再将b实例化成B,那么对于被B重写的方法,应该执行哪一个?

说起来很绕,具体看代码如下

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class A { public void print() { Console.WriteLine("A"); } } public class B : A { public void print() { Console.WriteLine("B"); } } class Program { static void Main(string[] args) { A b; b = new B(); b.print(); } }

换言之,A b指明了bA类;但b = new B()却建了个B类,那么b.print()执行谁呢?

结果有些出乎意料,最后输出了B,看来对于方法重写来说,声明比实例化更重要。

但这里有一个吊诡之处,如果声明一个抽象类,然后对抽象类实例化为其子类,再调用子类中实现的抽象方法,最终会执行谁?

这个问题很好回答,抽象方法没实现,必须执行子类的方法,所以这里看似出现了一个矛盾。

但仔细思量却可以发现,被abstract修饰的方法,在子类中是不能直接重写的,而必须用到overide关键字,换言之,方法经overide修饰后,会导致实现声明更重要。

但接下来又有一个问题,abstract不能在常规的类中使用,而且overide不能重载普通方法。就在这个逻辑崩溃的时刻,虚方法virtual出现了。通过virtual修饰的方法,可以被overide重写。

复制代码
1
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
public class A{ public virtual void print(){ Console.WriteLine("A"); } } public class B : A { public void print(){ Console.WriteLine("B"); } } public class C : A{ public override void print(){ Console.WriteLine("C"); } } class Program{ static void Main(string[] args){ A b; A c; b = new B(); b.print(); // 输出 A c = new C(); c.print(); // 输出 C } }

其他修饰符

一般新建一个WPF程序,VS默认的入口类都这么写

复制代码
1
2
public partial class MainWindow : Window

其中partial并没有定义一种新的类,相比之下,更像是个语法糖,允许程序员在两个文件中写同一个类。作为窗口程序,其MainWindow往往特别大,显得partial非常常见。

const表示声明一个常量;readonly表示声明一个只读变量,也叫动态常量。前者在编译时直接被替换为对应常量的值,所以不费内存;后者的值则在运行之后获得,但不可被更改。

const在使用时有一定的限制,即只能修饰基元类型,比如int, string之类的,如果有一个List<int>,则只能通过readonly修饰。一般如果想把一个泛型结构当作永不更改的常量使用,比较习惯的用法是static readonly

C#中的修饰符当然不止这些,但extern用于调用外部程序,尤以调用dll时居多;in, out, new主要应用在泛型方面,volatile, async则与并发编程更为密切,这些内容都不是三言两语说得清的,所以就不放在对OOP的介绍中了。

最后

以上就是干净羽毛最近收集整理的关于C#面向对象进阶:十二大修饰符一网打尽的全部内容,更多相关C#面向对象进阶内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(64)

评论列表共有 0 条评论

立即
投稿
返回
顶部