.NETのクラスライブラリ設計 (Microsoft.net Development Series) | Krzysztof Cwalina, Bard Abrams, 藤原 雄介 |本 | 通販 | Amazon
www.amazon.co.jp
『.NETのクラスライブラリ設計』という本を読んでいて、デフォルト引数はバージョニング問題があるので(ライブラリでの)使用は避けてくださいみたいな記述があり、なるほどなぁとなったので技術的検証とかしてみたので書いておきます。
ちなみに、実験プロジェクトはGithubのほうに公開しておきます。
GitHub - MeilCli/DefaultParameterSample
Contribute to MeilCli/DefaultParameterSample development by creating an account on GitHub.
github.com
バージョニング問題とは
バージョニング問題で有名なのはconst
のほうですね。
コンパイル時に固定値を埋め込むので、その固定値を変更してしまうとそのアセンブリを参照してるコードすべてをコンパイルしなおさないといけないというものです。
using System;
namespace DefaultParameterSample.Core
{
public class Sample
{
public const string ConstString = "const_string: 1";
public static readonly string ReadonlyString = ConstString;
public void Method(string value = ConstString)
{
Console.WriteLine(value);
}
}
}
例を挙げると、このコードはコンパイル時にReadonlyString
の値にconst_string: 1
が仕込まれます。
これは他のアセンブリでも同様に行われます。
using DefaultParameterSample.Core;
namespace DefaultParameterSample.Console
{
class Program
{
static void Main(string[] args)
{
var sample = new Sample();
System.Console.WriteLine($"--- {nameof(Sample.ConstString)} ---");
System.Console.WriteLine(Sample.ConstString);
System.Console.WriteLine($"--- {nameof(Sample.ReadonlyString)} ---");
System.Console.WriteLine(Sample.ReadonlyString);
System.Console.WriteLine($"--- {nameof(Sample.Method)} ---");
sample.Method();
System.Console.ReadLine();
}
}
}
たとえば、別プロジェクトでこのように参照しているコードがあるとしましょう。
Sample.ConstString
はコンパイル時にconst_string: 1
の値が仕込まれます。
しかし、このプロジェクトのコンパイル後に元のアセンブリのSample.ConstString
の値が変更され、その変更後のアセンブリを参照していたらどうなるでしょうか。
結果はこちらのプロジェクトのコードに仕込まれた値は変更されずに実行されます。
デフォルト引数もコンパイル時に定数を埋め込む
今回の主題ですが、デフォルト引数もconstのようにコンパイル時に定数を埋め込むような挙動をしているということです。
たとえば、上述のようなコードはCILレベルでは以下のようになります。
ちょっと見にくいかもしれないのでコードを挙げます。
// sample.Method("const_string: 1");
IL_0055: ldstr "const_string: 1"
IL_005a: callvirt instance void [DefaultParameterSample.Core]DefaultParameterSample.Core.Sample::Method(string)
IL_0055:
のldstr
は見た目そのまま、右側で宣言されている文字列をスタックに読み込む命令です。この部分がコンパイル時に埋め込まれているということを表すものになります。
実際にバージョニング問題を起こしてみよう
バージョニング問題を起こそうとしても、Visual Studioを使っているとなかなか起こせません。 手元に用意したプロジェクトではすべての参照コードを実行時にコンパイルするみたいで引き起こすことができなかったので、実行可能ファイルを生成し参照しているアセンブリファイルを取り換えるという手段を選びました。
コードは最初に挙げておいたGithubリポジトリのソリューションや、細かいところは前述のサンプルコードになります。
ソリューションを一度ビルドしたら、コマンドラインで\DefaultParameterSample.Console
に移動します。
Windowsでコンソールアプリケーションを実行したいので、dotnet publish -c Release -r win-x64
を実行します。
\DefaultParameterSample.Console\bin\Release\netcoreapp2.1\win-x64\
に.exeファイルやその他いろいろが生成されるので、生成されたDefaultParameterSample.Console.exe
を実行してみます。
実行結果は画像のようになります。
次にコードの値を変更してみましょう。
using System;
namespace DefaultParameterSample.Core
{
public class Sample
{
public const string ConstString = "const_string: 2";
public static readonly string ReadonlyString = ConstString;
public void Method(string value = ConstString)
{
Console.WriteLine(value);
}
}
}
今回はConstString = "const_string: 1";
をConstString = "const_string: 2";
に変えてみました。
ここからDefaultParameterSample.Core
のアセンブリのみを取り換える作業になります。
まずは、Visual StudioなどでDefaultParameterSample.Core
プロジェクトのみをReleaseモードでビルドしてください。
\DefaultParameterSample.Core\bin\Release\netstandard2.0\
に新たに生成された.dllファイルなどが存在するので、これを先ほどの\DefaultParameterSample.Console\bin\Release\netcoreapp2.1\win-x64\
にコピー&置き換えをして取り替えます。
これでバージョニング問題を再現できたので、DefaultParameterSample.Console.exe
を実行します。
結果は画像のようになり、static readonly
としたReadonlyString
以外のコンパイル時に埋め込まれた値が残っています。
まとめ
- publicなconstやデフォルト引数は将来にわたって値に変更がないときに使う
- ライブラリーでの使用はとくに注意したほうがいい
- 値を変更する可能性がある場合は
static readonly
にする