问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

一次学会C#十二大修饰符

创作时间:
作者:
@小白创作中心

一次学会C#十二大修饰符

引用
CSDN
1.
https://blog.csdn.net/m0_37816922/article/details/129086538

本文详细介绍了C#编程语言中的十二大修饰符,包括访问修饰符、类修饰符、接口以及虚拟方法等。通过代码示例和详细解释,帮助读者理解这些修饰符的使用场景和区别。

访问修饰

在声明类成员和方法的时候,往往在类型前面加一个修饰符,比如publicprivate,用以标识外部是否可以访问,此即访问修饰符。C#一共有6种访问修饰符,这些修饰符当然在类内都是可以访问的,但是在类外,根据是否为派生类、是否在相同程序集,共分为四种特殊情况。

  • public:公共属性,任何时候都可以访问
  • private:私有属性,除了类内,其他人都不可以访问
  • protected:只有派生类可以访问
  • internal:只有相同的程序集可以访问
  • private protected:在protected的基础上缩减一步,只允许相同程序集的派生类访问
  • protected internal:在protected的基础上拓展一步,还允许不同程序集的派生类访问

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

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 class StaticClass
{
    public static void printStatic()
    {
        Console.WriteLine("static class");
    }
}
class Program
{
    static void Main(string[] args)
    {
        StaticClass.printStatic();  
    }
}

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

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

public abstract class AbstractClass
{
    // 用abstract修饰之后,不可实现
    public abstract void printSubAbstract();
    // 不用abstract修饰,必须写细节
    public void printAbstract()
    {
        Console.WriteLine("Abstract");
    }
}

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

public class SubAbstract : AbstractClass
{
    public override void printSubAbstract()
    {
        Console.WriteLine("Abstract");
    }
}
// 如果没有abstract修饰,是会报错的
public abstract class AbstractAbstract : AbstractClass
{ }

接口

interface即接口,是比抽象类更加抽象的类修饰符,因其太过特殊,命名时多以I开头。

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重写的方法,应该执行哪一个?

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

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重写。

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默认的入口类都这么写

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的介绍中了。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号