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