Ruby 脳による C# 覚書き

一週間で身につくC#言語の基本|トップページ~C#言語の初心者でも、簡単にプログラミングが気軽に学習できるサイトです。
このサイトを pdf化したものを一時間で読んだメモ。超基本のみ。

全体的に

  • データ型は C とだいたい同じ。「object」や「string」というのもあるが。
  • 変数はアルファベットの小文字で始まり、メソッド名やクラス名は大文字で始まるという慣例。
  • 基本的にオブジェクト指向
  • 分岐処理。if, if else, else if, switch という感じ。
  • 繰り返し(ループ)処理。while, do while, for, foreach という感じ。
  • コレクション型。配列やハッシュなど。
  • ポインタもあるけれど、上記サイトでは扱っていない。

クラス

インスタンスの生成。引数はあってもなくても両方に対応できる。

p = new Person();

Ruby ではインスタンスにドットで後置されるのはメソッドだが、C# にはメソッド以外に「フィールド」という概念がある。

class Person
{
    public string name = "";
    public int age = 0;
    public void ShowAgeAndName()
    {
        Console.WriteLine("名前:{0} 年齢:{1}", name, age);
    }
    public void SetAgeAndName(string name, int age)
    {
        this.name = name;
        this.age = age;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person p1, p2;
        p1 = new Person();
        p2 = new Person();
        p1.name = "山田太郎";
        p1.age = 19;
        p2.SetAgeAndName("佐藤花子", 23);
        p1.ShowAgeAndName();
        p2.ShowAgeAndName();
    }
}

この name や age がフィールドで、「this.name = name;」は Ruby でいうと name がインスタンス変数だということ(最初に型宣言が必要)。ShowAgeAndName() と SetAgeAndName() がインスタンス・メソッド。

上のコードは、いわゆる「セッター/ゲッター・メソッド」を使って次のように書くこともできる。Ruby でいうと attr_accessor, attr_reader, attr_writer に相当する。これを C# では「プロパティ」と呼んでいる。Person クラスだけ書き直してみる。

class Person
{
    private string name = "";
    private int age = 0;
    public void SetAgeAndName(string name, int age)
    {
        this.name = name;
        this.age = age;
    }
    public void ShowAgeAndName()
    {
        Console.WriteLine("名前:{0} 年齢:{1}", name, age);
    }
    public string Name
    {
        set { name = value;  }
        get { return name;  }
    }
    public int Age
    {
        set { age = value; }
        get { return age; }
    }
}

Name と Age がプロパティ(セッター/ゲッター・メソッド)になっている。なお、インスタンス変数に代入(セット)するときは、必ず value という名前の「変数」を使う。なお、ここでは name と age に「private」が付けてあり、クラスの外から見えないようにしてある(カプセル化)。


同名でも引数の数や型戻り値の型がちがっているインスタンス・メソッドは、別のものとして取り扱われ、別に定義することができる。これを「オーバーロード」という。Ruby ではこのようなことはない(名前が重複した時点で、メソッドが再定義されてしまう)。


ここまではコンストラクタ(Ruby でいう initialize メソッド)は使っていない。C# では、クラス名と同じ名前のメソッドを定義すると、それがコンストラクタになる。コンストラクタはオーバーロードできる。

class Person
{
    private string name = "";
    private int age = 0;
    public Person() : this("名無し", 0)
    {
        Console.WriteLine("引数なしコンストラクタ");
    }
    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
        Console.WriteLine("引数ありコンストラクタ name:{0} age:{1}", name, age);
    }
    public void ShowAgeAndName()
    {
        Console.WriteLine("名前:{0} 年齢:{1}", name, age);
    }
    public string Name
    {
        set { name = value; }
        get { return name; }
    }
    public int Age
    {
        set { age = value; }
        get { return age; }
    }
}

ここでは Person() と Person(string name, int age) がコンストラクタである。二種類あってオーバーロードされている。
それから、「Person() : this("名無し", 0)」の this は何を表しているのか。これは同名の(オーバーロードされた)メソッドで、引数の数が合致するもの、ここでは Person(string name, int age) を最後に呼び出しているのだ。このようなことは(オーバーロードのない)Ruby には存在しない。

静的メンバ

フィールドとメソッドはインスタンスを必要としたが、インスタンスを必要としないフィールドやメソッドがある。これらはそれぞれ「静的フィールド」「静的メソッド」と呼ばれる。Ruby のクラスメソッドに相当する。まとめて「静的メンバ」と呼ばれ、static 修飾子を頭につける。

class Data
{
    private static int num = 0;
    private int id;
    public Data(int id)    //コンストラクタ
    {
        this.id = id;
        num++;
        Console.WriteLine("値:{0} 数:{1}", this.id, num);
    }
    public static void ShowNumber()
    {
        Console.WriteLine("Dataオブジェクトの数:{0}", num);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Data[] d = new Data[2];
        Data.ShowNumber();
        for (int i = 0; i < d.Length; i++)
        {
            d[i] = new Data(i*100);
            Data.ShowNumber();
        }
    }
}

Data.ShowNumber() のメソッド部分が静的メソッドである。クラス名の Data がインスタンスのように振舞っている。
また、num が静的フィールドである。これは本当は Data.num なのだが、同一クラス内なのでクラス名が省略できる。

継承

C# は単一継承。すべてのクラスは Object クラスを継承している。

class ExCalculator : Calculator

ExCalculator が子クラス、Calculator が親クラス(Rubyスーパークラス)。

class Program
{
    static void Main(string[] args)
    {
            //  Parentクラスのインスタンス生成
        Parent p = new Parent();
            //  Childクラスのインスタンス生成
        Child c = new Child();
            //  それぞれのクラスのfoo、barメソッドを実行
        p.Foo();
        c.Foo();
    }
}

class Parent
{
    public virtual void Foo()
    {
        Console.WriteLine("親クラスのFoo()メソッド");
    }
}

class Child : Parent
{
    public override void Foo()
    {
        Console.WriteLine("子クラスのFoo()メソッド");
    }
}

Child クラスは Parent クラスを継承していて、両者に同名で、かつ引数の組み合わせと戻り値の型が同じメソッドがある。この場合親クラスのメソッドの方には virtual を付け、子クラスのメソッドの方には override を付ける。両者はそれぞれのインスタンスに応じて、別のメソッドを呼び出す。これを「オーバーライド」という。いわゆる「ポリモーフィズム」のことである。

抽象クラス

class Program
{
    static void Main(string[] args)
    {
        Crow c = new Crow();          //  カラスクラス
        Sparrow s = new Sparrow();    //  すずめクラス
        Console.Write(c.Name + " : ");
        c.Sing();
        Console.Write(s.Name + " : ");
        s.Sing();
    }
}

class Crow : Bird
{
    public Crow() : base("カラス") {}
    public override void Sing()
    {
        Console.WriteLine("カーカー");
    }
}

class Sparrow : Bird
{
    public Sparrow() : base("スズメ") {}
    public override void Sing()
    {
        Console.WriteLine("チュンチュン");
    }
}

abstract class Bird
{
    private String name;
    public Bird(String name)
    {
        this.name = name;
    }
    public String Name
    {
        get { return name; }
    }
    public abstract void Sing();       
}

カラスもスズメも鳥で、鳴くことには変わりがない。なので、Crow クラスと Sparrow クラスの親として Bird クラスを作り、共通する名称の Sing() メソッドはそれぞれの子クラス内で処理する。この Bird クラスを「抽象クラス」と呼び、abstract修飾子を付ける。また、Bird クラスの Sing() メソッドにも abstract修飾子が付いている。これを「抽象メソッド」と呼ぶ。同様に「抽象プロパティ」も作ることができる。
「public Crow() : base("カラス")」の base() は Bird クラスのコンストラクタを呼び出す。インスタンス変数の name は Name プロパティで共有され、Main の中で呼び出されている。
なお、抽象クラスはインスタンスを生成することができない。しかし、変数として値を保持することはできる。ただし、インスタンスとしてはサブクラスのそれしか使えない。

class Program
{
    static void Main(string[] args)
    {
        Bird[] b = new Bird[2];    //変数として保持
        b[0] = new Crow();         //Crow クラスのインスタンス
        b[1] = new Sparrow();      //Sparrow クラスのインスタンス
        for(int i = 0; i < b.Length ; i++){
            Console.Write(b[i].Name + " : ");
            b[i].Sing();
        }
    }
}

Ruby には抽象クラスの考え方がなく、いわゆる「ダックタイピング」を使う。Ruby ならこのように書ける。これが Ruby のおもしろいところだ。

class Bird
  def initialize(name)
    @name = name
  end
  attr_reader :name

  def sing
    print self.name + " : "
    bird_sing
  end
end

class Crow < Bird
  def bird_sing
    puts "カーカー"
  end
end

class Sparrow < Bird
  def bird_sing
    puts "チュンチュン"
  end
end

Crow   .new("カラス").sing
Sparrow.new("スズメ").sing

インターフェイス

class Program
{
    static void Main(string[] args)
    {
        CellPhone cp = new CellPhone("hoge@email.com", "090-1234-5678");
        cp.Call("011-123-4567");
        cp.SendMail("fuga@email.com");
        IPhone phone = (IPhone)cp;
        phone.Call("011-987-6543");
        //phone.SendMail("foo@email.com");        //  メールの送信メソッドは利用できない。
        IEmail mail = (IEmail)cp;
        mail.SendMail("bar@email.com");
        //mail.Call("011-222-3333");    //  mailインターフェースでは、電話の機能を利用できない。
    }
}

class CellPhone : IPhone, IEmail
{
    private string mailAddress;
    private string number;
    public CellPhone(string mailAddress, string number)
    {
        this.mailAddress = mailAddress;
        this.number = number;
    }
    public void SendMail(string address)
    {
        Console.WriteLine(address + "に、" + this.mailAddress + "からメールを出します。");
    }
    public void Call(string number)
    {
        Console.WriteLine(number + "に、" + this.number + "から電話をかけます。");
    }
}

interface IPhone
{
    void Call(string number);
}

interface IEmail
{
    void SendMail(string address);
}

インターフェイスは抽象クラスと似ていて、ここで定義されたメソッドをクラスで実装しなければならない。ただし、インターフェイスはメソッドやフィールドの実装を持つことができない。
「class CellPhone : IPhone, IEmail」のように、インターフェイスはクラスとはちがい、複数持つことができる。
IPhone phone = (IPhone)cp;」「IEmail mail = (IEmail)cp;」などをインスタンスの「キャスト」という。キャストされると、もとは同じクラスに属していたインスタンスが、キャストした型のメンバしか使えなくなる。一種の型変換といえる。

インターフェイスはもちろん抽象クラスとはちがう。メソッドの重複を許す。

class Program
{
    static void Main(string[] args)
    {
        Dummy d = new Dummy();
        IFuncs1 i1 = (IFuncs1)d;
        IFuncs2 i2 = (IFuncs2)d;
        i1.Func1();
        i1.Func2();
        i2.Func2();
        i2.Func3();
    }
}

class Dummy : IFuncs1,IFuncs2
{
    public void Func1()
    {
        Console.WriteLine("Func1");
    }
    public void Func2()
    {
        Console.WriteLine("Func2");
    }
    public void Func3()
    {
        Console.WriteLine("Func3");
    }
}

interface IFuncs1
{
    void Func1();
    void Func2();
}

interface IFuncs2
{
    void Func2();
    void Func3();
}

Ruby だとこれは「移譲」を使う。移譲を簡単に扱うモジュール 'forwardable' が、標準添付ライブラリに入っている。

require 'forwardable'

class CellPhone
  def initialize(mail_address, number)
    @mail_address = mail_address
    @number = number
  end
  
  def send_mail(address)
    puts "#{address}に、#{@mail_address}からメールを出します。"
  end
  
  def call(number)
    puts "#{number}に、#{@number}から電話をかけます。"
  end
end

class Phone
  extend Forwardable
  def_delegators :@cp, :call
  def initialize(cp)
    @cp = cp
  end
end

class Email
  extend Forwardable
  def_delegators :@cp, :send_mail
  def initialize(cp)
    @cp = cp
  end
end

cp = CellPhone.new("hoge@email.com", "090-1234-5678")
cp.call("011-123-4567")
cp.send_mail("fuga@email.com")

phone = Phone.new(cp)
phone.call("011-987-6543")
mail = Email.new(cp)
mail.send_mail("bar@email.com")
require 'forwardable'

class Dummy
  def func1
    puts "func1"
  end
  
  def func2
    puts "func2"
  end
  
  def func3
    puts "func3"
  end
end

class IFuncs1
  extend Forwardable
  def_delegators :@d, :func1, :func2
  def initialize(d)
    @d = d
  end
end

class IFuncs2
  extend Forwardable
  def_delegators :@d, :func2, :func3
  def initialize(d)
    @d = d
  end
end

i1 = IFuncs1.new(Dummy.new)
i2 = IFuncs2.new(Dummy.new)
i1.func1
i1.func2
i2.func2
i2.func3

デリゲート

デリゲート」とは移譲の意味であるが、オブジェクトに対して行う。

delegate void Operation(int a,int b);

class Calc
{
    public void Sub(int a, int b)
    {
        Console.WriteLine("{0} - {1} = {2}", a, b, a - b);
    }   
}

class Program
{
    static void Add(int a, int b)
    {
        Console.WriteLine("{0} + {1} = {2}", a, b, a + b);
    }
    static void Main(string[] args)
    {
        Calc c = new Calc();
        Operation o1 = new Operation(Add);
        Operation o2 = new Operation(c.Sub);
        o1(2, 1);
        o2(2, 1);
    }
}

Ruby でも(メソッドをオブジェクト化することにより)似たようなことはできるが、正確に C# のデリゲートに対応するものではない。デリゲートというのは非常に変った仕様だと思う。

class Calc
  def sub(a, b)
    puts "#{a} - #{b} = #{a - b}"
  end
end

class Program
  def add(a, b)
    puts "#{a} + #{b} = #{a + b}"
  end

  def main
    c = Calc.new
    o1 =   method(:add)
    o2 = c.method(:sub)
    o1[2, 1]
    o2[2, 1]
  end
end

Program.new.main

this の謎

C# の this は Ruby の self みたいなものだと思ったのだが、this を付けていないインスタンス・メソッド内の変数が、他の(同じクラスの)インスタンス・メソッド内でも参照できてしまうように見える。インスタンス・メソッド内の変数のスコープが Ruby とちがうのかな? Ruby では、メソッドを跨いで参照できるのは(@の付いた)インスタンス変数だけなのだが。