本ブログはアフィリエイト広告を利用しています。

C# ジェネリックを使用したクラスのstatic変数の罠

C#の仕様で罠にはまったのでメモに残します。

環境

  • VisualStudio 2022 Version 17.3.0 Preview 5.0
  • .NET 6.0

どんな罠?

Sample<T>クラスにstaticなintの変数、countがあったとします。
こんな感じにクラスを生成したとします。

var a = new Sample<SampleGenericA>();
var b = new Sample<SampleGenericB>();

このとき、countはどうなるでしょう。
私のイメージとしてはaのcount、bのcountは共有というか共通の値を参照していると思っていました。だってstatic変数だよ?
aのなんらかのメソッドでcountをインクリメントするとbで参照した時には1となっているのでインクリメントすると2になる。
そんなイメージです。実際にそのように実装も行っていました。

だがしかし、だがしかし!
実際にはジェネリックの型ごとにstaticな変数が出来上がるため、上記に書いた動作にはならずそれぞれが0->1、0->1になるだけでした。
Sampleクラスでcountがstaticになるわけではなく、Sample<T>ごとにcountがstaticになるようです。
では実際にソースを動かして確認していきます。

namespace GenericsTest
{
    internal class GenericsTest
    {
        public static void Main(string[] args)
        {
            var a = new Sample<SampleGenericA>();
            var b = new Sample<SampleGenericB>();

            Console.WriteLine(a.Increment());  // 戻り値 = 1
            Console.WriteLine(a.Increment());  // 戻り値 = 2
            Console.WriteLine(b.Increment());  // 戻り値 = 1(3ではなく1)

            var a2 = new Sample<SampleGenericA>();
            Console.WriteLine(a2.Increment());  // 戻り値 = 3
        }
    }
}
class Sample<T>
{

    private static int count = 0;
    public int Increment()
    {
        return ++count;
    }
}
class SampleGenericA { }
class SampleGenericB { }

javaではどうなる?

javaは想定通りcountはきっとa, bどちらも同じ値を見ているはず。
もうそのイメージしかなく、だからこそC#ではまったのだ。でも確証はないのでjavaでも確認していきます。
ソースも一応貼っておきますがC#とほぼ同じです。
結果としては思っていた通り共通の値を参照しています。
ですよね!javaとC#って似てるようで微妙なところが違うんですね。
C#を主に使っている人にとってはこれって常識なんでしょうか。周りに使っている人がいないから聞きたくても聞けない。

public class GenericsTest {

    public static void main(String[] args) {

        var a = new Sample<SampleGenericA>();
        var b = new Sample<SampleGenericB>();

        System.out.println(a.increment());  // 戻り値 = 1
        System.out.println(a.increment());  // 戻り値 = 2
        System.out.println(b.increment());  // 戻り値 = 3

        var a2 = new Sample<SampleGenericA>();
        System.out.println(a2.increment());  // 戻り値 = 4
    }
}
class Sample<T> {

    private static int count = 0;
    public int increment() {
        return ++count;
    }
}
class SampleGenericA {}
class SampleGenericB {}

以上です。

コメント

  1. へげもに より:

    記事冒頭の
    > var a = new Sample();
    > var b = new Sample();

    var a = new Sample();
    var b = new Sample(); // こっちは多分 SampleGenericB

    ではないでしょうか?
    (実例のコード中ではそうなっています)

  2. へげもに より:

    ↑Genericsの型指定の山かっこがタグ扱いされる?のか、消えてしまったようです。

    記事冒頭の
    > var a = new Sample<SampleGenericA>();
    > var b = new Sample<SampleGenericA>(); // こっちは多分SampleGenericB

タイトルとURLをコピーしました