c#中抽象方法的概念

2017-12-12 19:57:52 czc1997 阅读数 8751

众所周知,C#作为一门OOP(面向对象程序设计)语言,在许多地方都有与C++相似的地方,然而也有很多不同的地方。


说到面向对象,脑袋里第一反应当然就是面向对象的三大原则(java中是四大原则):

封装、继承、多态。java中还包括抽象。在此不做过多讨论。

今天要讨论的虚方法、抽象方法、抽象类、接口所有的一切都是以多态作为基础的,所以让我们聚焦多态————

多态是什么?

多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数实现的。  (摘自百度百科)

用我自己的理解来说:多态就是在继承的前提下,不同对象调用相同方法却表现出不同的行为,此为多态。

关键性的一句话:多态性在C++中是通过虚函数实现的,这在C#中同样适用。但是在C#中有三种方法来体现:虚方法,抽象类,接口。

所谓的虚函数,也就是我们首先要讨论的虚方法。

 

I.虚方法 Virtual
 

虚方法存在于相对于需要实现多态的子类的父类中,同时也是最基本的实现多态的方法。

具体的语法是在父类中用virtual修饰,然后在子类中使用override进行重写。以下是一个简单易懂的例子:猫和狗都是动物,它们都会叫,但是叫声是不一样的。

  1.先定义父类,只定义一个叫做Dosth的方法,代表动物的嚎叫。

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CsharpTest9_10
{
    class Animal
    {
        
        public Animal()
        {
        }
        public virtual void Dosth()
        {
            Console.WriteLine("动物的嚎叫");
        }
    }
}

  2.定义猫类:override重写

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CsharpTest9_10
{
    class cat:Animal
    {
        public override void Dosth()
        {
            base.Dosth();
            Console.WriteLine("喵");
        }
    }
}

3.定义狗类:override重写

 

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CsharpTest9_10
{
    class dog:Animal
    {
        public override void Dosth()
        {
           // base.Dosth();
            Console.WriteLine("汪");
        }
    }
}

在主程序中:

 

 

 cat c1 = new cat();
 dog d1 = new dog();

            c1.Dosth();
            d1.Dosth();

运行结果:

 

 

现在反观结果:我在主程序中调用了猫类重写的父类方法和狗类重写的父类方法。唯一的区别是在猫类中重写方法的时候,在方法体内加入了这样一句:

 

 base.Dosth();

 


因此运行结果中同样输出了一次父类的方法体中的语句。由此我们可以知道,在子类重写方法的时候可以使用base.方法名来实现父类原本的函数功能。

在狗类中我屏蔽了这个语句,所以狗类仅仅输出了自己重写的方法体。

 

通过上面的这个例子,算是对多态已经有了最基本的了解。接下来是由虚方法引出的抽象方法以及抽象类。

 

II.抽象方法以及抽象类 Abstract
 

  通过上面的例子我们知道了虚方法。存在于父类中的虚方法是有自己的方法体的,而且这些方法体是必要的,少了他们就无法完成逻辑,这种情况需要使用虚方法。

然而如果父类中的方法完全不知道去干什么(即方法体中没有必要的代码),必须要子类进行重写才有意义的话,这种情况就需要使用抽象方法。

 

  抽象方法必须存在于抽象类中,抽象类的具体语法是类名前加上abstract。抽象方法的语法实例如下:public abstract void FUN();

  仍然使用上面的例子。父类(Animal)中的Dosth方法中,并没有必要的代码,即使方法里面什么都不写对子类仍然没有什么影响。这种情况就可以使用抽象方法和抽象类。

首先需要注意的是:抽象方法没有方法体,且所有继承了抽象类的子类必须重写所有的抽象方法。

父类:

 

namespace CsharpTest9_10
{
    abstract class Animal
    {
        public abstract void Dosth();     
    }
}


子类的代码不变。但是此时就不能使用base.方法体了,因为根本就不存在方法体。

 

 

抽象类中可以包括普通方法,并且抽象类不能被实例化。

抽象类的使用场景:

1.父类方法不知道如何去实现;

2.父类没有默认实现且不需要实例化

 

总的来说,抽象方法和虚方法的差别并不是很大,实现的功能都差不多。抽象类保证了每个抽象方法都必须得到重写,我们就要根据实际需要来选择对应的方法。

III.接口 Interface

  同样的,接口是从抽象类演变而来的————如果抽象类中的所有方法都是抽象方法,这个抽象类就可以叫做接口。当然,接口中的所有方法都不能有方法体。

接口中不能包含字段,但是可以包含属性。这里没有字段怎么编写属性呢?这里有一个自动属性的概念,我们将在别的博文中进行讲解。

接口中的所有成员都默认为public,这是不能被修改的,自己也不能写上去。

我们可以将接口想象为一个插件,可以用来实现一些附加功能。

 

代码还是使用上一个例子中的那份,首先定义接口:在program中右键新建项,选择接口。


定义如下:

 

namespace CsharpTest9_10
{
    interface Interface1
    {
        void Eat();
    }
}

 

 

 

 

 

在猫类中的接口调用:

 

namespace CsharpTest9_10
{
    class cat : Animal,Interface1
    {
        public override void Dosth()
        {
            //base.Dosth();
            Console.WriteLine("喵");
        }

        public void Eat()
        {
            Console.WriteLine("猫在进食");
        }
    }
}


狗类同理。

 

我们可以看到调用的语法就是在继承的父类后加上一个逗号,再写接口名即可。

此时在接口名上右键后点击实现接口,会自动生成接口中方法的实现。

 


接口的作用就是实现某些类的特殊功能。

 

总结

三者之间的关系,我用一张图来表示。

 

 

2019-01-21 21:22:33 qq_42351033 阅读数 632


先看一句话
C#允许把类和方法声明为abstract,即抽象类和抽象方法。
抽象类通常代表一个抽象的概念,他提供一个继承的出发点,当设计一个新的抽象类时,一定是用来继承的。所以在一个继承关系形成的等级结构中,树叶节点应当为具体类,而树枝节点均应当为抽象类。–摘自《大话设计模式》

这是比较官方的说法,下面用我自己的语言来描述下

一:什么是抽象类、抽象方法

在 C# 中,使用关键字 abstract 来定义抽象类和抽象方法。

二:抽象类的特性

抽象类是一种特殊的基础类,并不与具体的事物联系。它有以下特性

1:抽象类不能实例化

验证如下:

public abstract class Car
{

}

在这里插入图片描述

2:抽象类可以包含抽象方法和抽象访问器

验证代码如下:

//抽象方法
public abstract void Run();
//抽象访问器
public abstract string Name { get; }

3:不能用 sealed 关键字修饰抽象类

通常情况下,我们会把抽象类视为公共基类,如果使用了sealed 修饰符,这意味着抽象类将不能被继承
验证如下:
在这里插入图片描述

4:从抽象类派生的非抽象类,必须包括继承的所有抽象方法和抽象访问器的实现

验证如下:
在这里插入图片描述

三:抽象方法的特性

一个抽象方法通俗易懂的说就是一个没有方法体的方法。它有以下特性
1):抽象方法是隐式的虚方法。
2):只允许在抽象类使用抽象方法声明。
3):因为抽象方法声明不提供实际的实现,所有没有方法体。方法声明只是以一个分号结束,并且在签名后没有大括号,实现由一个重写方法提供,此重写方法是非抽象类的成员。
4):在抽象方法声明中使用static或virtual修饰符是错误的。
5):除了在声明和调用语法上不同外,抽象属性的行为与抽象方法一样。
6):在静态属性上使用abstract修饰符是错误的。
7):在派生类中,通过包括使用 override 修饰符的属性声明,可以重写抽象的继承属性。

下面两种抽象方法的写法都是可以的

//抽象方法
public abstract void Run();
abstract public void Stop();

只不过将 abstract 放到访问修饰符前面时会提示你“修饰符声明顺序不一致”,但这样写也没错,只不过我喜欢第一种写法,看着舒服。
另外,抽象方法不能设置为私有的(因为抽象方法写出来就是为了子类继承实现的)
验证如下
在这里插入图片描述

四:抽象方法与抽象类之间的关系

有抽象方法的类必然是抽象类,
但是,抽象类中的方法并不一定都是抽象方法。

五:抽象类与一般类的相同点及不同点

相同点:
都可以继承其它的类或者接口,也可以派生子类,并且都有具体的方法;
不同点:
抽象类中有抽象方法,一般类中没有;
抽象类不可以实例化,一般类却可以;

当然,概念毕竟只是概念,只能让我们简单的认识抽象类及抽象方法,拿例子分析会更好点,所以这里我补充了一篇 C# 抽象类的简单实现 的博客,希望可以帮到你


结束语

如果这篇博客有幸帮到了您,欢迎点击下方链接,和更多志同道合的伙伴一起交流,一起进步。

Web开发者俱乐部

2017-03-13 16:56:01 ycc449 阅读数 381

浅谈C#抽象方法、虚方法、接口
自己理解:
Interface(接口):是一种规则,要求继承类必需实现所有声明的成员。
Virtual方法(虚方法):可以在继承类里 Override覆盖重新的方方法,有自己的方法体。派生类可使用,可重写。
Abstract(抽象方法):只能在抽象类种修饰,并且没有具体的实现,抽象方法在派生类种 使用Override重写。

上帝(程序员)定义了一种行为:飞(接口)
那么什么可以飞呢?鸟、飞机(抽象类)
具体什么鸟呢?麻雀(实例化)。继承 抽象类 (鸟)、接口(飞)
具体什么飞机呢?空客320(实例化)。继承 抽象类(飞机)、接口(飞)
如果实例化麻雀只继承了抽象类 (鸟),没有继承接口(飞)。那么抱歉,这个麻雀不能飞。
上帝觉得都是一样的麻雀太没意思,加入很多虚方法,那么这个麻雀的子子孙孙,想和父亲一样就样,不想的化就进化(Override),于是世界上多了很多不一样的麻雀…..。

2011-10-05 20:19:36 wuyanyi 阅读数 5966
第四章 继承
4.1 继承的类型
4.1.1 实现继承和接口继承
● 实现继承:表示一个类型派生于一个基类型,拥有该基类型的所有成员字段和函数。
● 接口继承:表示一个类型只继承了函数的签名,没有继承任何实现代码。
接口继承常被看做一种契约:
让类型派生于接口,来保证为客户提供某个功能。
4.1.2 多重继承
csharp不支持多重实现继承。
但同时又允许类型派生于多个接口。
4.1.3 结构和类
结构不支持实现继承,但支持接口继承。


4.2 实现继承
public class MyDerivedClass : MyBaseClass, IInterface1,
IInterface2
{
//etc.
}
*csharp不支持私有继承,因此基类名上并没用public或private限定符。
*csharp支持object关键字,它用作System.Object类的假名。


4.2.1 虚方法
把基类函数声明为virtual,该函数就可以在派生类中重写。
class MyBaseClass
{
public virtual string VirtualMethod()
{
return "This method is virtual and defined in MyBaseClass";
}
}
也可以声明属性为virtual。
public virtual string ForeName
{
get { return foreName; }
set { foreName = value; }
}
private string foreName;
以下的讨论虽然都是对方法来说,但是也适用于属性。
*成员字段和静态函数都不能声明为virtual,因为这个概念只对类中的实例函数成员有意义。


4.2.2 隐藏方法
即c++中派生类和基类函数名相同时,且他们都没用声明virtual和override,则会同名覆盖。
但是在c#中,应该在派生类中对该新的重写函数添加关键字new前缀。
否则编译器会发出一个警告。


4.2.3 调用函数的基类版本
csharp的一个特殊语法可以用于从派生类中调用方法的基类版本:
base.function();
*这个语法可以调用基类中的任何方法。


4.2.4 抽象类和抽象函数
(类似c++的纯虚函数)
关键字abstract
c++开发人员要注意csharp中的一些语法区别。
csharp不能采用=0语法来声明抽象函数。
在csharp中,这个=<value>语法可以在类声明的成员字段上使用,提供初始值。




4.2.5 密封类和密封方法
关键字sealed
表示不能继承该类或该方法。
.NET基类库大量使用密封类。比如string。


4.2.6 派生类的构造函数
构造函数的调用顺序是先调用System.Object,
再按照层次结构由上向下进行,直到到达编译器要实例化的类为止。
*因为基类的构造函数总是最先调用。
故派生类的构造函数可以在执行过程中调用基类的成员,因为基类已经构造出来了。
public abstract class GenericCustomer
{
private string name;
public GenericCustomer()
: base() // we could omit this line without affecting the compiled code
{
name = "<no name>";
}
注意基类构造函数的调用base()
如果没加这个调用,编译器则在起始花括号的前面找不到对另一个构造函数的任何调用,
它会假定我们要调用基类构造函数。
这符号默认构造函数的工作方式。
*base和this关键字是调用另一个构造函数时允许使用的唯一关键字。
*还要注意只能制定一个其他的构造函数。


其它规则其实都和c++差不多。
都是定义派生类的同时要注意对基类构造函数的调用。
心细即可,原理不难。


4.3 修饰符


4.3.1 可见性修饰符
public。
private。
protected。
internal:只能在包含它的程序集中访问该方法。


4.3.2 其他修饰符
*注意一个extern,应用于仅静态[DllImport]方法。
说明成员在外部用另一种语言实现。


4.4 接口


下面是microsoft预定义的一个接口System.IDisposable的完整定义:
public interface IDisposable
{
void Dispose();
}
可看出,声明接口在语法上合声明抽象类完全相同。
但不允许提供接口中任何成员的执行方式。
接口不能有构造函数或字段。
接口定义也不允许包含运算符重载。
因为接口通常是公共契约,包含运算符重载会引起一些与其他.NET语言不兼容的问题。
接口成员总是公共的,不能声明为虚拟或静态。
如果需要最好通过执行的类来声明访问修饰符,像上例中的public。


接口的另一个例子是foreach循环。
foreach循环的内部工作方式是查询对象,看看它是否实现了System.Collections.IEnumerable 接口。


4.4.1 定义和实现接口


注意接口的名称传统上已字幕I开头(interface)。
namespace Wrox.ProCSharp
{
public interface IBankAccount
{
void PayIn(decimal amount);
bool Withdraw(decimal amount);
decimal Balance
{
get;
}
}
}


*接口引用完全可以看做是类引用--但接口引用的强大之处在于,它可以引用任何实现该接口的类。
IBankAccount[] accounts = new IBankAccount[2];
accounts[0] = new SaverAccount();
accounts[1] = new GoldAccount();


4.4.2 派生的接口
接口可以彼此继承,方式与类的继承相同。
2014-02-26 14:55:44 zjx86320 阅读数 2681

        一、抽象类和抽象方法的概念

        在基类中的虚方法有时候不可能被调用到,而只是表达一种抽象的概念,用以为它的派生类提供一个公共的界面。 C#中引入了抽象类(abstract class)的概念,可以将它定义成抽象方法。将该方法所在的类定义成抽象类

         抽象方法:只包含方法定义,但没有具体实现的方法,需要其子类或者子类的子类来具体实现。

         抽象类:抽象类是能够包含抽象成员的类。抽象类只能作为基类使用,不能被实例化。

        二、抽象类和抽象方法的使用原则

        1  抽象类中可以存在非抽象方法,抽象方法必须包含在抽象类中。

        2  实现抽象方法用override关键字。如果子类没有实现抽象基类中所有的抽象方法,则子类也必须定义成一个抽象类。

        3  抽象方法被实现后,不能更改修饰符。

        4  抽象类可以被抽象类所继承,结果仍是抽象类。

        5  所有抽象的方法,在派生类中必须被实现。

         下面就让我们通过一个例子来认识一下抽象类吧:

       

   abstract class shapleclass    //抽象类的声明
    {
      abstract public void area();  //抽象方法,只是声明没有实际的实现,所以只是以分号结束,并且在签名后没有大括号
        public void fun()       //成员函数
        {
            Console.WriteLine("这是一个非抽象方法!");
        }

    }
    
    class shap:shapleclass

    {

        public override void area()   //所有派生于抽象类的方法,必须实现抽象类中的抽象方法
        {
            Console.WriteLine("这是一个抽象方法");
        }
    }

 

 class Program
    {
        static void Main(string[] args)
        {
            shap  circle = new shap();
            circle.fun();
            circle.area();
        }
    }


        运行结果如下: