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 {}
以上です。
コメント
記事冒頭の
> var a = new Sample();
> var b = new Sample();
は
var a = new Sample();
var b = new Sample(); // こっちは多分 SampleGenericB
ではないでしょうか?
(実例のコード中ではそうなっています)
↑Genericsの型指定の山かっこがタグ扱いされる?のか、消えてしまったようです。
記事冒頭の
> var a = new Sample<SampleGenericA>();
> var b = new Sample<SampleGenericA>(); // こっちは多分SampleGenericB
ご指摘ありがとうございます。その通りなので修正しました!