白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

Java使いがC#を勉強する その④ インデクサ

こんばんは。

なんだか雪みたいなものが降りましたね。
寒いわけです。

さて、前回はプロパティについて書きましたが、
今回はその延長でインデクサについて見ていきます。

インデクサ


インデクサはインスタンスに添え字を指定してアクセスできる機能です。

Javaではリストから値を取得する場合には、

String hoge = list.get(1);

のように書きますが、C#ではインデクサを使ってこうやって書けます。

string hoge = list[1];

配列っぽいですね。

インデクサの文法はプロパティと似ています。
こんな風に定義すると、

class Hoge
{
    private string[] ary;

    public Hoge(int size)
    {
        this.ary = new string[size];
    }

    public string this[int i]
    {
        get { return ary[i]; }
        set { ary[i] = value; }
    }
}

普通のオブジェクトも配列みたいにアクセスできます。

Hoge hoge = new Hoge(1);
hoge[0] = "ふがふが";


インデクサ引数の定義に型があることからもわかりますが、
int以外にも指定できるため、連想配列みたいにも使えます。

public int this[string str]
{
    get { /* なんか返す */ }
    set { /* なんかする */ }
}

と定義すると、

Hoge hoge = new Hoge();
hoge["ほげほげ"] = 3;

みたいに使えるわけですね。

はて、使い道は・・・?


便利そうですが、どんな時に使うのかがよくわかりません。
引数を取って戻り値を返す、それはメソッドというやつじゃないか。

うーん。

class CharPicker
{
    private char[] ary;

    public CharPicker(string str)
    {
        this.ary = str.ToCharArray();
    }

    public char this[int i] => this.ary[i];
}

こんなのを作って、

CharPicker cp = new CharPicker("あいうえお");
Console.WriteLine(cp[2]); // う

いや…うれしいかなこれ。

Zip作ってみるとか?

class Zip<T, S>
{
    private IList<T> list1;
    private IList<S> list2;

    public Zip(IList<T> list1, IList<S> list2)
    {
        this.list1 = list1;
        this.list2 = list2;
    }

    public int Count => Math.Min(list1.Count, list2.Count);

    public Tuple<T, S> this[int i] => Tuple.Create(this.list1[i], this.list2[i]);
}

で、

var list1 = new List<string> { "あ", "い", "う" };
var list2 = new List<int> { 1, 2, 3 };

var zip = new Zip<string, int>(list1, list2);
for (int i = 0; i < zip.Count; i++)
{
    Console.WriteLine(zip[i]);
}

うーん。

Java使いがC#を勉強する その③ プロパティ

こんばんは。

スマホの液晶シートが上手に貼れない私です。
今日はプロパティについてみていきます。

アクセサ


オブジェクト指向プログラミングでは、オブジェクト内部のメンバ変数に外部からアクセスするためには、
「アクセサ」と呼ばれるメソッドを利用します。

public class Hoge {

  private String piyo;

  public void setPiyo(String piyo) {
    this.piyo = piyo;
  }

  public String getPiyo() {
    return piyo;
  }
}

Hoge hoge = new Hoge();
hoge.setPiyo("ぴよぴよ");


ただ、この場合だとメンバ変数をpublicにしても特に何かが変わるわけではありません。
どちらかというと、メンバ変数に対するインタフェースを提供するみたいなイメージで考えるとしっくりきます。

getterで末尾に勝手に文字列を追加するとか、
メンバ変数を外から見たときの振る舞いを書くことができます。

public class Hoge {

  private int price;

  public void setPrice(int price) {
    this.price = price;
  }

  public String getPrice() {
    return price + "万円";
  }
}

Hoge hoge = new Hoge();

hoge.setPrice(5);
System.out.println(hoge.getPrice()); // 5万円


単なるsetterとgetterを自動生成したいだけならば、
JavaではLombokなどのライブラリを使うことが多いですね。

@Data
public class Hoge {
  private String piyo;
}


プロパティ


C#にあってJavaにない機能に「プロパティ」というものがあります。

public class Hoge
{
  private string _piyo;

  public string Piyo
  {
    get { return _piyo; }
    set { _piyo = value; } // valueという名前でアクセスできる
  }
}


フィールドに直接アクセスしているように見えますが、
ちゃんとアクセサを経由してデータのやり取りがされています。

Hoge hoge = new Hoge();
hoge.Piyo = "ぴよぴよ";


単なるgetterとsetterなら簡潔に書くことができます。
メンバ変数の宣言とアクセサが合体したような構文ですね。

public class Hoge
{
  public string Piyo { get; set; }
}


C#6.0からは初期化も同時にできるらしいです。
なんかこの構文すごく違和感があるのですが・・。

public class Hoge
{
  public string Piyo { get; set; } = "ぴよぴよ";
}


getのみの定義も可能ですが、setのみの定義は不可です。
さらに、getのみの場合には式の結果を返すプロパティを作ることができます。

public class Hoge
{
  public string Huga { get; };
  public string Piyo { get; };

  public string HugaPiyo => Huga + Piyo;

  public Hoge(string huga, string piyo)
  {    
    this.Huga = huga;
    this.Piyo = piyo;
  }
}


ところで、setのオーバーロードをしたい場合にはプロパティは使えないのでしょうか?
こっちだけsetterメソッド経由ってなんか嫌だなぁ。

public class Hoge
{
  public string Piyo { get; set; }

  public void SetPiyo(int i)
  {
    this.Piyo = i.ToString();  // C#はプリミティブもObjectのサブクラスだからtoString使える!
  }
}

うーん。

Hoge hoge = new Hoge();
hoge.Piyo = "ぴよぴよ";
hoge.SetPiyo(3);


あと、getterは「get;」だけ書いて、setterはロジック書くとかダメなんですかね。

不安


言語仕様にこういうのが組み込まれているのはなかなかに素敵ですが、
パブリックフィールドアクセスしている気がして不安です。

早く慣れよう。

Java使いがC#を勉強する その② 配列

こんばんは。

久しぶりに雨が降りましたね。
雨が好きなわけではないですが、湿度が高いのはありがたいです。

さて、前回はデータ型について比較したので、
今回はそのついでのような形で配列について見ていきます。

1次元配列


1次元配列に関しては、あまり大きな違いはありません。
この2つくらいです。

①括弧を付ける位置

Javaはデータ型の後ろと変数の後ろのどちらにでも括弧を付けることができますが、
C#ではデータ型の後ろにしか付けることができません。

int[] ary1; // JavaでもC#でもOK

int ary2[]; // JavaはOKだけど、C#はNG


②初期化時のサイズ指定

C#では初期化時にサイズ指定ができます。

int[] ary1 = new int[]{1, 2}; // JavaでもC#でもOK

int[] ary2 = new int[2]{1, 2}; // JavaはNGだけど、C#はOK
int[] ary2 = new int[3]{1, 2}; // これはエラー

 

多次元配列

 

ジャグ配列

Javaにおける多次元配列は、C#においてはジャグ配列と呼ばれます。
これは配列の中に配列が入れ子になっている状態です。

同一の次元内にサイズの異なる配列を入れることができるため、
jag(ギザギザ)という名前のようです。

int[][] ary = new int[5] [];
for (int i = 0; i < ary.Length; i++)
{
    ary[i] = new int[i % 2 == 0 ? i + 1 : ary.Length - i];
}

f:id:Shiro-Neko:20170203150601j:plain

あふれでるテトリス感。

多次元配列

C#における多次元配列は、対応するものがJavaにはありません。

int[,] ary = new int[4, 5];
Console.WriteLine(ary.Length);        // 20
Console.WriteLine(ary.GetLength(0));  // 4
Console.WriteLine(ary.GetLength(1));  // 5

Lengthは全体のサイズが取れるので、気をつけなくてはいけないですね。

int[,] ary = { { 1, 2 }, { 3, 4 }, { 5, 6 } };

もちろんこんな感じの初期化もできます。

1回のnewですべてのメモリが確保できるのは素敵ですね。

そんなに違和感はない・・・?


C#Javaで配列にはあんまり違いはなさそうです。
が、これはかなり違和感あるなぁ。

var ary = new[] { 1, 10, 100, 1000 };

いや、これは配列じゃなくてvarへの違和感ですね。