Modern C# Programming Style Guide

C# Advent Calendar 2011、ということで、C# 4.0時代のプログラミングスタイルについて説明してみます。モダン、というけれど、某書のように変態的なことじゃなくて、むしろ基本的な話のほうです。こういったものはナマモノなので、5.0になればまた変わる、6.0になればまた変わる。変わります。古い話を間に受けすぎないこと(歴史を知るのは大事だけど、そのまま信じるのは別の話)、常に知識をリフレッシュするようにすること。そういうのが大事よね。でも、だからってモダンに書けなきゃダメ!なんてことはありません。ただ、知ること、少しずつ変えていくこと、そういうのは大事よね、って。

ところでしかし、私の主観がかなり入っているので、その辺は差っ引いてください。

  1. varを使う

C# 3.0から搭載された型推論での宣言。出た当初には散々議論があって、今もたまに否の意見が出てきたりもしますが、varは使いましょう。積極的に。何にでも。国内的にも世界的にもMicrosoft的にも、var積極利用の方向で傾いているように見えます。また、最近流行りの関数型言語(Haskell, Scala, F#)は、少なくともC#のvarで可能な範囲は全て推論を使いますね←C#のvarはそれらに比べれば遥かに貧弱ですからね。そういったこともあるので、使わない理由もないでしょう。

var person = new Person();
var dict = new Dictionary<string, Tuple<int, Person>>();
var query = Enumerable.Range(1, 10).Select(x => x * 10);

varの利点は、何といっても書いていて楽なことです。はい、圧倒的に楽です。そして、型宣言の長さが一致するので「実は見やすい」というのもポイント高し。たった3文字の短さと相まって、ソースコードが綺麗になります。また、必ず変数の初期化を伴う、というのも良いことです。

欠点は「メソッドの戻り値などは宣言を見ても型が分からない」「インターフェイスで宣言できない」の二つが代表的でしょうか。前者は、Visual Studioを使えばマウスオーバーで型が表示されるので、コーディング上では支障はない。メールやBlogやWikiなど、Visual Studioのサポートのない完全にコードのみの状態だとサッパリなのは確かに難点ではありますが、逆にその程度の部分的な範囲なら、括り出されている目的が明確なわけなので、適切な変数名がついているのなら、正確な型名とまではいかずとも何に使うもののか大体分かるのではないでしょうか?なので、大きな問題だとは私は思いません。もし変数名がテキトーで型名ぐらいしかヒントが得られないんだよ!ということならば、varよりも前にまともな変数名をつけるようにしたほうがいいです。

インターフェイスで宣言できないことは、私は何の問題もないと思っています。具象型やメソッドの返す型でそのまま受けることに何の不都合が?むしろインターフェイスで宣言すると、アップキャスト可能という怪しい状態を作り出しているだけです。

ちなみにintやstring、配列などの基本的なものぐらいは型を書くという流儀もなくはないようですが、それは意味無いのでやめたほうがいいでしょう。

var num = 100;
var text = "hogehoge";
var array = new[] { 1, 2, 3, 4, 5 };

だって、こういうのこそ、見れば一発で分かるほど自明なので。

  1. オプション引数を使う(使いすぎない)

害悪もあるわけですが、割と積極的に使ってもいいような気がします。実際Roslyn CTPなどでは結構派手に使われていますし、オーバーロード地獄よりはIntelliSense的にも分かりやすいかな、って。思います。enumなど使うと、明確に何が使われるか見えるんですね、これはとても嬉しくて。やっぱC#としてはIntelliSenseで分かりやすい、というのはとても大事かと。

さて、分かりやすく使いすぎに注意な点としては、引数なしコンストラクタが消滅してしまう可能性があげられます。引数なしコンストラクタがないと、色々なところで弊害が起こります。

// こんなオプション引数なコンストラクタしかないクラスがあるとして
public class ToaruClass
{
    public ToaruClass(int defaultValue = -1)
    {

    }
}

class Program
{
    static T New<T>() where T : new()
    {
        return new T();
    }

    static void Main(string[] args)
    {
        // 使うときは引数なしでnewできるけど
        var _ = new ToaruClass();

        // 実態は違うので、ジェネリックのnew制約が不可能になる
        New<ToaruClass>(); // コンパイル通らない

        // 引数なしコンストラクタを要求するシリアライザの利用も不可能に
        new XmlSerializer(typeof(ToaruClass)).Serialize();
    }
}

シリアライズできなかったりジェネリックのnew制約がきかなくなってしまったり。ご利用は計画的に。シリアライズに関しては、DataContractSerializerならばコンストラクタを無視するので使えはしますが……。その辺の話はneue cc - .NETの標準シリアライザ(XML/JSON)の使い分けまとめで。

Roslyn CTPのAPIはオプション引数が激しく使われているのですが、中でもこれは面白いと思いました。Mindscape Blog » Blog Archive » In bed with Roslynから引用します。

PropertyDeclarationSyntax newProperty = Syntax.PropertyDeclaration(
    modifiers: Syntax.TokenList(Syntax.Token(SyntaxKind.PublicKeyword)),
    type: node.Type,
    identifier: node.Identifier,
    accessorList: Syntax.AccessorList(
        accessors: Syntax.List(
            Syntax.AccessorDeclaration(
                kind: SyntaxKind.GetAccessorDeclaration,
                bodyOpt: Syntax.Block(
                    statements: Syntax.List(
                        getter
                    )
                )
            ),
            Syntax.AccessorDeclaration(
                kind: SyntaxKind.SetAccessorDeclaration,
                bodyOpt: Syntax.Block(
                    statements: Syntax.List(
                        setter
                    )
                )
            )
        )
    )
);

そう、名前付き引数でツリー作ってるんですね。LINQ to XMLの関数型構築も面白いやり方だと思いましたが、この名前付き引数を使った構築も、かなり素敵です。流行るかも!

  1. ジェネリックを使う

もしお持ちの本がArrayListやHashTableを使っているコードが例示されていたら、窓から投げ捨てましょう。Silverlightでは廃止されていますし、WinRT(Windows 8)でも、勿論そんな産廃はありません。もはやどこにもそんなものを使う理由はありません。どうしても何でも入れられるListが欲しければ、List<object>を使えばいいぢゃない。

  1. ジェネリックデリゲート(Func, Action)を使う

これも賛否両論ではあるのですが、私は断然ジェネリックデリゲート派です。ちなみにその反対は野良デリゲート量産派でしょうか(悪意のある言い方!)。ジェネリックデリゲートを使うと良い点は、デリゲートの型違い(同じ引数・戻り値のデリゲートでも型が違うとキャストが必要)に悩まされなくてすむ、定義しなくていいので楽、そして、なにより分かりやすい。例えば、「MatchEvaluator」というだけじゃ、何なのかさっぱり分かりません。正規表現のReplaceで使われるデリゲートなのですけどね。Func<Match, string>のほうが、ずっと分かりやすい。

では良くない点は、というと、引数に変数名で意味をつけられない。例えばLINQのSelectメソッドのFunc<TSource, int, TResult>。このintはインデックスですが、そのことはドキュメントコメントからしか分かりません。その点、野良デリゲートを作れば

public delegate TR ConverterWithIndex<T, TR>(T value, int index);

という形で、明示できます。なんて素晴らしい?そう?実のところそんなでもなくて、これ、IntelliSenseからじゃあ分からないんですよね。

F12なりなんなりで型定義まで飛ばないと見えないのです、デリゲートの変数名は。その点Func<Match, string>なら引数の型、戻り値の型がIntelliSenseで見えるわけでして。C#的にはIntelliSenseで見えないと価値は9割減です。というわけで、天秤にかければ、圧倒的にFuncの大勝利。引数ずらずらでイミフになるならドキュメントコメントに書くことで補う、でもいいぢゃない。

ちなみにoutやrefのジェネリックデリゲートは存在しないので、その場合のみ自作デリゲートを立てる必要があります。それ以外、つまるところ99%ぐらいはFunc, Action, EventHandler<T>でいいと思います。LINQだってPredicateじゃなくてFunc<T, bool>だしね。

  1. ラムダ式を使う

ラムダ式(C# 3.0)を使わなければ何を使うのって話ですが、ラムダ式の登場により割を食った匿名メソッド(C# 2.0)は産廃です。唯一の利点は、匿名メソッドは引数を使わない場合は省略して書けます。

// 引数省略して書けるぞ!
button.Click += delegate { MessageBox.Show("hoge"); };
// ラムダ式の場合は省略できないんだ(棒)
button.Click += (_, __) => MessageBox.Show("hoge");

こんなことは実にどうでもいいので、匿名メソッドを使うのはやめましょう。もしラムダ式が先にあれば、匿名メソッドはなかったと思います。ジェネリックが最初からあれば非ジェネリックコレクションクラスがなかっただろう、ということな程度には。あとジェネリックが先にあれば野良デリゲートもなかった気がする。なので、多少どうでもいい利点があったとしても、素直に使わないのが一番。

ところでラムダ式の引数の名前ですが、どうしていますか?私は、昔は型名から取っていました、例えばintだったらi、stringだったらs。でも最近は全てxにしています。理由は、面倒くさいし適切な名前が出てこない場合もあるし修正漏れが起こったりする(ハンガリアンみたいなもんですしねえ)などなどで、メリットを感じなかったので。

ちなみに、ラムダ式で長い名前を使うのは反対です。「名前はしっかりつけなきゃダメ!」が原則論のはずなのにxってなんだよそれって感じですが、逆に、小さい範囲のものは小さいほうがいいのです。名前をつけないことで、他の名前のついているものを強調します。なんでもかんでも名前をつけていると五月蝿くて、木を森に隠す、のようになってしまいます。LINQやRxでラムダ式だらけになると、なおそうです。勿論、ラムダ式だからって全てxにするわけではありません。中でネストしてネスト内でも使われたり、式ではなく文になってスコープが長くなっている場合などは、ちゃんと名前をつけます。また、(分かりやすさのため)強く意味を持たせたい場合も名前をつけます。型名以上の意味を持たせられないのなら、あえて名前をつける必要性を感じないのでxです。

そういうわけで、多少崩すこともありますが、原則的に私の命名規則は「ただの変数 = x, 配列などコレクション = xs, 引数を使わない = アンダースコア」としています。xのかわりにアンダースコアを使う流儀もあるようですが、私は嫌いですね……。Scalaのアンダースコアとは意味が違う感じもあるし、同じ.NETファミリーならばF#が引数を使わないという意味でアンダースコアを使っているので、それに合わせたほうがいいと思っています。xだと座標のxと被る、という場合は座標のxにつける変数名をpointXだかpxだかに変えます。

Exceptionはexにしたり、イベントの場合は(sender, e)にしたりはしますけれど、このへんは慣習ですし、わざわざ崩すほうが変かな。あとLINQでのGroupingはgを使ったりしますね。

  1. LINQを使う(主にメソッド構文を使う、クエリ構文もたまには使う)

LINQはデータベースのためだけじゃなく、むしろ通常のコレクションへの適用(LINQ to Objects)のほうが多い。そんなにコレクション操作することなんてない、わけがない、はず。

// 配列の中のYから始まるものの名前(スペースできった最後のもの)を取り出す
new[] { "Yamada Tarou", "Yamamoto Jirou", "Suzuki Saburou" }
    .Where(x => x.StartsWith("Y"))
    .Select(x => x.Split(' ').Last());

上の例のような、Where(フィルタリング)+ Select(射影)は特に良く使うパターンです。Pythonなどでもリスト内包表記としてパッケージされるぐらいには。やはり、この手の処理を持っていないと、重苦しい。しかし、C# 3.0はLINQを手にしたので、お陰で軽快に飛び回れるようになりました。しかもただのフィルタ+射影だけではなく、ありとあらゆる汎用コレクション処理を、チェーンで組み合わせることで、無限のパターンを手にしました。

LINQにはメソッド構文とクエリ構文があり、どちらも同じですがメソッド構文のほうが機能豊富だし、分かりやすいです。なのでメソッド構文でメソッドチェーンハァハァしましょう。linq.js - LINQ for JavaScriptで同様の記法でJavaScriptでも使えますし!

じゃあクエリ構文に利点はないのかというと当然そんなことはなく、多重from(SelectManyに変換される)が多く出現する場合はクエリ構文のほうがいいですね。また、Joinなどもクエリ構文のほうが書きやすいし、GroupJoinと合わせた左外部結合を記述したりなど複雑化する場合はクエリ構文じゃないと手に負えません(書けなくはないんですけどねえ)

それと、LINQ to SQLなどExpression Treeをそれぞれの独自プロパイダが解釈するタイプのものは、メソッド構文の豊富な記述可能性が逆に、プロパイダの解釈不能外に飛び出しがちなので、適度に制約の効いたクエリ構文だけで書いたほうがスムーズにいく可能性があります。

また、XMLはC#ではXmlReader/Writer, XmlDocument(DOM), XDocument(LINQ to XML)がありますが、そのうちDOMのXmlDocumentは産廃です。DOMって使いづらいのよね、それにSilverlightにはないし。メモリ内にツリーを持つタイプではXDocument(XElement)でLINQでハァハァするのが主流です。ちなみにXmlReader/Writerはストリーミング型なので別枠、ただ、生で使うことはあまりないと思います。特にWriterは、XStreamingElementを使えば省メモリなストリーミングで、Writeできる、しかもずっと簡単に。なので、使うことはないかと思います。

  1. Taskを使う(生スレッドを使わない)

マルチスレッドプログラミングしなきゃ!Threadを使おう?デリゲートのBeginInvokeがある?それともThreadPool?BackgroundWorkerもあるぞ!古い記事はこれらの使用方法が解説されてきました。そうです、今まではそれらしかなかったので。けれど、全部ゴミ箱に投げ捨てましょう。.NET 4.0からは基本的原則的にTaskを使うべきです。豊富な待ち合わせ処理・継続・例外処理・キャンセルなどをサポートしつつ、同じスレッドを使いまわそうとするなど実行効率も配慮されています。もはや生スレッドを使う理由はないし、デリゲートのBeginInvokeなどともさよなら。BackgroundWorkerは、もう少しは出番あるかも(UIへの通知周りが今のTaskだけだと少し面倒、RxやC# 5.0のAsyncなら簡単にこなせるのですが)。

CPUを使う処理を並列に実行をしたいのなら、PLINQやParallel.ForEachなどが手軽かつ超強力です。

また、C# 5.0からはTaskの言語サポートが入り、awaitキーワードによりコード上では待機したように見せかけ同期的のように書けつつ中身は非同期で動く、といったことが可能になります。

async Task<string> GetBingHtml()
{
    var wc = new WebClient();
    var html = await wc.DownloadStringTaskAsync(new Uri("http://bing.com/"));
    return html;
}

awaitするだけで同期的のように非同期が書けるなんて魔法のよう!

また、非同期といっても二つあります。CPUを沢山使って重たい処理と、I/O待ち(ネットワークやファイルアクセス)が重たい処理。これらへの対処は、別です。I/O待ちにスレッドを立てて対処することも可能ではありますが、あまり褒められた話ではありません。と、C#たんが非同期I/O待ちで言ってました。非同期I/Oは優れているのは分かったとしても、記述が面倒なのがネックだったのですね。しかし、C# 5.0からならばawaitが入るのでかなりサクッと書ける。非同期だって、node.jsにばかりは負けてられない!

なお、現在SilverlightやWindows Phone 7にはTaskがない(Silverlight 5にはTask入りました)ですが、将来的には間違いなく入るので、期待して待ちましょう。そして、分かりやすく書けるC# 5.0もwktkして待ちましょう。待ちきればければAsync CTPとして公開されているので、試すことが可能です。

  1. Rxを使う

C# 5.0はCTPだし、現実問題として非同期に困ってるんだよ!という場合は、変化球としてReactive Extensionsが使えます。詳しくはReactive Extensions(Rx)入門 - @ITで連載しているので読んでね!第二回がいつまでたっても始まらないのは何故なのでしょう、はい、私が原稿を送っていないからです、ごめんなさい……。これ書いてないで原稿書けやゴルァという感じですはい。いえ、もうすぐもう少しなので、ちょっと待ってください。

RxはTaskとは全く別の次元からやってきつつ、機能的にはある程度代替可能です。C# 5.0が来た時に共存できるのか、というと、非同期面ではTaskに譲るでしょう。けれど、非同期の生成をTaskで行なって、コントロールをawaitも使いつつRxでメインに使うとかも可能です。基本的に、コントロール周りはawaitサポートを除けばRxのほうが強力で柔軟です(代償として効率を若干犠牲にしているけれど)。ただまあ、基本的には非同期処理はTaskに絞られていくだろうと考えています。少し寂しいけど、全体としてより美しく書けるなら全然いいです、むしろ大歓迎なので早くC#5.0来ないかなあ。

ちなみに、Rxは別に非同期のためだけじゃなくて、イベントと、そしてあらゆるソースを合成するという点も見逃せないわけなので、決してAsync来たからRxさようなら、ではないです。その辺のことも連載であうあう。

  1. Expressionを使う(そしてEmitしない)

Expressionの簡単な基本ですが、Expressionとして宣言します。Funcとほとんど同じで、違うのは型宣言だけです。

// 同じラムダ式だけれど
Expression<Func<int, int>> expr = x => x * x;
Func<int, int> func = x => x * x;

// Expressionで宣言すると実態は以下のものになる(コンパイラが自動生成する)
var paramX = Expression.Parameter(typeof(int), "x");
var expr = Expression.Lambda<Func<int, int>>(
    Expression.Multiply(paramX, paramX),
    new[] { paramX });

Expressionで宣言するとコンパイラがコンパイル時に式木生成コードに変換してくれるのですね。自分で宣言しなくても、メソッドの引数の型がExpressionならば同じです。例えばQueryable.SelectのselectorはExpression型の引数なので、Queryableで連鎖を書いているということは同様に↑のようなコードが吐かれています。

Expressionの仕事は色々ありますが、概ね二つ。式がデータとして取り出せること。簡単な所ではINotifyPropertyChangedの実装なので話題沸騰したりしなかったりした、文字列ではなくプロパティを渡して、そこから引数名を取り出すことができること。

public class MyClass
{
    public string MyProperty { get; set; }
}

// これ
public static string GetPropertyName<T>(Expression<Func<T>> propertyExpression)
{
    return (propertyExpression.Body as MemberExpression).Member.Name;
}

static void Main(string[] args)
{
    var mc = new MyClass();
    var propName = GetPropertyName(() => mc.MyProperty);
}

こんな形で推し進めたのが、LINQ to SQLなど、式木の塊を解釈してSQLに変換するといった、QueryProviderですね。

そしてもう一つはILビルダー。式木はCompileすることでFuncに変換することが可能です。

// (object target, object value) => ((T)target).memberName = (U)value
static Action<object, object> CreateSetDelegate(Type type, string memberName)
{
    var target = Expression.Parameter(typeof(object), "target");
    var value = Expression.Parameter(typeof(object), "value");
 
    var left =
        Expression.PropertyOrField(
            Expression.Convert(target, type), memberName);
 
    var right = Expression.Convert(value, left.Type);
 
    var lambda = Expression.Lambda<Action<object, object>>(
        Expression.Assign(left, right),
        target, value);
 
    return lambda.Compile();
}
 
// Test
static void Main(string[] args)
{
    var target = new MyClass { MyProperty = 200 };
    var accessor = CreateSetDelegate(typeof(MyClass), "MyProperty");
 
    accessor(target, 1000); // set
    Console.WriteLine(target.MyProperty); // 1000
}

少なくとも、自前でILを書くよりは圧倒的に簡単に、動的コード生成を可能にしました。動的コード生成はCompileは重いものの、一度生成したデリゲートをキャッシュすることで二度目以降は超高速になります。単純なリフレクションよりはずっと速く。といったようなことをneue cc - Expression Treeのこね方・入門編 - 動的にデリゲートを生成してリフレクションを高速化で書いたので読んでね!

  1. dynamicを使わない(部分的に使う)

一時期、LLブームで動的言語が持て囃されましたが、今は(静的型付けの)関数型言語ブームで、静的型付けへ寄り戻しが来ています。なので、C#もdynamicあって動的だよねひゃっほーい、なんてことはなく、むしろvarで型推論です(キリッ のほうが正しくて。そんなわけで、dynamicはあまり使いません。ですが、使うとより素敵な場所も幾つかあります。それは、本質的に動的なところに対して。動的なのってどこ?というと、アプリケーションの管理範囲外。

例えばJSONはスキーマレス。DBも、自動生成しなければアプリケーションの外側で見えない。.NETはDLRがあるのでIronPythonなどスクリプト言語との連携などもそう。DynamicJsonを例にだすと、スキーマレスなJSONに対して、そのまま、JSONをJavaScriptで扱うのと同じように使えます。

var json = DynamicJson.Parse(@"{""foo"":""json"", ""bar"":100, ""nest"":{ ""foobar"":true } }");

var r1 = json.foo; // "json" - dynamic(string)
var r2 = json.bar; // 100 - dynamic(double)
var r3 = json.nest.foobar; // true - dynamic(bool)

また、動的な存在であるDBへのIDataRecordも、DbExecutorを例に出すと

var products = DbExecutor.ExecuteReaderDynamic(new SqlConnection(connStr), @"
        select ProductName, QuantityPerUnit from Products
        where SupplierID = @SupplierID and UnitPrice > @UnitPrice
        ", new { SupplierID = 1, UnitPrice = 10 })
    .Select(d => new Product
    {
        ProductName = d.ProductName,
        QuantityPerUnit = d.QuantityPerUnit
    })
    .ToArray();

ドットで自然に参照可能なことと、また、dynamicが自動でキャストしてくれるので明示的なキャストが不要なため、取り回しの面倒な手動DBアクセスが随分と簡単になります。(が、DBのアクセス結果を決まったクラスにマッピングするのなら、9で紹介している動的コード生成でアクセサを作ったほうが更に楽々になるのでそこまで出番はないかも。DbExecutorは両方を搭載しているので、必要に応じて選ぶことが可能です)

  1. 自動プロパティを使う

自動プロパティ vs パブリックフィールド。同じです。はい、同じです。じゃあパブリックフィールドでいいぢゃん?という話が度々ありますが、いえ、そんなことはありません。プロパティにするとカプセル化が云々、変更した場合に云々、などは割とどうでもいいのですが、重要な違いはちゃんとあります。

WPFとかASP.NETとかのバインディングがプロパティ大前提だったりしてパブリックフィールドだと動かないことがある。

なので、黙って自動プロパティにしておきましょう。それに関連してですが、リフレクションでの扱い易さがプロパティとフィールドでは全然違って、プロパティだと断然楽だったりします。そういう面でサードパーティのライブラリでも、プロパティだけをサポート、なものも割とあるのではかと思います。具体例はあげられませんが私は自分で作るちょろっとリフレクションもにょもにょ系の小粒なライブラリは面倒なのでプロパティだけサポート、にすることが結構あります。

あと自動プロパティの定義はコードスニペットを使って一行でやりましょう。prop->Tab->Tabです。

public int MyProperty { get; set; }

get, setで改行して5行使ったりするのはコードの可読性が落ちるので、好きじゃありません。コードスニペットで一行のものが生成されるわけなので、それに従うという意味でも、一行で書くのがベストと思います。

まとめ

C#は言語がちゃんと進化を続けてきた。進化って無用な複雑化!ではなくて、基本的には今までよくなかった、やりづらいことを改善するために進化するんですよね。だから、素直にその良さを甘受したい。そしてまた、進化するということは、歴史の都合上で廃棄物が出てきてしまうというのもまた隣合わせ。C#は巧妙に、多量の廃棄物が出現するのを避けてきていると思います。ヘジたんの手腕が光ります。しかし、やはりどうしても幾つかの廃棄物に関しては致し方ないところです。それに関しては、ノウハウとして、自分で避けるしかなくて。

この手の話だったら、.NETのクラスライブラリ設計が良いです。もしお持ちでなければ、今すぐ買いましょう!いますぐ!超おすすめです。

また、この手の本だったらEffective C# 4.0もかしら。第一版は(翻訳が出たのが既にC# 4.0の頃で1.0の内容)古くてう~ん、といった感だったのですが、第二版(C# 4.0対応)はかなり良かったです。More Effective C#のほうはLINQ前夜といった感じの内容で若干微妙なのですが、LINQ的な考えが必要な理由を抑える、という点では悪くないかもしれません。また、決定版的な内容を求めるならば、読み通す必要はなく気になるところつまみ読みで良いので、プログラミング.NET Frameworkがお薦めです。

これらに加えて、自分のやりたいことの対象フレームワークの本(ASP.NET/ASP.NET MVC/Win Forms/WPF/WCF/Silverlight/Windows Phone 7/Unity)を一冊用意すれば、導入としては準備万端で素敵ではないかしらん。まあ、今時フレームワークとかの先端の部分だと、フレームワークの進化の速度が速すぎて本だと情報の鮮度が落ちる(特に日本だと)ので、基本は本でサッと抑えて、深い部分はネットの記事を見たほうが良いのではかと思います。ソースコードが公開されていたりフレームワークの制作陣や第一人者が情報出していたりしますしね。本こそが情報の基本にして全て、という時代でもないのだなぁ、と。学習の仕方というのも、時代で変わっていくものだと思います。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive