読者です 読者をやめる 読者になる 読者になる

More Effective C#

Book C#

More Effective C#

More Effective C#

を読むに当たって、注意すべきかなー、と思ったところをだらだらと。

ICloneable は実装すべきではない

ICloneable を実装することはお勧めできません



†:『Framework Design Guideline : Conversions, Idioms, and Patterns for Reusable .NET Libraries』(著/ Krzysztof Cwalina, Brad Abrams 刊/ Addison-Wesley) を参照してください。

More Effective C# 項目 1 1.x フレームワーク API のクラスではなく、ジェネリッククラスを使え (P.9)

とあるので、みなさん

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)

.NETのクラスライブラリ設計 開発チーム直伝の設計原則、コーディング標準、パターン (Microsoft.net Development Series)

も買いましょう、ってことですね!
・・・何はともあれ、ICloneable 使うな!って本が割と読まれるであろうタイトルの日本語で読める本として出てきたのはありがたい限り。

もうちょっと標準ライブラリについて言及があってもいいんじゃ・・・

例えば 11 ページで、

// ある型のシーケンスを別の型のシーケンスに変換
IEnumerable<TOutput> Transform<TInput, TOutput>(IEnumerable<TInput> theCollection, Converter<TInput, TOutput> transformer);
// testを満足させるすべての要素を集める
IEnumerable<T> Test<T>(IEnumerable<T> theCollection, Predicate<T> test);

なメソッドを実装してるんだけど、標準ライブラリに用意された似たようなメソッドである Select や Where については言及していないという・・・
しかも、ここだけじゃなくて本全体を通して言えることなので更にがっかり・・・

IDisposable

public interface IEngine
{
    void DoWork();
}

public class EngineDriver<T> where T : IEngine, new()
{
    public void GetThingsDone()
    {
        T driver = new T();
        driver.DoWork();
    }
}

T が IDisposable を実装する場合、リソースリークが起きます。T 型のローカル変数を作るときには、T が IDisposable を実装しているかどうかをかならずチェックし、実装している場合には、適切に廃棄される (Dispose() 呼び出しが行われる) ようにする必要があります

public void GetThingsDone()
{
    T driver = new T();
    using (driver as IDisposable)
    {
        driver.DoWork();
    }
}
More Effective C# 項目 5 ジェネリッククラスは、IDisposable を実装する型パラメータのサポートを忘れるな (P.29)

あー、これは今まで意識してなかった!
これは T に IDisposable 制約を付ければいいということではなくて、例えそうだとしても、それならそれで IEngine 自体が IDisposable を継承するはず。
あと、この問題は new 制約に限らず、ローカル変数を作るときは常に意識しなければならないことに注意が必要。

IEnumerator

IEnumerator<T1> leftSequence = left.GetEnumerator();
IEnumerator<T2> rightSequence = right.GetEnumerator();
while (leftSequence.MoveNext() &;&; rightSequence.MoveNext())
{
    yield return generator(leftSequence.Current,
        rightSequence.Current);
}
More Effective C# 項目 6 型パラメータのメソッド制約の定義にはデリゲートを使え (P.34)

「&;&;」 ってのは勿論「&&」の typo かなにかで、多分訳したときのミスだとおもうけど・・・
IEnumerator は IDisposable を実装してるんですよね。
で、この本もここ以外ではちゃんと using 使ってるんですけど、なぜかここでは使っていない・・・これは原著のミスかな?
正しくはこう。

using (var itr1 = left.GetEnumerator())
using (var itr2 = right.GetEnumerator())
{
    while (itr1.MoveNext() && itr2.MoveNext())
    {
        yield return generator(itr1.Current, itr2.Current);
    }
}

ただ、この本の流儀としては、

using (var itr1 = left.GetEnumerator())
{
    using (var itr2 = right.GetEnumerator())
    {
        while (itr1.MoveNext() && itr2.MoveNext())
        {
            yield return generator(itr1.Current, itr2.Current);
        }
    }
}

こっちはネストが深くなる上、そんなに分かりやすくなるとも思えないんですけどねー。

ラムダ構文を使うまでもない

// 無名デリゲート構文を使ったForEach
myInts.ForEach(delegate(int collectionMember)
{
    Console.WriteLine(collectionMember);
});

// ラムダ構文を使ったForEach
myInts.ForEach((collectionMember) => Console.WriteLine(collectionMember));
More Effective C# 項目 18 アクション、述語、関数から反復処理を切り離せ (P.99)

とあるんだけど、まずラムダ式の引数名はスコープも短いし短い名前でもいいと思うんだよね。
あと、引数が一個なら丸括弧も省略できるんだから、省略してしまって、

myInts.ForEach(i => Console.WriteLine(i));

・・・でも待って欲しい。これ、ラムダ構文すら必要ないよね?

myInts.ForEach(Console.WriteLine);

こうでしょ。
なんか他でも無駄にラムダ構文使ってる部分があったような・・・?

れいがい!

More Effective C# 項目 25 例外はメソッドの約束ごとが守れなかったときに使え (P.127〜)

頻繁に「エラー処理」という言葉が出てくるけど、エラー処理が何を意味するか、までは出てこないので各自考える必要がある。
れいがいむずい・・・

プロパティ

プログラマは、プロパティアクセッサが大仕事をすることを予想していません。プロパティゲッターは、コストのかかる処理をしてはなりません。プロパティセッターも、ある程度のデータチェックは必要でしょうが、呼び出しにコストがかかるようであってはなりません。
クラスを使うプログラマたちは、なぜこのような予想のもとに動いているのでしょうか。それは、彼らがプロパティをデータとして見ているからです。

More Effective C# 項目 26 プロパティはデータらしくふるまうように作れ (P.131)

基本的には同意なんだけど、「プロパティをプロパティとして使わない」ことってあるんだよねー。流れるようなインターフェイスとか。
まぁ、ほとんどの場合はプロパティをプロパティとして使うんで、問題ないでしょうけど。

var を使うことによる問題

More Effective C# 項目 30 ローカル変数はできる限り暗黙の型付けに委ねよ (P.147〜)

出来る限り var 使った方がいいよ!ってのには同意なんだけど、微妙な問題もあるんだよねぇ・・・
例えばこんなの。

static List<int> CreateList() { return new List<int>() { 0, 1, 2, 3, 4 }; }

static void Main()
{
    var l = CreateList();
    var l2 = l.Reverse().Take(2);   // コンパイルエラー
}

IEnumerable の拡張メソッドとして、IEnumerable を返す Reverse メソッドはあるにもかかわらず、これはコンパイルエラーになってしまう。
なぜなら、List にも Reverse メソッドが存在し、しかもこっちは void、つまり何も返さないから。
これを解決するにはいくつか方法がある。

  • CreateList の戻り値を弱くする。つまり、IList とか IEnumerable にする。
  • 呼び出し後にキャストする。つまり、(IList)CreateList() とか CreateList() as IList とする。
  • var をやめて型を明示する。つまり、IList l = CreateList(); とかにする。

一番目は影響範囲が大きいという可能性があるから、普通は二番目か三番目を選ぶかな。
どちらを選ぶにしても、コメントを書いておかないと分かりにくいとおもうので注意。


えー、なので、戻り値が異なるオーバーロードとかは出来る限り避けるのは当然として、3.0 以降は拡張メソッドにも注意が必要、ということになる。
List の Reverse が this 返すようにしてくれてりゃなぁ・・・


追記:
コメントで第四、第五の方法を教えてもらいました!

  • 呼び出し後に AsEnumerable を呼び出す。つまり、CreateList().AsEnumerable() とする。
  • l は弄らず、l2 のために使うときに AsEnumerable を呼び出す。つまり、l.AsEnumerable().Reverse().Take(2) とする。

メソッドチェイン最高!

LINQ という名の流れるようなインターフェイス

単に this(と言うか自分自身と同じ型) を返せばいいってもんじゃないってのは前に書いたとおりだけど、LINQ も同じような場所があった。

ThenBy は、ソートされたシーケンスでなければ操作できません

More Effective C# 項目 36 クエリ式からメソッド呼び出しへの変換がどのように行われるかを理解せよ (P.178)

で、確認してみると、確かに IEnumerable の拡張メソッドとしては ThenBy は定義されておらず、OrderBy メソッドの戻り値型が IOrderedEnumerable になっていた。
なので、ThenBy を使うためには必ず直前に OrderBy メソッド (もしくは ThenBy メソッド) を使用しなければならないことが強制できる。

全体

えー、typo というか、誤植が結構あります。残念です。
でも内容としては、ためになる内容もあるのでいい本です。

追記

Effective C#

Effective C#

なんか無印も出るみたいですよ、っと。
4.0 版待ってるから More が先に出るのかな、と思ったんだけど、どうやら違ったみたい。
嬉しいような、残念なような・・・


ちなみに 4.0 版はこちら。

Effective C#  (Covers C# 4.0): 50 Specific Ways to Improve Your C# (Effective Software Development Series)

Effective C# (Covers C# 4.0): 50 Specific Ways to Improve Your C# (Effective Software Development Series)