Hatena::ブログ(Diary)

Marginalia

2017-02-16

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 では、メソッドを跨いで参照できるのは(@の付いた)インスタンス変数だけなのだが。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/obelisk2+marginalia/20170216/1487250722