プログラマとプロマネのあいだ

プログラマもやるし、プロマネもやるし、たまに似非アーキとか営業っぽいこともやる

Javaでは定数が呼び出し元に埋め込まれるが、.NET(C#)はどうか?

概要

あるdll内で定義されている定数を変更した場合、
呼び出し元を再コンパイルする必要はあるか?という質問。


常識的に考えたら、dllが変わったからといって、
呼び出し元を再コンパイルする必要はないのだが、
Javaでは、定数は呼び出し元に埋め込まれるため、
.NETではどうなのだろう、と考え出したのがきっかけ。


まず、Javaの挙動について実例を示す。

Main.javaからConstants.javaの定数を呼び出す場合

Constants.java
public interface Constants
{
	public static final int NUM_CONST = 100;
}
Main.java
public class Main
{
	public static void main(String[] args)
	{
		System.out.println(Constants.NUM_CONST);
	}
}
コンパイル&実行
>javac -version
javac 1.5.0_10

>javac Main.java

>java Main
100

当然のことながら、100が表示される。
次に、定数を200に変更して、Constants.javaのみコンパイルする。(やや恣意的だが)

Constants.java
public interface Constants
{
	public static final int NUM_CONST = 200;
}
コンパイル&実行
>javac Constants.java

>java Main
100

定数を変更したにも関わらず、
変更前の100が表示されてしまう。
これは、最適化の関係で、呼び出し元に定数が埋め込まれてしまうためである。

コンパイル
>javap -c Main
Compiled from "Main.java"
public class Main extends java.lang.Object{
public Main();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   bipush  100
   5:   invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   8:   return

}

改めて呼び出し元もコンパイルし直す必要がある。

コンパイル&実行
>javac Main.java

>java Main
200
コンパイル
>javap -c Main
Compiled from "Main.java"
public class Main extends java.lang.Object{
public Main();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   sipush  200
   6:   invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   9:   return

}

Main.javaからconst.jarの定数を呼び出す場合

ここまで書いて、外部提供のライブラリとかだったら困るんじゃね?
と思い、Constantsをjarにしてみたらどうかというのを試してみることに。

フォルダ構成
>tree /f
│  Main.java
│
└─lib
        Constants.java
Constants.java
public interface Constants
{
	public static final int NUM_CONST = 100;
}
コンパイル(サブ)&jar
>javac lib\Constants.java

>jar cvf const100.jar -C lib Constants.class
マニフェストが追加されました。
Constants.class を追加中です。(入 = 152) (出 = 126)(17% 収縮されました)
コンパイル(メイン)
>javac -classpath const100.jar Main.java
実行
>java -classpath const100.jar;. Main
100

次に、定数を200に変更して、Constants.javaのみコンパイルしてjarを作る。

Constants.java
public interface Constants
{
	public static final int NUM_CONST = 200;
}
コンパイル(サブ)&jar
>javac lib\Constants.java

>jar cvf const200.jar -C
lib Constants.class
マニフェストが追加されました。
Constants.class を追加中です。(入 = 152) (出 = 126)(17% 収縮されました)
実行
>java -classpath const200.jar;. Main
100

>
コンパイル
>javap -c Main
Compiled from "Main.java"
public class Main extends java.lang.Object{
public Main();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   bipush  100
   5:   invokevirtual   #3; //Method java/io/PrintStream.println:(I)V
   8:   return

}

というわけで、別にjarであるからといって、定数埋め込みの最適化が
行われなくなるということはありませんでした。
つまり、Javaでは常にフルビルドが必須ということだけ覚えておけばよい。
ということで良さそうです。

C#ではどうか

Constant.cs
public class Constant
{
	public const int NUM_CONST = 100;
}
Main.cs
using System;

class Test
{
	static void Main()
	{
		Console.WriteLine(Constant.NUM_CONST);
	}
}
コンパイル(サブ)
>csc /target:library Constant.cs
Microsoft(R) Visual C# .NET Compiler version 7.10.6001.4
for Microsoft(R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.
コンパイル(メイン)
>csc /reference:Constant.dll Main.cs
Microsoft(R) Visual C# .NET Compiler version 7.10.6001.4
for Microsoft(R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.
実行
>Main
100
Constant.cs

次、定数を200に変更してdll再作成。

public class Constant
{
	public const int NUM_CONST = 200;
}
コンパイル(サブ)
>csc /target:library Constant.cs
Microsoft(R) Visual C# .NET Compiler version 7.10.6001.4
for Microsoft(R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.
実行
>Main
100

うーむ、同じことが起こっているなあ。。

コンパイル(メイン)
>csc /reference:Constant.dll Main.cs
Microsoft(R) Visual C# .NET Compiler version 7.10.6001.4
for Microsoft(R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.
実行
>Main
200
念のため
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.3053
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

でもやってみましたが、同じ結果でした。

結論

C#では、Javaと同様に、定数を呼び出し元へ埋め込むことで、最適化を行っている。
よって、定数を変更した場合は、呼び出し元も含めてビルドすべき。
(依存関係を調べてるヒマがあったら、フルビルドした方が早い。)
という結論になりました。