web-dev-qa-db-ja.com

C#での仮想関数の実用的な使用法

C#での仮想関数の実際の使用法は何ですか?

48
odiseh

基本的に、先祖クラスでメソッドの特定の動作が必要な場合。子孫が同じメソッドを使用しているが実装が異なる場合は、overrideすることができます。virtualキーワード。

using System;
class TestClass 
{
   public class Dimensions 
   {
      public const double pi = Math.PI;
      protected double x, y;
      public Dimensions() 
      {
      }
      public Dimensions (double x, double y) 
      {
         this.x = x;
         this.y = y;
      }

      public virtual double Area() 
      {
         return x*y;
      }
   }

   public class Circle: Dimensions 
   {
      public Circle(double r): base(r, 0) 
      {
      }

      public override double Area() 
      { 
         return pi * x * x; 
      }
   }

   class Sphere: Dimensions 
   {
      public Sphere(double r): base(r, 0) 
      {
      }

      public override double Area()
      {
         return 4 * pi * x * x; 
      }
   }

   class Cylinder: Dimensions 
   {
      public Cylinder(double r, double h): base(r, h) 
      {
      }

      public override double Area() 
      {
         return 2*pi*x*x + 2*pi*x*y; 
      }
   }

   public static void Main()  
   {
      double r = 3.0, h = 5.0;
      Dimensions c = new Circle(r);
      Dimensions s = new Sphere(r);
      Dimensions l = new Cylinder(r, h);
      // Display results:
      Console.WriteLine("Area of Circle   = {0:F2}", c.Area());
      Console.WriteLine("Area of Sphere   = {0:F2}", s.Area());
      Console.WriteLine("Area of Cylinder = {0:F2}", l.Area());
   }
}

編集:コメントの質問
基本クラスで仮想キーワードを使用しない場合、機能しますか?

下位クラスでoverrideキーワードを使用すると、機能しません。コンパイラエラーを生成します CS0506 'function1':「virtual」、「abstract」、または「override」とマークされていないため、継承されたメンバー「function2」をオーバーライドできません

オーバーライドを使用しない場合 CS0108 警告が表示されます。 'desc.Method()'は継承されたメンバー 'base.Method()'を非表示にします。

これを回避するには、メソッドの前にnewキーワードを配置しますhiding

例えば.

  new public double Area() 
  {
     return 2*pi*x*x + 2*pi*x*y; 
  }

..そして派生クラスの仮想メソッドをオーバーライドすることは必須ですか?
いいえ、メソッドをオーバーライドしない場合、子孫クラスは継承元のメソッドを使用します。

82
Johnno Nolan

仮想関数の実際の使用法を理解するための鍵は、特定のクラスのオブジェクトに、最初のオブジェクトのクラスから派生したクラスの別のオブジェクトを割り当てることができることに留意することです。

例えば。:

_class Animal {
   public void eat() {...}
}

class FlyingAnimal : Animal {
   public void eat() {...}
}

Animal a = new FlyingAnimal();
_

Animalクラスには、動物がどのように食べるべきかを一般的に説明するeat()関数があります(たとえば、オブジェクトを口に入れて飲み込む)。

ただし、FlyingAnimalクラスは新しいeat()メソッドを定義する必要があります。これは、空飛ぶ動物には特定の食事方法があるためです。

ここで思い浮かぶ疑問は、変数aAnimal型として宣言し、それにFlyingAnimal型の新しいオブジェクトを割り当てた後、何がa.eat()になるかです。行う? 2つのメソッドのどちらが呼び出されますか?

ここでの答えは:aAnimal型であるため、Animalのメソッドを呼び出します。コンパイラーは愚かで、別のクラスのオブジェクトをa変数に割り当てることを知りません。

virtualキーワードが動作する場所は次のとおりです。メソッドをvirtual void eat() {...}として宣言すると、基本的にコンパイラーに次のように伝えます。あなたはそれほど賢くない」。そのため、コンパイラはa.eat()呼び出しを2つのメソッドのいずれにもリンクしようとしませんが、代わりにシステムに実行を指示します実行時

そのため、コードが実行されるときのみ、システムはaの-​​コンテンツタイプを宣言されたタイプではなく、FlyingAnimalのメソッドを実行します。

あなたは疑問に思うかもしれません:なぜ私はそれをしたいのですか?初めからFlyingAnimal a = new FlyingAnimal()と言ってみませんか?

その理由は、たとえば、AnimalFlyingAnimalSwimmingAnimalBigAnimalWhiteDogなどから多くの派生クラスを使用できるためです多くのAnimalsを含む世界を定義する1つのポイントなので、次のように言います。

_Animal[] happy_friends = new Animal[100];
_

私たちには100匹の幸せな動物がいる世界があります。それらをある時点で初期化します:

_...
happy_friends[2] = new AngryFish();
...
happy_friends[10] = new LoudSnake();
...
_

そして、一日の終わりには、みんなが寝る前に食べてほしい。だからあなたは言いたい:

_for (int i=0; i<100; i++) {
   happy_friends[i].eat();
}
_

あなたが見ることができるように、各動物には独自の食事方法があります。 virtual関数を使用することによってのみ、この機能を実現できます。それ以外の場合は、eatクラス内の最も一般的なAnimal関数で説明されているように、全員がまったく同じ方法で「食べる」ことを余儀なくされます。

編集:この動作は、実際にはJavaのような一般的な高レベル言語ではデフォルトです。

64

他の言語のように..多態性が必要な場合。これにはたくさんの使い方があります。たとえば、コンソール、ファイル、または他のデバイスから入力を読み取る方法を抽象化します。仮想関数を使用して、複数の具体的な実装が続く汎用リーダーインターフェイスを使用できます。

7
Naveen

例えばプロキシメソッド。つまり、実行時にメソッドを上書きします。たとえば、NHibernateはこれを使用して遅延読み込みをサポートします。

これにより、レイトバインディングを実現できます。つまり、コンパイル時ではなく実行時に、どのオブジェクトのメンバーが呼び出されるかを決定します。 Wikipedia を参照してください。

4
Juri

基本的に仮想メンバーを使用すると、ポリモーフィズムを表現できます。派生クラスは、基本クラスのメソッドと同じシグネチャを持つメソッドを持つことができ、基本クラスは派生クラスのメソッドを呼び出します。

基本的な例:

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}
3
CMS

ここ から:

オブジェクト指向プログラミングでは、仮想関数または仮想メソッドは、同じシグネチャを持つ関数によって継承クラス内で動作をオーバーライドできる関数またはメソッドです。

1
Burkhard

たとえば、基本クラスParamsと派生クラスのセットがあります。 paramsから派生したすべての可能なクラスを格納する配列に対して同じ操作を実行できるようにします。

問題ありません-メソッドvirtualを宣言し、いくつかの基本的な実装をParamsクラスに追加して、派生クラスでオーバーライドします。これで、配列を走査し、参照を通じてメソッドを呼び出すことができます-正しいメソッドが呼び出されます。

class Params {
public:
   virtual void Manipulate() { //basic impl here }
}

class DerivedParams1 : public Params {
public:
   override void Manipulate() {
      base.Manipulate();
      // other statements here
   }
};

// more derived classes can do the same

void ManipulateAll( Params[] params )
{
    for( int i = 0; i < params.Length; i++ ) {
       params[i].Manipulate();
    }
 }
1
sharptooth

C#での仮想関数の使用

仮想関数は、主に、同じシグネチャを持つ派生クラスの基本クラスメソッドをオーバーライドするために使用されます。

派生クラスが基本クラスを継承する場合、派生クラスのオブジェクトは、派生クラスまたは基本クラスへの参照です。

仮想関数はコンパイラーによって遅く解決されます(つまり、実行時バインディング)

基本クラスのvirtualでは、ポインターまたは参照の宣言されたタイプに関係なく、参照されたオブジェクトの実際のタイプに従って、最も派生したクラスの関数の実装が呼び出されます。 virtualでない場合、メソッドはearlyに解決され、呼び出された関数は、宣言されたポインターまたは参照の型に従って選択されます。

1
Divya