試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

C#言語とVala言語におけるクラス継承時の親クラスのコンストラクタの呼び出しに関する挙動の違いについて

Vala言語はC#言語に近い文法を持つが、クラスを継承したときの親クラスのコンストラクタの呼び出しに関する挙動は異なる。

  1. 親クラスの引数なしコンストラクタはVala言語では自動的には呼ばれない
  2. 親クラスのコンストラクタを引数付きで呼ぶ
  3. Vala言語で親クラスの引数なしコンストラクタを呼ぶ

親クラスの引数なしコンストラクタはVala言語では自動的には呼ばれない

下のコードではクラスTestClass1を継承したTestClass2を定義し、更にこれを継承したTestClass3を定義している。
[任意]ファイル名: InheritanceTest1.cs

using System;

/*
 * gmcs -out:InheritanceTest1.exe InheritanceTest1.cs
 */

namespace InheritanceTest1
{
  class TestClass1
  {
    protected int a;  // 「protected」は外部クラスから参照不可かつ継承可能
    public TestClass1 ()
    {
      Console.WriteLine ("TestClass1(): a = 50");
      this.a = 50;
    }
    public virtual void Show ()  // virtualを付けたメンバ関数は子クラスで再定義可能
    {
      Console.WriteLine ("Show(): a:{0}", this.a);
    }
  }
  class TestClass2 : TestClass1
  {
    protected int b;
    public TestClass2 ()
    {
      Console.WriteLine ("TestClass2(): b = 3");
      this.b = 3;
    }
    public override void Show ()  // bも表示するものを再定義
    {
      Console.WriteLine ("Show(): a:{0}, b:{1}", this.a, this.b);
    }
  }
  class TestClass3 : TestClass2
  {
    int c;
    public TestClass3 ()
    {
      Console.WriteLine ("TestClass3(): c = 999");
      this.c = 999;
    }
    public override void Show ()
    {
      Console.WriteLine ("Show(): a:{0}, b:{1}, c:{2}", this.a, this.b, this.c);
    }
  }
  class MainClass
  {
    public static void Main (string[] args)
    {
      TestClass1 ins1 = new TestClass1 ();
      ins1.Show ();
      Console.WriteLine ("-----");
      TestClass1 ins2 = new TestClass2 ();
      ins2.Show ();
      Console.WriteLine ("-----");
      TestClass1 ins3 = new TestClass3 ();
      ins3.Show ();
    }
  }
}

下は実行結果。C#言語では自動的に親クラスのコンストラクタが呼ばれていることが分かる。ただし、これは親クラスのコンストラクタへの引数がない場合にのみ自動で呼ばれるという仕組みによるもので、引数を取る場合は後述のbaseキーワードを用いる。

TestClass1(): a = 50              
Show(): a:50
-----
TestClass1(): a = 50
TestClass2(): b = 3
Show(): a:50, b:3
-----
TestClass1(): a = 50
TestClass2(): b = 3
TestClass3(): c = 999
Show(): a:50, b:3, c:999

同じような形のコードをVala言語で書くと
[任意]ファイル名: inheritancetest1.vala

using GLib;

/*
 * valac -o inheritancetest1 inheritancetest1.vala
 */

namespace InheritanceTest1
{
  class TestClass1
  {
    protected int a;  // 「protected」は外部クラスから参照不可かつ継承可能
    public TestClass1 ()
    {
      print ("TestClass1(): a = 50\n");
      this.a = 50;
    }
    public virtual void show ()  // virtualを付けたメンバ関数は子クラスで再定義可能
    {
      print ("show(): a:%d\n", this.a);
    }
  }
  class TestClass2 : TestClass1
  {
    protected int b;
    public TestClass2 ()
    {
      print ("TestClass2(): b = 3\n");
      this.b = 3;
    }
    public override void show ()  // bも表示するものを再定義
    {
      print ("show(): a:%d, b:%d\n", this.a, this.b);
    }
  }
  class TestClass3 : TestClass2
  {
    int c;
    public TestClass3 ()
    {
      print ("TestClass3(): c = 999\n");
      this.c = 999;
    }
    public override void show ()
    {
      print ("show(): a:%d, b:%d, c:%d\n", this.a, this.b, this.c);
    }
  }
  class MainClass
  {
    public static void main (string[] args)
    {
      TestClass1 ins1 = new TestClass1 ();
      ins1.show ();
      print ("-----\n");
      TestClass1 ins2 = new TestClass2 ();
      ins2.show ();
      print ("-----\n");
      TestClass1 ins3 = new TestClass3 ();
      ins3.show ();
    }
  }
}

となり、実行結果は

TestClass1(): a = 50
show(): a:50
-----
TestClass2(): b = 3
show(): a:0, b:3
-----
TestClass3(): c = 999
show(): a:0, b:0, c:999

のようになっていて、親クラスのコンストラクタは呼ばれていない。このコードを上のC#のコードのように動作させるための書き方については後述。
なお、コード中のコメントにも書いているが、(親クラス内の)メンバの定義に修飾子virtualを付けると、継承した子クラス側で修飾子overrideを付けることで同名のメンバを再定義(オーバーライド)できる。これはC#とValaで共通している。また、修飾子protectedの付いたメンバはクラス継承した先(子クラス)でも使えると同時に外部からは直接参照できないようにできる。

親クラスのコンストラクタを引数付きで呼ぶ

C#言語では親クラスのコンストラクタに引数を渡す場合には子クラスのコンストラクタを

public [クラス名] ([引数...]) : base ([親クラスのコンストラクタへの引数...])
{
  (処理...)
}

として定義する。一方でVala言語では

public [クラス名] ([引数...])
{
  base ([親クラスのコンストラクタへの引数...]);
  (処理...)
}

と記述する。
[任意]ファイル名: InheritanceTest2.cs

using System;

/*
 * gmcs -out:InheritanceTest2.exe InheritanceTest2.cs
 */

namespace InheritanceTest2
{
  class TestClass1
  {
    protected int a;  // 「protected」は外部クラスから参照不可かつ継承可能
    public TestClass1 (int a)
    {
      Console.WriteLine ("TestClass1(): a = {0}", a);
      this.a = a;
    }
    public virtual void Show ()  // virtualを付けたメンバ関数は子クラスで再定義可能
    {
      Console.WriteLine ("Show(): a:{0}", this.a);
    }
  }
  class TestClass2 : TestClass1
  {
    protected int b;
    public TestClass2 (int a, int b) : base (a)
    {
      Console.WriteLine ("TestClass2(): b = {0}", b);
      this.b = b;
    }
    public override void Show ()  // bも表示するものを再定義
    {
      Console.WriteLine ("Show(): a:{0}, b:{1}", this.a, this.b);
    }
  }
  class TestClass3 : TestClass2
  {
    int c;
    public TestClass3 (int a, int b, int c) : base (a, b)
    {
      Console.WriteLine ("TestClass3(): c = {0}", c);
      this.c = c;
    }
    public override void Show ()
    {
      Console.WriteLine ("Show(): a:{0}, b:{1}, c:{2}", this.a, this.b, this.c);
    }
  }
  class MainClass
  {
    public static void Main (string[] args)
    {
      TestClass1 ins1 = new TestClass1 (1);
      ins1.Show ();
      Console.WriteLine ("-----");
      TestClass1 ins2 = new TestClass2 (2, 3);
      ins2.Show ();
      Console.WriteLine ("-----");
      TestClass1 ins3 = new TestClass3 (4, 5, 6);
      ins3.Show ();
    }
  }
}

上のコードの実行結果は下のようになる。

TestClass1(): a = 1
Show(): a:1
-----
TestClass1(): a = 2
TestClass2(): b = 3
Show(): a:2, b:3
-----
TestClass1(): a = 4
TestClass2(): b = 5
TestClass3(): c = 6
Show(): a:4, b:5, c:6

[任意]ファイル名: inheritancetest2.vala

using GLib;

/*
 * valac -o inheritancetest2 inheritancetest2.vala
 */

namespace InheritanceTest2
{
  class TestClass1
  {
    protected int a;  // 「protected」は外部クラスから参照不可かつ継承可能
    public TestClass1 (int a)
    {
      print ("TestClass1(): a = %d\n", a);
      this.a = a;
    }
    public virtual void show ()  // virtualを付けた関数は子クラスで再定義可能
    {
      print ("show(): a:%d\n", this.a);
    }
  }
  class TestClass2 : TestClass1
  {
    protected int b;
    public TestClass2 (int a, int b)  // ここには何も書かない
    {
      base (a);  // 親クラスのコンストラクタにaを渡す
      print ("TestClass2(): b = %d\n", b);
      this.b = b;
    }
    public override void show ()  // bも表示するものを再定義
    {
      print ("show(): a:%d, b:%d\n", this.a, this.b);
    }
  }
  class TestClass3 : TestClass2
  {
    int c;
    public TestClass3 (int a, int b, int c)
    {
      base (a, b);
      print ("TestClass3(): c = %d\n", c);
      this.c = c;
    }
    public override void show ()
    {
      print ("show(): a:%d, b:%d, c:%d\n", this.a, this.b, this.c);
    }
  }
  class MainClass
  {
    public static void main (string[] args)
    {
      TestClass1 ins1 = new TestClass1 (1);
      ins1.show ();
      print ("-----\n");
      TestClass1 ins2 = new TestClass2 (2, 3);
      ins2.show ();
      print ("-----\n");
      TestClass1 ins3 = new TestClass3 (4, 5, 6);
      ins3.show ();
    }
  }
}

上のコードの実行結果は下のようになる。

TestClass1(): a = 1
show(): a:1
-----
TestClass1(): a = 2
TestClass2(): b = 3
show(): a:2, b:3
-----
TestClass1(): a = 4
TestClass2(): b = 5
TestClass3(): c = 6
show(): a:4, b:5, c:6

Vala言語で親クラスの引数なしコンストラクタを呼ぶ

本記事中のinheritancetest1.valaとして貼り付けたコードでは親クラスのコンストラクタは呼ばれていないが、この場合も「base();」をコンストラクタの中に記述することで呼ぶことができる。下に修正を差分形式で貼り付ける。

--- inheritancetest1.vala
+++ inheritancetest1-2.vala
@@ -24,6 +24,7 @@
     protected int b;
     public TestClass2 ()
     {
+      base ();
       print ("TestClass2(): b = 3\n");
       this.b = 3;
     }
@@ -37,6 +38,7 @@
     int c;
     public TestClass3 ()
     {
+      base ();
       print ("TestClass3(): c = 999\n");
       this.c = 999;
     }

これを実行すると

TestClass1(): a = 50
show(): a:50
-----
TestClass1(): a = 50
TestClass2(): b = 3
show(): a:50, b:3
-----
TestClass1(): a = 50
TestClass2(): b = 3
TestClass3(): c = 999
show(): a:50, b:3, c:999

となる。

使用したバージョン:

  • Mono 1.9.1
  • Vala 0.7.3