trsing’s diary

勉強、読んだ本、仕事で調べたこととかのメモ。

EFFECTIVE C# 6.0/7.0 読書メモ 項目1~項目5

項目1 ローカル変数の方をなるべく暗黙的に指定すること

ローカル変数の型を宣言する場合はなるべくvarを使う。 コンパイラが型を宣言する。

メリット

  • ローカル変数の型という細部を気にせずに済み、コードの意味に注力できる。
  • 型を自分で選択するよりコンパイラに選択させたほうが良い場合が多々ある(IQueryableとIEnumerable)

注意点

  • コードから型を判断できるならvar。できないなら型を明記する。数値型も型を使用した方が良い。
  • varは動的型付けではない。コンパイル時の型と実行時の型が異なる場合、コンパイル時の型が優先される。

良い例

var foo = new MyType();//型が明確

悪い例

var foo = sumeObject.DoSomeWork();//返り値の型が明確でない。
//型を明記するか、変数名を工夫してわかりやすくする。

項目2 constよりもreadonlyを使用すること

constはコンパイル時に値が利用可能でなければいけない場合(属性の引数、switch caseのラベル、enumの定義)にのみ使用すべき。 ほかはreadonlyを使用したほうが高い柔軟性を得られる。

const(コンパイル時定数)はオブジェクトコード内の定数値に置き換えられる。次の二つは同じコードが出力される。

if(myDateTime.Year == Millennium)//public const int Millennium = 2000;
if(myDateTime.Year == 2000)

constはメソッド内でも宣言できる。

readonly(実行時定数)は実行時に値が割り当てられるのでより柔軟な対応が可能。ILはreadonly変数を参照するものになる。
readonlyはメソッド内では宣言できない。

アセンブリを超えた場合も同様。
例)
アプリケーションアセンブリがInfrastructureという別のアセンブリを参照しているとする。 Infrastructureのconstについては値に置き換えられる。readonlyの値は実行時に解決される。
・Infrastractureのconstが変更になった場合、アプリケーション側の再コンパイルが必要。
・readonlyの場合、アプリケーション側の再コンパイルは不要。

注意点
constは実行時測度が若干有利だが、柔軟性を損なってまで効率性を揚げる必要があるかよく検討すること。実行速度を理由にconstを使用する場合先にパフォーマンスの測定もするべき。

項目3 キャストにはisまたはasを使用すること

as演算子のほうが安全で、実行時の効率も優れる。 キャスト演算の場合、予想もしない副作用が起こったり、思いがけないタイミングで成功あるいは失敗することがある。

as、is演算子
ユーザー定義の変換を行わず、実行時の型のチェックのみ。実行時の型が要求された型と一致する場合にのみ成功する。指定の型ではない、指定の型から派生した型でない場合には変換に失敗する。
※ボックス化された値型をボックス化解除されたnull許容型へと変換する場合、新しい型を作成する

キャスト
指定の型への変換演算子を利用できる。

キャストだと文脈で振る舞いが変わる例)
SecondTypeからMyTypeへのユーザー定義の変換演算子が存在する場合、

object st = Factory.Getobject();//SecondType型を返す
t = (MyType)st;

上記は失敗するが、

SecondType st = Factory.Getobject();

と宣言していれば成功する。

コンパイラコンパイル時におけるstの型を基準とするため、前者ではstはobject型。objectからMyTypeに変換するユーザー定義の変換演算子はない。そのため、コンパイラはoの実行時の型がMyTypeかどうかをチェックするコードを生成。oはMyTypeではないため失敗。(実行時における実際の型がMyType型に変換できるかチェックするわけではない)

その他注意点

  • as演算子は参照型またはNull許容型で使用すること。
  • foreachステートメントはキャスト演算子を使用している。値型と参照型の両方をサポートする必要があるため。(IEnumerable.CurrentはSystem.Objectで変換演算子を持たないため対象となる型によらず同じ挙動)
  • 変換可能かではなく厳密な型を知りたい場合はGetType()メソッド。オブジェクトの実行時における型を返す。

項目4 string.Format()を補完文字列に置き換える

可読性が大幅に向上する。置換される式が簡単に把握できる。結果の確認も簡単。引数のインデックスを間違えることもない。

注意点

  • 三項演算子を使用する場合は式を括弧で囲む(:を見つけると書式指定文字列の解質店だと判断するため)。
  • 文字列補完によりコンピュータに読み取らせる文字列(SQLコマンドなど)を生成する場合、それによって発生しうるリスクを考慮すること。(作成されるのは単一の文字列。想定しない文字列になってない?)
  • パフォーマンスに注意
Console.WriteLine($"円周率の値は{Math.PI}")

という補完文字列があったとする。値型をobject型と強制するため、ボックス化が起こる。このコードが繰り返し呼ばれる場合、パフォーマンスに重大な影響を与える。

項目5 カルチャ固有の文字列よりもFormattableStringを使用すること

特定のカルチャが必要な場合、文字列補間を明示的にFormattableStringとして作成してから、特定のカルチャを使用して文字列に変換すると楽。

暗黙的な型指定を使用すると、文字列補完は文字列になる。

> var third = $"今日は{DateTime.Now.Month}月{DateTime.Now.Day}日です";
> third.GetType().Name
"String"

指定するとFormattableStringから派生したオブジェクトを作成できる

> FormattableString second = $"今日は{DateTime.Now.Month}月{DateTime.Now.Day}日です";
> second.GetType().Name
"ConcreteFormattableString"
> second.Format
"今日は{0}月{1}日です"
> second.GetArguments()
object[2] { 3, 30 }

注意点
文字列とFormattableString両方を受け付けるオーバーロードがある場合、 $"" リテラルを渡すと、文字列引数のメソッドが優先される。

> string ToFrenchCanada(FormattableString src)
. {
.     return string.Format( System.Globalization.CultureInfo.CreateSpecificCulture("fr-CA"), src.Format, src.GetArguments());
. }
> ToFrenchCanada($"{5.5}")
"5,5"
> string ToUs(FormattableString src)
. {
.     return string.Format(System.Globalization.CultureInfo.CreateSpecificCulture("en-US"), src.Format, src.GetArguments());
. }
> ToUs($"{5.5}")
"5.5"

Formatの一つ目の引数(null)いらんかった。