4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

気ぃつけよ! JavaエンジニアがC#を学ぶ時につまずくポイント3選

Posted at

こんにちは。
Javaを今まで書いていてここ2ヶ月でC#を書き始めたものです。

それにしてもJavaとC#って似てますよね?

「JavaとC#ってほぼ同じだし、1日で文法さらえたわ」

こんなふうに思っていたら、要注意です(自戒)。
似てるが故にハマる落とし穴というものもあるのです。

背景

聞くところによると、Javaが1995年に最初に登場して、C#が2000年に最初に登場したようです。
Javaの方が5年上の先輩ですね。

よって、C#にできるけどJavaではできない、みたいな言語機能もあるわけです。

まあでもお互いに影響し合って今まで成長してきたのでしょうね。

ここからは、実際にJavaとC#の相違点について考え、Javaエンジニアが特に陥りやすい言語仕様などを紹介していきたいと思います。

1. "protected" の意味が違う

JavaをやっていてC#を始めて触る人は大概、「internalってなんだ?でもprotectedは共通してる!たぶん同じ意味だろうな」なんて思ったりするものです。

違います。

JavaとC#では、アクセス修飾子の意味合いはかなり違います
privatepublicしか同じじゃないです)

Java

protected: 同じパッケージ内 or 継承クラス
(修飾子なし): 同じパッケージ内

C#

protected internal: 同じアセンブリ内 or 継承クラス
internal: 同じアセンブリ内
protected: 継承クラス
private protected: 同じアセンブリ内 and 継承クラス

このように…

違うんですよね、意外と。
表にまとめてみるとこんな感じです。

アクセスしやすさ Java C# 詳細
1 public public どこでも
2 protected protected internal 同じパッケージ/アセンブリ内 or 継承クラス
3 (修飾子なし) internal 同じパッケージ/アセンブリ内
3 - protected 継承クラス
5 - private protected 同じアセンブリ内 and 継承クラス
6 private private 同じクラス内のみ

こうみると、C#はJavaの悪い点をちゃんと改善していて良いなって思いますよね。

C#での「修飾子なし」のアクセスレベル

またもう1つC#を学ぶ際に罠になるのが、C#での「修飾子なし」の場合です。
この場合は、アクセスレベルには「規定のアクセシビリティ」が付与されます。

しかしこの「規定のアクセシビリティ」、つまり「デフォルトのやつ」がものによって異なっているんですよね〜。

アクセスされるもの メンバのアクセス修飾子
class private
struct private
interface public
enum public

つまりこれらは一緒です。

public class TestClass
{
    private int _privateInt1;
    int _privateInt2;
}

public struct TestStruct
{
    private string _privateString1;
    string _privateString2;
}

public interface ITest
{
    public void PublicFunction1();
    void PublicFunction2();
}

覚え方としては、「考えられるアクセス修飾子の中で、一番厳しいもの」と覚えれば大丈夫です。interfaceenumはそもそも普通は実装を持つものではないし、privateにする意味もないのでpublicになってる、と考えれば納得がいきます。

2. getHoge, setHogeなどを大量に書きがち

Java上がりのエンジニアは、getter関数やsetter関数を大量に書く習性があります。
なぜなら、Javaで大事なメンバをpublicで宣言して…なんて事したら上司にこっ酷く怒られたからです。

なので普通は外部に公開したいメンバには基本的にgetterとsetterを書いて値の正当性を保持したりするのですが…

C#erはあんまりSetHogeGetHogeなんて書きません。代わりに**「プロパティ」**を使用します。

プロパティとは?

getterとsetterが備わっているメンバみたいなもののことをいいます。
例えば以下の例。

Person.cs
public class Person
{
    private int _age;
    public int Age
    {
        internal get
        {
            return _age;
        }
        set
        {
            if (value < 0 || 150 < value) throw new System.ArgumentOutOfRangeException();
            _age = value;
        }
    }
}

_ageというメンバは外からは見えません。しかしAgeというプロパティを通してそのメンバにアクセスすることができるようになります。

また、getterとsetterにはそれぞれ個別のアクセシビリティを指定することができます。上記の例では、getterはinternal, setterはpublicとなります。これによって歳を気にしだしたアラフォーのおばさんも、外部のアセンブリには自分の歳を非公開にすることができます(まあReflection使ったら見れるんですけどね)。

これによって、下手に冗長なコードを書かずにすみますし、綺麗ですよね。

でもsetHoge, getHogeを書きたくなるのはわかります…僕も抜け出せないでいました…

3. Javaにはない参照渡しがある…!!

そうです。Javaにはない参照渡しが存在してしまっているのです。

Javaでは?

Javaでは、関数の引数はいわゆる「値渡し」というものでした。正確に言えば、全てが「クラスのインスタンスへのポインタ」でした。なので、新しいnewしたインスタンスを返してあげたい時は絶対に返り値で戻す必要があったのです。

Java初心者じゃなければ以下の意味がわかるはずです。

ListRefModify.java
public class ListRefModify {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("original");
        modify(list);
        System.out.println(list);
    }

    private static void modify(ArrayList<String> list) {
        list = new ArrayList<String>();
        list.add("modified");
    }
}
出力: ["original"]

※なぜならlistはポインタの値であり、ポインタの指し示す値を変えているわけではないから

C#では?

C#ではJavaでは紛らわしかった、ポインタ渡しではなく、純粋な「参照渡し」ができます!

C#では参照渡しのためにref, out, in という3つのキーワードを、実引数と仮引数の両方に使います。

ref

ただの参照渡し。

public static void modify(ref int a)
{
    a = 2;
}

public static void test()
{
    int a = 1;
    modify(ref a);
    Console.WriteLine(a);  // 2
}

in

呼ばれた関数内で、readonlyとして使われる参照渡し。「見るだけ、お触りはだめ」的なノリ。

public static void modify(in int a)
{
    // a = 2;  // compile error
    int b = a;
    Console.WriteLine(b);  // 0
}

public static void test()
{
    int a = 0;
    modify(in a);
    Console.WriteLine(a);  // 0
}

out

呼ばれた関数内のみで、絶対に初期化される参照渡し。「これになんかもの入れといて〜」的なノリ。

public static void modify(out int a)
{
    a = 2;
}

public static void test()
{
    int a;
    modify(out a);
    Console.WriteLine(a);  // 2
}

問題解決…!

これにより、先ほどのJavaの問題(つまり、呼び出し先でnewしたやつを入れてそれを反映させたいという欲求)が解消されます。ListRefModify.javaをC#で書き直してみます。

ListRefModify.cs
public class ListRefModify
{
    public static void Main(String[] args)
    {
        List<string> list = new List<string>();
        list.Add("original");
        Modify(ref list);
        Console.WriteLine(list);
    }

    private static void Modify(ref List<string> list)
    {
        list = new List<string>();
        list.Add("modified");
    }
}
出力: ["modified"]

C#はもともとクラスに関しては参照渡しですが、refをつけることで「参照の参照渡し」ができるようになります。これで、上記のように呼び出し先で違うインスタンスの参照を代入しても値が変更されないことがありません。

最初は多いように感じますが、使っていくたびにC#の参照渡しの多様性のありがたみに気づき、C#無しじゃ生きていけないようになり、そしてJavaが霞んで見えてくるでしょう

おわり

今回はJavaに慣れている人が驚くであろう、そしてつまずくであろうポイントを3つ紹介しました。なぜかC#を称賛してJavaを卑下しそうになりましたが、Javaにだって良いところはあります。どっちも愛してあげようね。

4
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?