The History of LINQ
- 2013-06-23
という内容で、つくばC#勉強会で話してきました。
初心者向け勉強会(?)ということで、歴史ですかにぇー。詳細よりは、全体的にあっさり、みたいな。多分ね!Cωとかもう10年前ですが、これだけ時間のたった今だからこそ、あらためてちょっと見てみると面白いね、みたいな。
第一回つくばC#勉強会 #tkbcsmt 当日の様子ということで実にカオス、よかったね!面白かったです~。
TypeScript 0.9のジェネリクス対応でlinq.jsの型定義作って苦労した話
- 2013-06-20
久しぶりのTypeScript。Announcing TypeScript 0.9というわけで、ついに待望のジェネリクスが搭載されました。やったね!というわけで、ジェネリクス対応のlinq.jsの型定義を早速作りました、と。ver 3.0.4-Beta5です。まだまだBeta、すみませんすみません、色々忙しくて……。NuGetからもしくはサイトからのダウンロードで公開してます。
とりあえず例として使ってみた感じ。
// booleanしか受け入れないwhereにnumberを突っ込んだらちゃんと怒ってくれるよ!
// Call signatures of types '(x: any) => number' and '(element: number, index: number) => boolean' are incompatible.
var seq = Enumerable.from([1, 10, 100, 100]).where(x => x * 2);
やったー。これだよこれ!LINQはジェネリクスがあって本当の本当の真価を発揮するんです!!!
面白げな型定義
C#とちょっと違うところとしては、ジェネリクスの型変数のところにもオブジェクトを突っ込めるんですね。だから
// from(obj)の型定義はこんな感じ。
from(obj: any): IEnumerable<{ key: string; value: any }>;
// ちなみにfromのオーバーロードはいっぱいある
// linq.js自体はJScriptのIEnumerableとWinMDのIIterable<T>にも対応してるのですがTSで表現できないので、そこは未定義……。。。
from(): IEnumerable<any>; // empty
from<T>(obj: IEnumerable<T>): IEnumerable<T>;
from(obj: number): IEnumerable<number>;
from(obj: boolean): IEnumerable<boolean>;
from(obj: string): IEnumerable<string>;
from<T>(obj: T[]): IEnumerable<T>;
from<T>(obj: { length: number;[x: number]: T; }): IEnumerable<T>;
このkey,valueはどこから出てきたんだよって感じでわかりづらかったので、こうして見えてくれると嬉しい度高い。
型消去
場合によってはIEnumerable<any>となるため、Tの型をつけてあげたかったり、もしくは強引に変換したかったり(例えば↑のIIterable<T>は{key,value}に解釈されてしまうので、明示的に変換してあげる必要がある)する場合のために、castメソッドを用意しました。
// any[]型で渡ってきたりする場合に
var seq: any[] = [1, 2, 3, 4, 5];
var result = Enumerable.from(seq)
.cast<number>() // numberに変換
.select(x => x * x) // x:number
.toArray(); // result:number[]
さて、ところで、TypeScriptのジェネリクスは型消去(Type Erasure)式です。コンパイル結果のJSは以下のような感じで
var result = Enumerable.from(seq)
.cast()
.select(function (x) { return x * x; })
.toArray();
一切の型は消えています。なので、TypeScriptでTの型を取って実行時に扱ったりはできません。型が欲しければ型を渡せ方式。
linq.jsにはofTypeという、型でフィルタリングするメソッドがあるのですが
// 混在した配列から、型でフィルタして取り出す
var mixed: any[] = [1, "hoge", 100, true, "nano"];
// このままじゃ型が分からないのでresultはany[]
var result1 = Enumerable.from(mixed)
.ofType(Number) // 数値型のみでフィルタ、つまり[1, 100]
.toArray();
// ofTypeの後にcastするか、もしくはofTypeで型指定するか
var result2 = Enumerable.from(mixed)
.ofType<number>(Number)
.toArray();
というように、<number>(Number)と連続するのが非常に不恰好……。まあ、この辺はそういうものなのでしょうがないと諦めましょう。
地雷ふんだり
さて、そんな素敵なTypeScript 0.9なのですが、残念なお知らせ。現在の、というか、この0.9ですが、完成度はものすごーーーーーーーーく、低いです。はい、超低いです。Visual Studioと組み合わせて使う場合、半端無く動作も補完も遅いです。正直、ベータどころかアルファぐらいのクオリティで、何故に堂々と出してきたのか理解に苦しむ。遅いだけならまだしも、ジェネリクスの解釈が非常に怪しく、地雷を踏むと補完やエラー通知が消え去ります。いや、消え去るだけならまだよくて、Visual Studioと裏で動くTypeScript Compilerが大暴走をはじめてCPUが100%に張り付いたりします。もうヤヴァい。タスクマネージャー開きっぱにして警戒しながらじゃないと書けないです。linq.jsの型定義書くの超絶苦労した……。
interface IEnumerable<T> {
// 戻り値の型をIEnumerable<IGrouping<TKey, T>>にしたいのですが、
// interface側で定義しているTをネストした型変数に使うとVSが大暴走して死ぬ
// ので、今回のバージョンではanyにしてます
groupBy<TKey>(keySelector: (element: T) => TKey): IEnumerable<IGrouping<TKey, any>>;
}
具体的に踏んだのは↑ですねえ。最初原因が分かってなくてかなり時間食われちゃいました。んもー。最小セットで試してる限りでは、コンパイルエラーにはなるんですが(A generic type may not reference itself with a wrapped form of its own type parameters.)暴走はしない、んですがlinq.d.tsで弄ってると死ぬ。おうふ。もう定義の仕方が悪いのかコンパイラがアレなのか判断つかないのでしんどい。
そんなわけで、0.8の完成度からだいぶ退化して、実用性はゼロになってしまいました。私の環境だけ、じゃあないよねえ…‥?とりあえず0.9.1を待ちましょう。どうやらコンパイラをまるっと書き換えたそうですしねー、初物だからshoganaiと思うことにして。しかし泣きたい。
linq.d.ts
とりあえず、こんな感じになってます。
// Type Definition for linq.js, ver 3.0.4-Beta5
declare module linqjs {
interface IEnumerator<T> {
current(): T;
moveNext(): boolean;
dispose(): void;
}
interface Enumerable {
Utils: {
createLambda(expression: any): (...params: any[]) => any;
createEnumerable<T>(getEnumerator: () => IEnumerator<T>): IEnumerable<T>;
createEnumerator<T>(initialize: () => void , tryGetNext: () => boolean, dispose: () => void ): IEnumerator<T>;
extendTo(type: any): void;
};
choice<T>(...params: T[]): IEnumerable<T>;
cycle<T>(...params: T[]): IEnumerable<T>;
empty<T>(): IEnumerable<T>;
// from<T>, obj as JScript's IEnumerable or WinMD IIterable<T> is IEnumerable<T> but it can't define.
from(): IEnumerable<any>; // empty
from<T>(obj: IEnumerable<T>): IEnumerable<T>;
from(obj: number): IEnumerable<number>;
from(obj: boolean): IEnumerable<boolean>;
from(obj: string): IEnumerable<string>;
from<T>(obj: T[]): IEnumerable<T>;
from<T>(obj: { length: number;[x: number]: T; }): IEnumerable<T>;
from(obj: any): IEnumerable<{ key: string; value: any }>;
make<T>(element: T): IEnumerable<T>;
matches<T>(input: string, pattern: RegExp): IEnumerable<T>;
matches<T>(input: string, pattern: string, flags?: string): IEnumerable<T>;
range(start: number, count: number, step?: number): IEnumerable<number>;
rangeDown(start: number, count: number, step?: number): IEnumerable<number>;
rangeTo(start: number, to: number, step?: number): IEnumerable<number>;
repeat<T>(element: T, count?: number): IEnumerable<T>;
repeatWithFinalize<T>(initializer: () => T, finalizer: (element) => void ): IEnumerable<T>;
generate<T>(func: () => T, count?: number): IEnumerable<T>;
toInfinity(start?: number, step?: number): IEnumerable<number>;
toNegativeInfinity(start?: number, step?: number): IEnumerable<number>;
unfold<T>(seed: T, func: (value: T) => T): IEnumerable<T>;
defer<T>(enumerableFactory: () => IEnumerable<T>): IEnumerable<T>;
}
interface IEnumerable<T> {
constructor(getEnumerator: () => IEnumerator<T>);
getEnumerator(): IEnumerator<T>;
// Extension Methods
traverseBreadthFirst(func: (element: T) => IEnumerable<T>): IEnumerable<T>;
traverseBreadthFirst<TResult>(func: (element: T) => IEnumerable<T>, resultSelector: (element: T, nestLevel: number) => TResult): IEnumerable<TResult>;
traverseDepthFirst<TResult>(func: (element: T) => Enumerable): IEnumerable<T>;
traverseDepthFirst<TResult>(func: (element: T) => Enumerable, resultSelector?: (element: T, nestLevel: number) => TResult): IEnumerable<TResult>;
flatten(): IEnumerable<any>;
pairwise<TResult>(selector: (prev: T, current: T) => TResult): IEnumerable<TResult>;
scan(func: (prev: T, current: T) => T): IEnumerable<T>;
scan<TAccumulate>(seed: TAccumulate, func: (prev: TAccumulate, current: T) => TAccumulate): IEnumerable<TAccumulate>;
select<TResult>(selector: (element: T, index: number) => TResult): IEnumerable<TResult>;
selectMany<TOther>(collectionSelector: (element: T, index: number) => IEnumerable<TOther>): IEnumerable<TOther>;
selectMany<TCollection, TResult>(collectionSelector: (element: T, index: number) => IEnumerable<TCollection>, resultSelector: (outer: T, inner: TCollection) => TResult): IEnumerable<TResult>;
selectMany<TOther>(collectionSelector: (element: T, index: number) => TOther[]): IEnumerable<TOther>;
selectMany<TCollection, TResult>(collectionSelector: (element: T, index: number) => TCollection[], resultSelector: (outer: T, inner: TCollection) => TResult): IEnumerable<TResult>;
selectMany<TOther>(collectionSelector: (element: T, index: number) => { length: number;[x: number]: TOther; }): IEnumerable<TOther>;
selectMany<TCollection, TResult>(collectionSelector: (element: T, index: number) => { length: number;[x: number]: TCollection; }, resultSelector: (outer: T, inner: TCollection) => TResult): IEnumerable<TResult>;
where(predicate: (element: T, index: number) => boolean): IEnumerable<T>;
choose(selector: (element: T, index: number) => T): IEnumerable<T>;
ofType<TResult>(type: any): IEnumerable<TResult>;
zip<TResult>(second: IEnumerable<T>, resultSelector: (first: T, second: T, index: number) => TResult): IEnumerable<TResult>;
zip<TResult>(second: { length: number;[x: number]: T; }, resultSelector: (first: T, second: T, index: number) => TResult): IEnumerable<TResult>;
zip<TResult>(second: T[], resultSelector: (first: T, second: T, index: number) => TResult): IEnumerable<TResult>;
zip<TResult>(...params: any[]): IEnumerable<TResult>; // last one is selector
merge<TResult>(...params: IEnumerable<T>[]): IEnumerable<T>;
merge<TResult>(...params: { length: number;[x: number]: T; }[]): IEnumerable<T>;
merge<TResult>(...params: T[][]): IEnumerable<T>;
join<TInner, TKey, TResult>(inner: IEnumerable<TInner>, outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
join<TInner, TKey, TResult>(inner: { length: number;[x: number]: TInner; }, outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
join<TInner, TKey, TResult>(inner: TInner[], outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
groupJoin<TInner, TKey, TResult>(inner: IEnumerable<TInner>, outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
groupJoin<TInner, TKey, TResult>(inner: { length: number;[x: number]: TInner; }, outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
groupJoin<TInner, TKey, TResult>(inner: TInner[], outerKeySelector: (outer: T) => TKey, innerKeySelector: (inner: TInner) => TKey, resultSelector: (outer: T, inner: TKey) => TResult, compareSelector?: (obj: T) => TKey): IEnumerable<TResult>;
all(predicate: (element: T) => boolean): boolean;
any(predicate?: (element: T) => boolean): boolean;
isEmpty(): boolean;
concat(...sequences: IEnumerable<T>[]): IEnumerable<T>;
concat(...sequences: { length: number;[x: number]: T; }[]): IEnumerable<T>;
concat(...sequences: T[]): IEnumerable<T>;
insert(index: number, second: IEnumerable<T>): IEnumerable<T>;
insert(index: number, second: { length: number;[x: number]: T; }): IEnumerable<T>;
alternate(alternateValue: T): IEnumerable<T>;
alternate(alternateSequence: { length: number;[x: number]: T; }): IEnumerable<T>;
alternate(alternateSequence: IEnumerable<T>): IEnumerable<T>;
alternate(alternateSequence: T[]): IEnumerable<T>;
contains(value: T): boolean;
contains<TCompare>(value: T, compareSelector?: (element: T) => TCompare): boolean;
defaultIfEmpty(defaultValue?: T): IEnumerable<T>;
distinct(): IEnumerable<T>;
distinct<TCompare>(compareSelector: (element: T) => TCompare): IEnumerable<T>;
distinctUntilChanged(): IEnumerable<T>;
distinctUntilChanged<TCompare>(compareSelector: (element: T) => TCompare): IEnumerable<T>;
except(second: { length: number;[x: number]: T; }): IEnumerable<T>;
except<TCompare>(second: { length: number;[x: number]: T; }, compareSelector: (element: T) => TCompare): IEnumerable<T>;
except(second: IEnumerable<T>): IEnumerable<T>;
except<TCompare>(second: IEnumerable<T>, compareSelector: (element: T) => TCompare): IEnumerable<T>;
except(second: T[]): IEnumerable<T>;
except<TCompare>(second: T[], compareSelector: (element: T) => TCompare): IEnumerable<T>;
intersect(second: { length: number;[x: number]: T; }): IEnumerable<T>;
intersect<TCompare>(second: { length: number;[x: number]: T; }, compareSelector: (element: T) => TCompare): IEnumerable<T>;
intersect(second: IEnumerable<T>): IEnumerable<T>;
intersect<TCompare>(second: IEnumerable<T>, compareSelector: (element: T) => TCompare): IEnumerable<T>;
intersect(second: T[]): IEnumerable<T>;
intersect<TCompare>(second: T[], compareSelector: (element: T) => TCompare): IEnumerable<T>;
union(second: { length: number;[x: number]: T; }): IEnumerable<T>;
union<TCompare>(second: { length: number;[x: number]: T; }, compareSelector: (element: T) => TCompare): IEnumerable<T>;
union(second: IEnumerable<T>): IEnumerable<T>;
union<TCompare>(second: IEnumerable<T>, compareSelector: (element: T) => TCompare): IEnumerable<T>;
union(second: T[]): IEnumerable<T>;
union<TCompare>(second: T[], compareSelector: (element: T) => TCompare): IEnumerable<T>;
sequenceEqual(second: { length: number;[x: number]: T; }): boolean;
sequenceEqual<TCompare>(second: { length: number;[x: number]: T; }, compareSelector: (element: T) => TCompare): boolean;
sequenceEqual(second: IEnumerable<T>): boolean;
sequenceEqual<TCompare>(second: IEnumerable<T>, compareSelector: (element: T) => TCompare): boolean;
sequenceEqual(second: T[]): boolean;
sequenceEqual<TCompare>(second: T[], compareSelector: (element: T) => TCompare): boolean;
orderBy<TKey>(keySelector: (element: T) => TKey): IOrderedEnumerable<T>;
orderByDescending<TKey>(keySelector: (element: T) => TKey): IOrderedEnumerable<T>;
reverse(): IEnumerable<T>;
shuffle(): IEnumerable<T>;
weightedSample(weightSelector: (element: T) => number): IEnumerable<T>;
// truly, return type is IEnumerable<IGrouping<TKey, T>> but Visual Studio + TypeScript Compiler can't compile.
groupBy<TKey>(keySelector: (element: T) => TKey): IEnumerable<IGrouping<TKey, T>>;
groupBy<TKey, TElement>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement): IEnumerable<IGrouping<TKey, TElement>>;
groupBy<TKey, TElement, TResult>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, resultSelector: (key: TKey, element: IEnumerable<TElement>) => TResult): IEnumerable<TResult>;
groupBy<TKey, TElement, TResult, TCompare>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, resultSelector: (key: TKey, element: IEnumerable<TElement>) => TResult, compareSelector: (element: T) => TCompare): IEnumerable<TResult>;
// :IEnumerable<IGrouping<TKey, T>>
partitionBy<TKey>(keySelector: (element: T) => TKey): IEnumerable<IGrouping<TKey, any>>;
// :IEnumerable<IGrouping<TKey, TElement>>
partitionBy<TKey, TElement>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement): IEnumerable<IGrouping<TKey, TElement>>;
partitionBy<TKey, TElement, TResult>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, resultSelector: (key: TKey, element: IEnumerable<TElement>) => TResult): IEnumerable<TResult>;
partitionBy<TKey, TElement, TResult, TCompare>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, resultSelector: (key: TKey, element: IEnumerable<TElement>) => TResult, compareSelector: (element: T) => TCompare): IEnumerable<TResult>;
buffer(count: number): IEnumerable<T>;
aggregate(func: (prev: T, current: T) => T): T;
aggregate<TAccumulate>(seed: TAccumulate, func: (prev: TAccumulate, current: T) => TAccumulate): TAccumulate;
aggregate<TAccumulate, TResult>(seed: TAccumulate, func: (prev: TAccumulate, current: T) => TAccumulate, resultSelector: (last: TAccumulate) => TResult): TResult;
average(selector?: (element: T) => number): number;
count(predicate?: (element: T, index: number) => boolean): number;
max(selector?: (element: T) => number): number;
min(selector?: (element: T) => number): number;
maxBy<TKey>(keySelector: (element: T) => TKey): T;
minBy<TKey>(keySelector: (element: T) => TKey): T;
sum(selector?: (element: T) => number): number;
elementAt(index: number): T;
elementAtOrDefault(index: number, defaultValue?: T): T;
first(predicate?: (element: T, index: number) => boolean): T;
firstOrDefault(predicate?: (element: T, index: number) => boolean, defaultValue?: T): T;
last(predicate?: (element: T, index: number) => boolean): T;
lastOrDefault(predicate?: (element: T, index: number) => boolean, defaultValue?: T): T;
single(predicate?: (element: T, index: number) => boolean): T;
singleOrDefault(predicate?: (element: T, index: number) => boolean, defaultValue?: T): T;
skip(count: number): IEnumerable<T>;
skipWhile(predicate: (element: T, index: number) => boolean): IEnumerable<T>;
take(count: number): IEnumerable<T>;
takeWhile(predicate: (element: T, index: number) => boolean): IEnumerable<T>;
takeExceptLast(count?: number): IEnumerable<T>;
takeFromLast(count: number): IEnumerable<T>;
indexOf(item: T): number;
indexOf(predicate: (element: T, index: number) => boolean): number;
lastIndexOf(item: T): number;
lastIndexOf(predicate: (element: T, index: number) => boolean): number;
asEnumerable(): IEnumerable<T>;
cast<TResult>(): IEnumerable<TResult>;
toArray(): T[];
// truly, return type is ILookup<TKey, T> but Visual Studio + TypeScript Compiler can't compile.
toLookup<TKey>(keySelector: (element: T) => TKey): ILookup<TKey, any>;
toLookup<TKey, TElement>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement): ILookup<TKey, TElement>;
toLookup<TKey, TElement, TCompare>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TElement, compareSelector: (key: TKey) => TCompare): ILookup<TKey, TElement>;
toObject(keySelector: (element: T) => any, elementSelector?: (element: T) => any): Object;
// :IDictionary<TKey, T>
toDictionary<TKey>(keySelector: (element: T) => TKey): IDictionary<TKey, any>;
toDictionary<TKey, TValue>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TValue): IDictionary<TKey, TValue>;
toDictionary<TKey, TValue, TCompare>(keySelector: (element: T) => TKey, elementSelector: (element: T) => TValue, compareSelector: (key: TKey) => TCompare): IDictionary<TKey, TValue>;
toJSONString(replacer: (key: string, value: any) => any): string;
toJSONString(replacer: any[]): string;
toJSONString(replacer: (key: string, value: any) => any, space: any): string;
toJSONString(replacer: any[], space: any): string;
toJoinedString(separator?: string): string;
toJoinedString<TResult>(separator: string, selector: (element: T, index: number) => TResult): string;
doAction(action: (element: T, index: number) => void ): IEnumerable<T>;
doAction(action: (element: T, index: number) => boolean): IEnumerable<T>;
forEach(action: (element: T, index: number) => void ): void;
forEach(action: (element: T, index: number) => boolean): void;
write(separator?: string): void;
write<TResult>(separator: string, selector: (element: T) => TResult): void;
writeLine(): void;
writeLine<TResult>(selector: (element: T) => TResult): void;
force(): void;
letBind<TResult>(func: (source: IEnumerable<T>) => { length: number;[x: number]: TResult; }): IEnumerable<TResult>;
letBind<TResult>(func: (source: IEnumerable<T>) => TResult[]): IEnumerable<TResult>;
letBind<TResult>(func: (source: IEnumerable<T>) => IEnumerable<TResult>): IEnumerable<TResult>;
share(): IDisposableEnumerable<T>;
memoize(): IDisposableEnumerable<T>;
catchError(handler: (exception: any) => void ): IEnumerable<T>;
finallyAction(finallyAction: () => void ): IEnumerable<T>;
log(): IEnumerable<T>;
log<TValue>(selector: (element: T) => TValue ): IEnumerable<T>;
trace(message?: string): IEnumerable<T>;
trace<TValue>(message: string, selector: (element: T) => TValue ): IEnumerable<T>;
}
interface IOrderedEnumerable<T> extends IEnumerable<T> {
createOrderedEnumerable<TKey>(keySelector: (element: T) => TKey, descending: boolean): IOrderedEnumerable<T>;
thenBy<TKey>(keySelector: (element: T) => TKey): IOrderedEnumerable<T>;
thenByDescending<TKey>(keySelector: (element: T) => TKey): IOrderedEnumerable<T>;
}
interface IDisposableEnumerable<T> extends IEnumerable<T> {
dispose(): void;
}
interface IDictionary<TKey, TValue> {
add(key: TKey, value: TValue): void;
get(key: TKey): TValue;
set(key: TKey, value: TValue): boolean;
contains(key: TKey): boolean;
clear(): void;
remove(key: TKey): void;
count(): number;
toEnumerable(): IEnumerable<{ key: TKey; value: TValue }>;
}
interface ILookup<TKey, TElement> {
count(): number;
get(key: TKey): IEnumerable<TElement>;
contains(key: TKey): boolean;
toEnumerable(): IEnumerable<IGrouping<TKey, TElement>>;
}
interface IGrouping<TKey, TElement> extends IEnumerable<TElement> {
key(): TKey;
}
}
// export definition
declare var Enumerable: linqjs.Enumerable;
アップデートを全然追ってないので、初期に作った定義の仕方のまんまなので、大丈夫かな、まあ、大丈夫じゃないかな、きっと。
とりあえずとにかく面倒くさかった。しかし定義する人が苦労すれば、利用者はハッピーになれるから!!!なのではやくVS対応がまともになってください。ぶんぶんぶん回したいのだけれどねえ←その前にlinq.js ver.3がいつまでもBetaなのをなんとかしろ
C#の強み、或いは何故PHPから乗り換えるのか
- 2013-06-14
という内容で、C#ユーザー会で話してきました。
特にPHPディスりたいわけでは、あるのかないのかはともかく、やっぱり実際に使ってきて良いところというのも分からなくもない感じです。会場でも話したのは、短期的な開発速度には有利なのは間違いないのかな、と。デプロイとかも、とりあえずポン置きでいいし、開発も、なんかもう複雑なことやると面倒だし、どうせ文字列だらけになるしで、開き直ってハードコーディングでバカバカ作っていくから速い、とか。ただし勿論あとで苦労するわけですがそれはそれとして。けれどやっぱC#良いよね、って。
言語も色々なトレードオフで成り立つわけですが、その中でもC#は、バランス良くて好きだなーというのが私の個人的なところです。Visual Studio良いよねー、でもいいですしLINQ良いよねー、もいいですし、IntelliSenseがないと生きていけないですし。うん、そう、IntelliSense指向言語が好きなわけです。
ほとんどVisual Studioの話じゃねーか、というのは、まぁそうなのですけれど、大事なのはVisual Studioを前提においた言語構造になってるってとこです。強力すぎる型推論は、100%の入力補完を実現できなかったりする。強力すぎる動的さは100%の入力補完を実現できなかったりする。C#がVisual Studioとともに使って快適なのは、そういう言語設計になっているからです。コンパイルの速さも重要で。C#は他のコンパイル型言語に比べて速い部類に入ります。だから快適だし、エラー通知とかもリアルタイム。目に見えないところ、使ってみないと評価しにくい、ただの○×表だけの性能比較にはない部分、結構多いものです。
.NET最先端技術によるハイパフォーマンスウェブアプリケーション FAQ
- 2013-06-10
Build Insider Offlineにて、「.NET最先端技術によるハイパフォーマンスウェブアプリケーション」と題して、グラニのC#によるウェブアプリケーション作成の仕組みについて話してきました。
一日でViewsが1万、はてブが250と、ドトネト系にしては珍しく多くの人に見てもらえたようでなにより。
今までのC#関連って、MS純正ライブラリを使ったどうのこうの、というのはありましたが、外部ライブラリを組み合わせて、実践的にどうしているかっていうような話ってほとんどなかったんですよね。やってるところがない、ことはないのですが、しかし表にない、見えないものはないに等しいです。
別にC#だって自分達の手でライブラリを選び、作り、組み上げていく。それがこれからの時代のスタンダードです。遅れていたのかもしれません。しかし、遅すぎるなんてことはない。素材は良いし、.NETは死んだのでなく、むしろ風が吹いてきている。リアルなモデルケースとして、引っ張っていけたらと思っています。
FAQ
- 今時Windowsサーバー?
AWSやAzureなどで簡単にWindowsインスタンスの立ち上げが可能なので、今時というか普通に選択肢に入ってくれるといいですねえ、これからは。ウェブ=LAMPとか誰が決めたの?という話で。コスト面ではそんなに高くなるわけでもないし、それで開発効率が上がったり、サーバー台数が削減されるなら、むしろプラスです。開発効率に関しては人員次第ですが、うちのメンバーの習熟度で言ったら間違いなく上がります、圧倒的に。
- サーバーサイドでトラフィック以外がくってるのおかしい
そうそう、一般的にウェブではネックなのは通信部分だけ、と思っていたことも有りました。でもまぁ、クックパッドのRails アプリケーションのパフォーマンスについて RubyKaigi 2013 で発表しましたのスライドのResponse time breakdownのところ。SQLが20%がRubyが80%とあるんですね。うちのグラフと一緒?CakePHPはとにかくアレなわけですが、重量級フレームワークは割とそうなるもんなのかしらねえ、と少しだけホッとしたような絶望したような。
先入観じゃなくちゃんとモニタリングしてくのが大事、と当たり前の話ですけれど。スライドでも推しましたがNew Relicは本当に最高なので、よほどの事情がない限りは入れるべきかなぁ、と。PHP, Ruby, Java, .NET, Python, Node.js、多くの言語に対応しています。確実にパワーは喰いますが、それでも全然お釣りくる。
- アプリケーションサーバー:DBマスター:DBスレーブの比率がおかしい
ええと、これ、実質的にはSlaveは0です。色々な経緯があって、アプリ本体からは参照も全てMasterにし か振ってません。Slaveの用途は管理画面からの参照用とか、その程度です(スタンバイという点でも、RDSのMulti-AZを利用しているので意味は無い)。となると比率はますますオカシクなるわけですが、うーん、まあ、今時のDBってなんだかんだで頑丈ですからねえ、そう増えないかなあ。しかしそれにしてもアプリケーションサーバーの台数が多すぎなのは、まぁもう本当にすみませんすみませんって感じなわけなのですが。みんなCakePHP触ってみるといいですよ、XHProfにかけると絶望しますから。
勿論チューニングの余地はあります。ありますし、最後にはCakePHPを捨てるというところになることも見えるし、PHPにこだわる理由がどこにもありません。それならC#移行に、といったところですね。とはいえ実際のとこ増え続けることによる歪みが色々発生してるので、ヒィヒィ言いながら適宜対処してます。あと、最低限というか割とそれなりにはCakePHP本体に手を入れたりもしてはいるんですけど、それでも中々どうにも。
- PHPのLINQあるよ
ないよ。幾つかのPHP-LINQライブラリを、コードも読んで評価しましたが、ゴミという結論に達しました。私はJavaScriptにLINQ移植したりとかしているので、LINQにはこだわりがありひじょーにうるさいのです。あとCakePHPに含まれてるコレクション系のメソッドもゴミですね。かわりに自作したLINQっぽいコレクション処理用の何かを使っています。PHP 5.4を使っているので、ラムダとかもがしがし使えはするので、メソッドチェーンでフィルターやグルーピング、複数キーのソートとか一通りできるように作りました。これ作ってなかったら死んでたわ……。ただ、簡易的な実装なので遅延実行ではありません。最近発表されたGinqは非常に良いですね!もっと前にあったら、採用していたかもしれません。
- 「何でもハッシュに詰めるしかない」
ここ説明が足りなくてアレでしたね、申し訳ないです。文脈としてはIntelliSenseが効くか効かないか、の話しかするつもりなかったのと、ハッシュだろうとオブジェクトだろうとDBからの戻り値の場合はどうせ効かないのでどうでもいい、といった感だったので不正確でした、すみません。
あ、DBから以外の部分で、Modelとして作りこむところでは普通にclass立てたり、TypeHinting使ったりPhpDoc書いたりして、補完がなるべく効きやすいように作ってはいますよ。IDE信奉者なので、PhpStormを会社で購入して使っていますので。とはいえ、タイプヒンティングやPHPDocでも、PHP自体がゆるふわなので効きめはイマイチなんですよ。じゃあ、PHP自体がのゆるふわさを捨てて完全にガチガチに書くか?といったら、それはそれでイマイチになるので、まぁ、半分諦めるのがいいかな、とは。
一応誤解なきように弁解すると、無知のC#モノがイヤイヤPHP使ってるだけでPHPについて何も分かっちゃいない、というほどに分かってないわけではないです。一応はPHPの最新言語仕様についてはちゃんと追っかけて差分取っているぐらいには使っています。さすがに仕事の商売道具ですから、嫌いだから何も勉強しない!わけでもないです。traitなども効果的だと思ったところには使ってますし、まぁtrait使うと更にPhpStormの補完が死ぬわけですが。
- なんで水平分割いやがるの
一応スライド中にも書きましたが、メンドーごとが増えるので。特に嫌なのは、HeidiSQLとか、GUIツール郡の使い勝手が低下して、かわりに自社製のツールセット(コマンドか、しょぼいWebUI)になるか、とかですよねえ、それが一番避けたくて。他社の話を聞いていて驚くのは、phpMyAdminとかコマンド叩いてるとかいうんですよ、DB見たりするのに。私的には、ありえない。みんなもっとちゃんとGUIツールも使いましょう。必要とあらばウェブツールだけじゃなくてGUIツールも自作しましょう(C#なら簡単です!)。
まあ、どうしてもダメになったら水平にします。でもFusion-IOはもとより、AWSでも10万IOPSのインスタンスが出たりとか、ハードウェア性能はどんどん進化しているので、何とかなってくれるんじゃないかなあ、と楽観視はしています。良い時代になったな、と思います。
なぜ移行するの?C#のどこがいいの?
ということを、6/11 第90回codeseek&第30回日本C#ユーザー会 勉強会 「やっぱり.NETだよね」でお話しますので、明日ですが、当日まで申し込みは受け付けてる的なノリがいつもの感じだとあるので、時間あるかたは是非お越しください。そんなにDisってよりは割とSoftな感じです、私の分はいちおー。
珍しく既にスライドは完成していまして、「Static vs Dynamic」「Type for IntelliSense」「Type for Refactoring」「Debugger is Power」「LINQ vs array_xxx」「Razor:Template Engine Revolution」といったようなタイトルが並んでますので、気になった方はどーぞ。
ああ、そう、あとA vs Bにおいて、全てにおけてAのほうがいい、なんてことは絶対にないんですね。どこかしらかはBのほうがよかったり、BならXxxなのに、とかいうようなことも出てくるでしょう。何がどれだけ自分に、自分達に良いのかの選択をしてくのが肝要なので、その選択を考えるための道具になればいいかと思ってます。
HttpClient詳解、或いはAsyncOAuthのアップデートについて
- 2013-05-27
すっかり忘れていたわけではないですが、ちょっとかなり前、3/30のRoom metro #15にて、HttpClient詳解という、HttpClientについてのセッションを行いました。
HttpClientは、使えば使うほど、もうWebRequestやWebClientに戻りたくないわー、という非常に秀逸な、完全にこれからのスタンダードになる代物なので、きっちり習得しましょう。
或いは非同期の落とし穴について、ということで、async/awaitでも顕在の、いや、async/awaitだからこそ現れるデッドロックの問題と回避方法についても紹介しています。はまる時ははまっちゃうんですよねー、これ、何気に地味に実は。それなりに痛い目みました、私も。
PCL版のRC
HttpClientは現在.NET 4.5とWindows Store Appsのほうに標準搭載されていますが、それ以外でも使うために、Portable Class Libraryとしての提供がされています。そして、5/22にPortable HttpClient is now available as RCとしてRC版がリリースされました!
こないだまでのBeta版だと、AsyncOAuthを使ってTiwtterのストリーミングAPIを読む時に、awaitすると全く戻ってこなくなるという現象がありました。これはAsyncOAuthが悪いのかHttpClientが悪いのか調べたんですが、結果としてHttpClientのバグでした。HttpClientは内部で通信にWebRequestを使っているのですが、それのAllowReadStreamBufferingとAllowWriteStreamBufferingをfalseにセットしなければならないのに、何もセットしない(ことによって結果的にtrueになっている)状態でした。すると、ストリーミングAPIを読むのにバッファを取ろうとして、当然ストリーミングなのでオワリがないので永遠に帰ってこないという……。
ちゃんとバグ報告したら(偉い!←自分で言う)、今回のRC版で直してくれたようです、多分。とりあえずWP8のEmulatorで試した限りでは、ちゃんとストリーミングAPI動きました。よかったよかった。というわけで、AsyncOAuthもver.0.6.4として、新しいHttpClientに依存するようにアップデートしておきました。なお、AsyncOAuthについてはAsyncOAuth - C#用の全プラットフォーム対応の非同期OAuthライブラリを読んでくださいな。
そういえば同時に、というか4/17にですが、.NET4などでもasync/awaitを使えるようにするMicrosoft.Bcl.AsyncはStableになってました。これで気兼ねなくasync使える!
6月の予定
6月は何故かいっぱいイベントに出ることになっています。6/8のBuild Insider OFFLINE、そこで「.NET最先端技術によるハイパフォーマンスウェブアプリケーション」についてお話します。もう席は満席となってしまいましたが、Ustreamでの中継も行われるようですので、よろしければそちらで見ていただければと思います。
また、6/11~14あたりに、C#ユーザー会で何か話すそうです。何か。何でしょうね。一節によるとPHP被害者友の会(?)だとか・
そして6/22につくばC#勉強会でThe History of LINQと題して、何か話すそうです。はい。つくばいいですね!素晴らしいですぅー。つくば勉強会はまだまだ残席あるようなので、みんな参加しよう!登壇者も募集しているようですので、登壇もしよう!
RxとRedisを用いたリモートPub/Sub通信
- 2013-04-24
今日から始まったBuild Insiderで、RedisとBookSleeveの記事を書きました - C#のRedisライブラリ「BookSleeve」の利用法。Redis、面白いし、Windowsで試すのも想像以上に簡単なので、是非是非試してみて欲しいです。そして何よりもBookSleeve!使うと、強制的に全てがasyncになるので、C# 5.0でのコーディングの仕方のトレーニングになる(笑)。にちじょー的にasync/awaitを使い倒すと、そこから色々アイディアが沸き上がっきます。ただたんにsyncがasyncになった、などというだけじゃなく、アプリケーションの造りが変わります。そういう意味では、Taskは結構過小評価されてるのかもしれないな、なんて最近は思っています。
さて、RedisにはPub/Sub機能がついているわけですが、Pub/Sub→オブザーバーパターン→Rx!これはティンと来た!と、いうわけで、これ、Rxに乗せられました、とても自然に。というわけで、□□を○○と見做すシリーズ、なCloudStructuresにRx対応を載せました。
追加したクラスはRedisSubject<T>。これはSubjectやAsyncSubjectなどと同じく、ISubject<T>になっています。IObservableであり、IObserverでもある代物。とりあえず、コード例を見てください。
var settings = new RedisSettings("127.0.0.1");
var subject = new RedisSubject<string>(settings, "PubSubTest");
// SubscribeはIObservable<T>なのでRxなLINQで書ける
var a = subject
.Select(x => DateTime.Now.Ticks + " " + x)
.Subscribe(x => Console.WriteLine(x));
var b = subject
.Where(x => !x.StartsWith("A"))
.Subscribe(x => Console.WriteLine(x), () => Console.WriteLine("completed!"));
// IObserverなのでOnNext/OnError/OnCompletedでメッセージ配信
subject.OnNext("ABCDEFGHIJKLM");
subject.OnNext("あいうえお");
subject.OnNext("なにぬねの");
Thread.Sleep(200); // 結果表示を待つ...
a.Dispose(); // UnsubscribeはDisposeで
subject.OnCompleted(); // OnCompletedを受信したSubscriberもUnsubscribeされる
はい、別になんてこともない極々フツーのRxのSubjectです。が、しかし、これはネットワークを通って、Redisを通して、全てのSubscriberへとメッセージを配信しています。おお~。いやまあ、コードの見た目からじゃあそういうの分からないので何も感動するところもないのですが、とにかく、本当に極々自然に普通に、しかし、ネットワークを超えます。この、見た目何も変わらずに、というところがいいところなわけです。
Subscribeする側はIObservableなので別にフツーに合成していけますし、Publishする側もIObserverなので、Subscribeにしれっと潜り込ませたりしても構わない。もう、全然、普通にインメモリなRxでやる時と同じことが、できます。
オワリ。地味だ。
う、うーん。ほ、ほらほら!!
// ネットワーク経由
var settings = new RedisSettings("xx.xxx.xxx.xxx");
var subject = new RedisSubject<DateTime>(settings, "PubSubTest");
// publisherはこちらのコード
while (true)
{
Console.ReadLine();
var now = DateTime.Now;
Console.WriteLine(now.Ticks);
subject.OnNext(now);
}
// subscriberはこちらのコードを動かす
subject.Subscribe(x =>
{
var now = DateTime.Now;
Console.Write(x.Ticks + " => " + now.Ticks);
Console.WriteLine(" | " + (now - x));
});
というわけで、実際にネットワーク経由(AWS上に立てたRedisサーバーを通してる)で動かしてみた結果がこんな感じです。ネットワークを超えたことで用法は幾らでもある!夢膨らみまくり!
で、↑のコードは遅延時間のチェックを兼ねてるのですが、概ね、0.03秒ぐらい。たまにひっかかって0.5秒超えてるのがあって、ぐぬぬですが。実際のとこRedis/Linuxの設定で結構変わってくるところがあるので、その辺は煮詰めてくださいといったところでしょうか。
ともあれチャットとかなら全然問題なし、ゲームでもアクションとかタイミングにシビアじゃないものなら余裕ですね、ボードゲームぐらいなら全く問題ない。ちょっとしたMMOぐらいならいけるかも。これからはネットワーク対戦はRedisで、Rxで!!!
余談
CloudStructuresですが、.configからの設定読み込み機能も地味につけました。
<configSections>
<section name="cloudStructures" type="CloudStructures.Redis.CloudStructuresConfigurationSection, CloudStructures" />
</configSections>
<cloudStructures>
<redis>
<group name="cache">
<add host="127.0.0.1" />
<add host="127.0.0.2" port="1000" />
</group>
<group name="session">
<add host="127.0.0.1" db="2" valueConverter="CloudStructures.Redis.ProtoBufRedisValueConverter, CloudStructures" />
</group>
</redis>
</cloudStructures>
// これで設定を読み込める
var groups = CloudStructuresConfigurationSection.GetSection().ToRedisGroups();
色々使いやすくなってきて良い感じじゃあないでしょーか。
コールバック撲滅
そうそう、最後に実装の話を。元々BookSleeveのPub/Sub購読はコールバック形式です。「public Task Subscribe(string key, Action<string, byte[]> handler)」handlerはstringがキー, byte[]が送られてくるオブジェクトを指します。コールバック is ダサい。コールバック is 扱いにくい。ので、コールバックを見かけたらObservableかTaskに変換することを考えましょう!それがC# 5.0世代の常識です!
というわけで、以下のようにして変換しました。
public IDisposable Subscribe(IObserver<T> observer)
{
var channel = Connection.GetOpenSubscriberChannel();
var disposable = System.Reactive.Disposables.Disposable.Create(() =>
{
channel.Unsubscribe(Key).Wait();
});
// ここが元からあるコールバック
channel.Subscribe(Key, (_, xs) =>
{
using (var ms = new MemoryStream(xs))
{
var value = RemotableNotification<T>.ReadFrom(ms, valueConverter);
value.Accept(observer); // この中でobserverのOnNext/OnError/OnCompletedが叩かれる
if (value.Kind == NotificationKind.OnError || value.Kind == NotificationKind.OnCompleted)
{
disposable.Dispose(); // ErrorかCompletedでもUnsubscribeしますん
}
}
}).Wait();
return disposable; // もしDisposableが呼ばれたらUnsubscribeしますん
}
こんなふぅーにRxで包むことで、相当使いやすさがアップします。感動した。
CloudStructures - ローカルとクラウドのデータ構造を透過的に表現するC# + Redisライブラリ
- 2013-04-05
というものを作りました。インストールはNuGetから。
何を言ってるのかヨクワカラナイので、まずはコード例を。
// こんなクラスがあるとして
public class Person
{
public string Name { get; private set; }
public List<Person> Friends { get; private set; }
public Person(string name)
{
Name = name;
Friends = new List<Person>();
}
}
// こんなのがいるとして
var sato = new Person("さとう");
// 人を足す
sato.Friends.Add(new Person("やまだ"));
sato.Friends.Add(new Person("いとう"));
// 件数数える
var friendCount = sato.Friends.Count;
これは普通にローカルで表現する場合です。実に普通です。では、次。
// RedisServerの設定の表現
public static class RedisServer
{
public static readonly RedisSettings Default = new RedisSettings("127.0.0.1");
}
// こんなクラスがあるとして
public class Person
{
public string Name { get; private set; }
public RedisList<Person> Friends { get; private set; }
public Person(string name)
{
Name = name;
Friends = new RedisList<Person>(RedisServer.Default, "Person-" + Name);
}
}
// こんなのがいるとして
var sato = new Person("さとう");
// 人を足す
await sato.Friends.AddLast(new Person("やまだ"));
await sato.Friends.AddLast(new Person("いとう"));
// 件数数える
var friendCount = await sato.Friends.GetLength();
この場合、Redisを通してサーバー上にデータは保存されています。ですが、操作感覚はローカルにあるものとほぼほぼ同じです。違いは全ての操作が非同期なので、awaitするぐらい。
IAsyncList
これは、Actor Framework for Windows AzureのDistributed Collectionsに影響を受けています。ActorFxのそれは、SOURCE CODEを落としてdocsフォルダの Distributed Collections using the ActorFx.docx に色々書いてあって面白いので必読です。
そして、ActorFxではSystem.Cloud.Collectionsとして(System名前空間!)、現状、以下のようなインターフェイスが定義されています(まだ変更の可能性大いにあり)。
namespace System.Cloud.Collections
{
public interface IAsyncCollection<T> : IObservable<T>
{
Task<int> CountAsync { get; }
Task<bool> IsReadOnlyAsync { get; }
Task AddAsync(T item);
Task ClearAsync();
Task<bool> ContainsAsync(T item);
Task CopyToAsync(T[] array, int arrayIndex);
Task<bool> RemoveAsync(T item);
}
public interface IAsyncList<T> : IAsyncCollection<T>
{
Task<T> GetItemAsync(int index);
Task SetItemAsync(int index, T value);
Task<int> IndexOfAsync(T item);
Task InsertAsync(int index, T item);
Task RemoveAtAsync(int index);
// Less chatty versions
Task AddAsync(IEnumerable<T> items);
Task RemoveRangeAsync(int index, int count);
}
public interface IAsyncDictionary<TKey, TValue> : IAsyncCollection<KeyValuePair<TKey, TValue>>
{
Task<TValue> GetValueAsync(TKey key);
Task SetValueAsync(TKey key, TValue value);
Task<Tuple<bool, TValue>> TryGetValueAsync(TKey key);
// No AddAsync - use SetValueAsync instead. We have no atomic operation to add iff a value is not in the dictionary.
Task<bool> ContainsKeyAsync(TKey key);
Task<bool> RemoveAsync(TKey key);
// Bulk operations
Task<ICollection<TValue>> GetValuesAsync(IEnumerable<TKey> keys);
Task SetValuesAsync(IEnumerable<TKey> keys, IEnumerable<TValue> values);
Task RemoveAsync(IEnumerable<TKey> keys);
ICollection<TKey> Keys { get; }
ICollection<TValue> Values { get; }
}
}
わくわくしてきません?私はこの定義を見た瞬間に衝撃を受けました。RxのIObservable<T>を見た時と同程度の衝撃かもわからない。Ax(ActorFx)の実装としてはCloudList, CloudDictionary, CloudStringDictionaryがありますが(基盤としてAzure Table)、見てすぐにRedisと結びついた。Redisの持つデータ構造、List, Hash, Set, SortedSetってこれじゃないか!って。こういう風に表現されたらどれだけ素敵な見た目になるか……!
Strings, Set, SortedSet, List, Hash, その他
というわけで、最初の例ではRedisListだけ出しましたが、StringsもSetもSortedSetもHashもあります。また、HashClassやMemoizedRedisStringといった特殊なものも幾つか用意してあります。
// フィールドに持たなくても、ふつーにRedisClient的に使ってもいいよ
var client = new RedisString<string>(RedisServer.Default, "toaru-key");
await client.Set("あいうえお!", expirySeconds: TimeSpan.FromMinutes(60).TotalSeconds);
// RedisClassはRedisのHash構造をクラスにマッピングするもの
var hito = new RedisClass<Hito>(RedisServer.Default, "hito-1");
await hito.SetField("Name", "やまもと");
await hito.Increment("Money", 100);
var localHito = await hito.GetValue(); // Cloud -> Localに落とす、的ないめーぢ
実際色々あるので見て回ってください!
ConnectionManagement
基盤的な機能として、BookSleeveの接続管理を兼ねています。
// Redisの設定を表す
var settings = new RedisSettings(host: "127.0.0.1", port: 6379, db: 0);
// BookSleeveはスレッドセーフで単一のコネクションを扱う
// コネクションを一つに保ったり切断されていた場合の再接続などをしてくれる
var conn = settings.GetConnection();
// 複数接続はRedisGroupで管理できる
var group = new RedisGroup(groupName: "Cache", settings: new[]
{
new RedisSettings(host: "100.0.0.1", port: 6379, db: 0),
new RedisSettings(host: "105.0.0.1", port: 6379, db: 0),
});
// keyを元に分散先のサーバーを決める(デフォルトはMD5をサーバー台数で割って決めるだけの単純な分散)
var conn = group.GetSettings("hogehoge-100").GetConnection();
// シリアライザはデフォルトではJSONとProtoBufを用意(未指定の場合はJSON)
new RedisSettings("127.0.0.1", converter: new JsonRedisValueConverter());
new RedisSettings("127.0.0.1", converter: new ProtoBufRedisValueConverter());
って、ここまでBookSleeveの説明がなかった!BookSleeveはRedisのライブラリで、非同期の操作のみを提供しています。CloudStructuresのRedis操作はBookSleeveに全部委ねてます。というかぶっちゃけ、かなり単純なラップがほとんどだったりします(!)。見せ方を変えただけ、です、よーするところ。
んで、BookSleeveは斬新で非常に良いライブラリなのですけれど、操作が本当にプリミティブなものしかないので(全てのGetとSetがstringとbyte[]しかない、とかね)、ある程度、自分で作りこんでやらないと全く使えません。なので、この部分だけでも、結構使えるかなって思います。
Next
個人的にはすっごく面白いと思ってます。見せ方の違いでしかないわけですが、しかし、その見せ方の違いというのが非常に大事なのです。直感的、ですが、ある種奇抜なデザインなので、戸惑うとは思います。異色度合いで言ったら、以前に私の作ったReactivePropertyと同程度に異色かな、と。だからこそ、凄く大きな可能性を感じませんか?
ちなみに、これは(いつものように)コンセプト止まりじゃなくて、実際に使う予定アリなので、しっかり育ててく気満々です。是非、試してみてもらえると嬉しいですね。
Microsoft MVP for Visual C#を再再受賞しました
- 2013-04-02
今年も再受賞することができました。去年は某g社のフロントに立って、というのもあって、オフライン活動が割と非常に活発でした。ブログは量は減りましたし、中身も職業柄ウェブ系の要素が濃くなった感。とはいえ、内容自体は結構面白げなC#の内容になってたのではかしらん。基本的には、他で得られない情報や、ディープに踏み込んだ先の話、新しい考え。そういったのを提供し続けたいです。
今年はもっともっと面白くしたい。去年に、私は
C#を世間(といっても、技術系の人に、ですね)に強くアピールするにはどうすればいいのか、といったら、一番大事なのはそれで書かれたメジャーなアプリケーションなのです。PerlではてなやMixi、livedoorなどを思い浮かべる、RubyでCookpadを、ScalaでFoursquareやTwitterを、そういった憧れにも似た気持ちを浮かべさせるようなアプリケーションがなければいけなくて。
Stack OverflowはC#, ASP.NET MVCだし、TIOBEのプログラミング言語ランキングでは三位など、海外でのC#の地位は十分に高い。のですが、国内ではそれと比べれば全くもってない。日本で誰もが知る会社の誰もが知るアプリケーション、それがC#で書かれている。そういう状態にならなければ、日本で強く普及は無理だな、と。
と言いました。今年はそれを実現するため、謎社(もう謎ではない)のCTOとして、人の憧れるような、日本を代表するC#の企業にします。C#による圧倒的な成果、C#だからこその強さ、というのを現実に示していく。幸い、私は今、それができる場所にいると思っています。
そんなわけで、今年もよろしくお願いします。
C#のラムダ式でyieldっぽい何かをawaitで代用する方法
- 2013-03-27
C#がインラインでyield書けないならawait使えばいいじゃない。と、偉い人は言いました。というわけで、こそこそっと開発がされているIxに、面白い機能が入りました(開発リポジトリ上だけなのでNuGetからダウンロードしても、まだ入ってません)。こんなのです。
var hoge = "あいうえお";
var seq = EnumerableEx.Create<int>(async Yield =>
{
await Yield.Return(10);
await Yield.Return(100);
hoge = "ふがふが"; // インラインで書けるのでお外への副作用が可能
await Yield.Return(1000);
});
foreach (var item in seq)
{
Console.WriteLine(item); // 10, 100, 1000
}
Console.WriteLine(hoge); // ふがふが
そう、yield return(っぽい何か)がラムダ式で、メソッド外部に出すことなく書けてしまうのです!これは素敵ですね?い、いや、なんか何やってるのか分からなすぎて黒魔術怖いって雰囲気も漂ってますね!しかし面白いものは面白いので、実装見ましょう。
add types iyielder, iawaitable, and iawait; add support for creating ienumerable from action
public static class EnumerableEx
{
public static IEnumerable<T> Create<T>(Action<IYielder<T>> create)
{
if (create == null) throw new ArgumentNullException("create");
foreach (var x in new Yielder<T>(create))
{
yield return x;
}
}
}
public interface IYielder<in T>
{
IAwaitable Return(T value);
IAwaitable Break();
}
public interface IAwaitable
{
IAwaiter GetAwaiter();
}
public interface IAwaiter : ICriticalNotifyCompletion
{
bool IsCompleted { get; }
void GetResult();
}
public class Yielder<T> : IYielder<T>, IAwaitable, IAwaiter, ICriticalNotifyCompletion
{
private readonly Action<Yielder<T>> _create;
private bool _running;
private bool _hasValue;
private T _value;
private bool _stopped;
private Action _continuation;
public Yielder(Action<Yielder<T>> create)
{
_create = create;
}
public IAwaitable Return(T value)
{
_hasValue = true;
_value = value;
return this;
}
public IAwaitable Break()
{
_stopped = true;
return this;
}
public Yielder<T> GetEnumerator()
{
return this;
}
public bool MoveNext()
{
if (!_running)
{
_running = true;
_create(this);
}
else
{
_hasValue = false;
_continuation();
}
return !_stopped && _hasValue;
}
public T Current
{
get
{
return _value;
}
}
public IAwaiter GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get { return false; }
}
public void GetResult() { }
public void OnCompleted(Action continuation)
{
_continuation = continuation;
}
public void UnsafeOnCompleted(Action continuation)
{
_continuation = continuation;
}
}
ほぅ、わけわからん?若干トリッキーなので、順を追っていきますか。asyncについて考える前に、まず、基本的なforeachのルール。実はIEnumerableを実装している必要はなくて、GetEnumeratorという名前のメソッドがあればいい。同様にMoveNextとCurrentというメソッドがあればIEnumerator扱いされる。なので、foreach (var x in new Yielder
あと、インターフェイスが、IAwaitableとかいっぱい再定義されてて、ワケワカランのですけれど、そこまで意味あるわけじゃないです。これはラムダ式にYielderを渡すわけですが、そこで内部の諸々が呼べちゃうのはイクナイので隠ぺいする、程度の意味合いでしかないので、これを実装するのにインターフェイスの再定義が必要!というわけは全然ないです。
で、コアになるのはMoveNext。
public bool MoveNext()
{
if (!_running)
{
_running = true;
_create(this);
}
else
{
_hasValue = false;
_continuation();
}
return !_stopped && _hasValue;
}
そもそもyield returnで生成されたメソッドが最初に実行されるのは、GetEnumeratorのタイミングではなく、GetEnumeratorされて最初のMoveNextが走った時、なので、ここが本体になっているのはセマンティクス的に問題なし。
!_runnningは初回実行時の意味で、ここで_create(this)、によってラムダ式で書いた本体が走ります。
var seq = EnumerableEx.Create<int>(async Yield =>
{
await Yield.Return(10);
// ↑のとこがまず実行され始める
await Yield.Return(100);
await Yield.Return(1000);
});
public IAwaitable Return(T value)
{
_hasValue = true;
_value = value;
return this;
}
まずはメソッド実行なのでReturn。これは値をセットして回っているだけ。そしてIAwaitableを返し、await。ここで流れは別のところに行きます。
public bool IsCompleted
{
get { return false; }
}
public void GetResult() { }
public void OnCompleted(Action continuation)
{
_continuation = continuation;
}
public void UnsafeOnCompleted(Action continuation)
{
_continuation = continuation;
}
まず完了しているかどうかの確認(IsCompleted)が走りますが、この場合は常にfalseで(そうしないと終了ということになってラムダ式のほうに戻ってこなくなっちゃう)。これによってUnsafeOnCompleted(ICriticalNotifyCompletionが実装されている場合はこっちが走る)でcontinuation(メソッド本体)が走る。で、「次回用」に変数保存して、MoveNext(create(this)したとこの位置)に戻ってくる。あとはMoveNextがtrueを返すのでCurrentで値取得して、それがyield returnされる。
二度目のMoveNextでは
public bool MoveNext()
{
if (!_running)
{
_running = true;
_create(this);
}
else
{
_hasValue = false;
_continuation(); // ここが呼び出されて
}
return !_stopped && _hasValue;
}
var seq = EnumerableEx.Create<int>(async Yield =>
{
await Yield.Return(10);
// ここから再度走り出す
await Yield.Return(100);
await Yield.Return(1000);
});
といった感じになって、以下繰り返し。良く出来てますね!ていうか、asyncなのに非同期全く関係ないのが素敵。そう、asyncは別に非同期関係なく使えちゃうわけです。ここ大事なので繰り返しましょう。asyncは別に非同期関係なく使うことができます。
まとめ
async、フツーに使うのもそろそろ飽きてき頃だと思うので、弄って遊ぶのは大正義。実際に投下しだすかどうかは判断次第。あと、↑のはまだ大事な要素ができていないので絶対使いませんけれど。大事な要素はIDisposableであること。foreachで大事だと思ってるのはDisposeしてくれるとこ!だとも思っているので、それが実現できてないのはナイナー、と。
そういえばAsyncについてですが、3/30の土曜にRoom metro #15でHttpClient(非同期の塊!)について話すので、まだ残席ありますので良ければお越しくだしあー。
並列実行とSqlConnection
- 2013-03-09
どうも、ParallelやThreadな処理が苦痛度100なペチパーです。嘘です。空前のThreadLocalブームが来てたり来てなかったりする昨今です。あ、謎社の宣伝しますとグリーとグラニ、「GREE」におけるソーシャルゲームの提供などについて戦略的業務提携に合意というわけで、ぐりとぐら、としかいいようがない昨今でもあります。その日に開催されていたGREEプラットフォームカンファレンスでは、謎社はC#企業になる!と大宣言したので、ちゃんと実現させていきたいところです、いや、むしろそのためにフル回転しています。
そんな宣伝はおいておいて本題なのですけれど、SQL。データベース。大量にクエリ発行したい時など、パラレル実行したいの!インサートだったら当然BulkInsertが一番早いんですが、Updateとかね。シンドイんだよね。あとUpsert(Merge/ON DUPLICATE KEY UPDATE)とかも使っちゃったりしてね。そんなわけで、お手軽お気楽な手法としてはParallelがありますねー、.NETはこの辺本当に楽なんだよねー、ぴーHPはシラネ。
で、実際パラレールにこんな感じに書くと……
using (var connection = new SqlConnection("接続文字列"))
{
connection.Open();
Parallel.For(1, 1000, x =>
{
var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper
});
}
落ちます。理由は単純明快でSqlConnectionはスレッドセーフじゃないから。というわけで、やるなら
Parallel.For(1, 1000, x =>
{
using (var connection = new SqlConnection("接続文字列"))
{
connection.Open();
var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper
}
});
となります、これなら絶対安全。でも、スレッドって基本的にコアの数とちょびっとしか立てられないわけだし、連続的に実行しているのだから、たとえコネクションプール行きだとかなんだりであっても、一々コネクションを開いて閉じてをするよりも、開きっぱで行きたいよね。
ようするにSqlConnectionがスレッドセーフじゃないからいけない。これはどこかで聞いたような話です。先日C#とランダムで出したThreadLocalの出番ではないでしょうか!
ThreadLocal
というわけでスレッドセーフなSqlConnectionを作りましょう、ThreadLocalを使って。
using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("接続文字列"); conn.Open(); return conn; }))
{
Parallel.For(1, 1000, x =>
{
var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper
});
}
new SqlConnectionがThreadLocalに変わっただけのお手軽さ。これで、安全なんですって!本当に?本当に。で、実際こうして速度はどうなのかというと、私の環境で実行したところ、シングルスレッドで16秒、毎回new SqlConnectionするParallelで5秒、ThreadLocalなParallelで2秒でした。これは圧勝。幸せになれそう。
Disposeを忘れない、或いは忘れた
でも↑のコードはダメです。ダメな理由は、コネクションをDisposeしてないからです。ThreadLocalのDisposeは、あくまでThreadLocalのDisposeなのであって、中身のDisposeはしてくれてないのです。ここ忘れると悲劇が待ってます。でもFactoryで作ってる上にThreadで一意なValue、どうやってまとめてDisposeすればいいの!というと、trackAllValuesというオプションを有効にすると簡単に実現できます。
using (var connection = new ThreadLocal<SqlConnection>(() => { var conn = new SqlConnection("接続文字列"); conn.Open(); return conn; }
, trackAllValues: true)) // ThreadLocalの.Valuesプロパティの参照を有効化する
{
Parallel.For(1, 1000, x =>
{
var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper
});
// 生成された全てのConnectionを一括Dispose
foreach (var item in connection.Values.OfType<IDisposable>()) item.Dispose();
}
このtrackAllValuesが可能なThreadLocalは.NET 4.5からです。それ以前の人は、残念でした……。謎社は遠慮なく.NET 4.5を使いますので全然問題ありません(
もう一つまとめて
とはいえ、なんか面倒くさいので、ちょっとラップしませう、以下のようなクラスを用意します。
public static class DisposableThreadLocal
{
public static DisposableThreadLocal<T> Create<T>(Func<T> valueFactory)
where T : IDisposable
{
return new DisposableThreadLocal<T>(valueFactory);
}
}
public class DisposableThreadLocal<T> : ThreadLocal<T>
where T : IDisposable
{
public DisposableThreadLocal(Func<T> valueFactory)
: base(valueFactory, trackAllValues: true)
{
}
protected override void Dispose(bool disposing)
{
var exceptions = new List<Exception>();
foreach (var item in this.Values.OfType<IDisposable>())
{
try
{
item.Dispose();
}
catch (Exception e)
{
exceptions.Add(e);
}
}
base.Dispose(disposing);
if (exceptions.Any()) throw new AggregateException(exceptions);
}
}
これを使うと
using (var connection = DisposableThreadLocal.Create(() => { var conn = new SqlConnection("接続文字列"); conn.Open(); return conn; }))
{
Parallel.For(1, 1000, x =>
{
var _ = connection.Value.Query<DateTime>("select current_timestamp").First(); // Dapper
});
}
といったように、超シンプルに書けます。うん。いいね。
それAsync?
Asyncでドバッと発行してTask.WhenAll的なやり方も、接続が非スレッドセーフなのは変わらなくて、結構やりづらいんですよ……。それで、なんか色々細かくawaitしまくりで逆に遅くなったら意味ないし。それならドストレートに行ったほうがいいのでは感が若干ある。どうせThreadなんてそこそこ余ってるんだから(←そうか?)局所的にParallelってもいいぢゃないと思いたい、とかなんとかかんとか。
非.NET 4.5の場合
Parallel.For, ForEachに関しては、localInit, localFinallyというタスク内で一意になる変数を利用したオーバーロードを利用して、似たような雰囲気で書けます。正確には同じ挙動ではないですが、まぁまぁ悪くない結果が得られます。
Parallel.For(1, 1000,
() =>
{
// local init
var conn = new SqlConnection("接続文字列");
conn.Open();
return conn;
},
(x, state, connection) =>
{
var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper
return connection;
},
(connection) =>
{
// local finally
connection.Dispose();
});
オーバーロードが結構地獄でシンドイですね!ここも簡単にラップしたものを作りましょう。
public static class ParallelEx
{
public static ParallelLoopResult DisposableFor<TDisposable>(long fromInclusive, long toExclusive, Func<TDisposable> resourceFactory, Action<long, ParallelLoopState, TDisposable> body)
where TDisposable : IDisposable
{
return Parallel.For(fromInclusive, toExclusive, resourceFactory, (item, state, resource) => { body(item, state, resource); return resource; }, disp => disp.Dispose());
}
public static ParallelLoopResult DisposableForEach<T, TDisposable>(IEnumerable<T> source, Func<TDisposable> resourceFactory, Action<T, ParallelLoopState, TDisposable> body)
where TDisposable : IDisposable
{
return Parallel.ForEach(source, resourceFactory, (item, state, resource) => { body(item, state, resource); return resource; }, disp => disp.Dispose());
}
}
こうしたものを作れば、
ParallelEx.DisposableFor(1, 1000,
() =>
{
var conn = new var conn = new SqlConnection("接続文字列");
conn.Open();
return conn;
},
(x, state, connection) =>
{
var _ = connection.Query<DateTime>("select current_timestamp").First(); // Dapper
});
まぁまぁ許せる、かな?
C#とランダム
- 2013-03-06
古くて新しいわけはない昔ながらのSystem.Randomのお話。Randomのコンストラクタは二種類あって、seed引数アリの場合は必ず同じ順序で数値を返すようになります。
// 何度実行しても同じ結果
var rand = new Random(0);
Console.WriteLine(rand.Next()); // 1559595546
Console.WriteLine(rand.Next()); // 1755192844
Console.WriteLine(rand.Next()); // 1649316166
例えばゲームのリプレイなどは、ランダムだけど同一の結果が得られることを期待したいわけなので、大事大事ですね。(とはいえ、Windows-CLIとLinux-monoでは結果が違ったりするので、マルチプラットフォームでの共有などという場合は、別策を取ったほうがよさそうです)。何も渡さない場合はseedとしてEnvironment.TickCountが渡されます。精度はミリ秒。ということは、ですね、例えばループの中でRandomをnewするとですよ、
for (int i = 0; i < 100; i++)
{
var rand = new Random();
Console.WriteLine(rand.Next());
}
マシンスペックにもよりますが、私の環境では30個ぐらい同じ数値が出た後に、別の、また30個ぐらい同じ数値が続き……となりました。何故か、というと、seedがEnvironment.TickCountだからで、ループ内といったようなミリ秒を超える超高速の状態で生成されている時は、seed値が同じとなってしまうから。なので、正しくは
var rand = new Random();
for (int i = 0; i < 100; i++)
{
Console.WriteLine(rand.Next());
}
といったように、ループの外に出す必要性があります。
ランダムなランダム
では、ランダムなランダムが欲しい場合は。例えばマルチスレッド。そうでなくても、例えばループの外に出す(直接的でなくてもメソッドの中身がそうなっていて、意図せず使われてしまう可能性がある)のを忘れてしまうのを強制的に避ける場合。もしくは、別にマルチスレッドは気を付けるよー、といっても、ASP.NETとか複数リクエストが同時に走るわけで、同タイミングでのRandom生成になってしまう可能性は十分にある。そういう時は、RandomNumberGeneratorを使います。
using (var rng = new RNGCryptoServiceProvider())
{
// 厳密にランダムなInt32を作る
var buffer = new byte[sizeof(int)];
rng.GetBytes(buffer);
var seed = BitConverter.ToInt32(buffer, 0);
// そのseedを基にRandomを作る
var rand = new Random(seed);
}
これでマルチスレッドでも安全安心だ!勿論、RNGCryptoServiceProviderはちょっとコスト高。でも、全然我慢できる範囲ではある。お終い。
ThreadLocal
でも、これって別にスレッドセーフなランダムが欲しいってだけなわけだよね、それなのにちょっとした、とはいえ、コスト高を背負うのって馬鹿げてない?そこで出てくるのがThreadLocal<T>、.NET 4.0以降ですが、スレッド単位で一意な変数を宣言できます。それを使った、Jon Skeet氏(ゆーめーじん)の実装は
public static class RandomProvider
{
private static int seed = Environment.TickCount;
private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
new Random(Interlocked.Increment(ref seed))
);
public static Random GetThreadRandom()
{
return randomWrapper.Value;
}
}
なるほどねー!これなら軽量だし、とってもセーフで安心できるしイイね!もし複数スレッドで同時タイミングで初期化が走った時のために、Interlocked.Incrementで、必ず違う値がseedになるようになってるので、これなら色々大丈夫。
マルチスレッド→マルチサーバー
けれど、大丈夫なのは、一台のコンピューターで完結する時だけの時の話。クラウドでしょ!サーバー山盛りでしょ!な時代では、サーバーをまたいで同時タイミングなEnvironment.TickCountで初期化されてしまう可能性が微レ存。というわけで、Environment.TickCountに頼るのは完全に安全ではない。じゃあ、そう、合わせ技で行けばいいじゃない、seedは完全ランダムで行きましょう。
public static class RandomProvider
{
private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
{
using (var rng = new RNGCryptoServiceProvider())
{
var buffer = new byte[sizeof(int)];
rng.GetBytes(buffer);
var seed = BitConverter.ToInt32(buffer, 0);
return new Random(seed);
}
});
public static Random GetThreadRandom()
{
return randomWrapper.Value;
}
}
これで、軽量かつ安全安泰なRandomが手に入りました。めでたしめでたし。
AsyncOAuth - C#用の全プラットフォーム対応の非同期OAuthライブラリ
- 2013-02-27
待ち望まれていたHttpClientがPortable Class Library化しました、まだBetaだけどね!というわけで、早速PCL版のHttpClientをベースにしたOAuthライブラリを仕上げてみました。ポータブルクラスライブラリなので、.NET 4.5は勿論、Windows Phone 7.5, 8, Windows Store Apps, Silverlight, それと.NET 4.0にも対応です。
前身のReactiveOAuthがTwitterでしかロクにテストしてなくてHatenaでズタボロだったことを反省し、今回はSampleにTwitterとHatenaを入れておきました&どっちでもちゃんと正常に動きます。なお、完全に上位互換なので、ReactiveOAuthはObsoleteです。それと、ライブラリのインストールはNuGet経由でのみの提供です。
PM> Install-Package AsyncOAuth -Pre
もしくはPreReleaseを表示に含めてGUIから検索してください。
AsyncOAuth is not a new library
AsyncOAuthの実態はOAuthMessageHandlerというDelegatingHandlerです。
var client = new HttpClient(new OAuthMessageHandler("consumerKey", "consumerSecret", new AccessToken("accessToken", "accessTokenSecret")));
// 上のだとnewの入れ子が面倒なので短縮形、戻り値は上のと同じ
var client = OAuthUtility.CreateOAuthClient("consumerKey", "consumerSecret", new AccessToken("accessToken", "accessTokenSecret"));
こうなっていると何がいいか、というと、全ての操作がHttpClient標準通りなのです。
// Get
var json = await client.GetStringAsync("http://api.twitter.com/1.1/statuses/home_timeline.json?count=" + count + "&page=" + page);
// Post
var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("status", status) });
var response = await client.PostAsync("http://api.twitter.com/1.1/statuses/update.json", content);
var json = await response.Content.ReadAsStringAsync();
// Multi Post
var content = new MultipartFormDataContent();
content.Add(new StringContent(status), "\"status\"");
content.Add(new ByteArrayContent(media), "media[]", "\"" + fileName + "\"");
var response = await client.PostAsync("https://upload.twitter.com/1/statuses/update_with_media.json", content);
var json = await response.Content.ReadAsStringAsync();
もうおれおれクライアントのAPIを覚える必要はありません。これからの標準クライアントであるHttpClientの操作だけを覚えればいいのです。
コンセプトはHttpClientチームから掲示されているサンプルコードExtending HttpClient with OAuth to Access Twitterどおりですが、このサンプルコードは本当にただのコンセプトレベルなサンプルで、そのまんまじゃ使えないので、ちゃんと実用的なOAuthライブラリとして叩き直したのがAsyncOAuthになります。DelegatingHandlerというのは、リクエストを投げる直前をフックするものなので、そこでOAuth用の認証を作っているわけです。
イニシャライズ
使う場合は、必ず最初にHMAC-SHA1の計算関数をセットしなければなりません。何故か、というと、ポータブルクラスライブラリには現状、暗号系のライブラリが含まれていなくて、その部分は含むことができないからです。外部から差し込んでもらうことでしか対処できない、という。ご不便おかけしますが、的な何か。そのうち含まれてくれるといいなあ、って感じですねえ。それまでは、以下のコードをApp.xaml.csとかApplication_Startとか、初回の本当に最初の最初に呼ばれるところに、コピペってください。
// WinRT以外(Silverlight, Windows Phone, Consoleなどなど)
OAuthUtility.ComputeHash = (key, buffer) => { using (var hmac = new HMACSHA1(key)) { return hmac.ComputeHash(buffer); } };
// Windows Store App(めんどうくせえええええ)
AsyncOAuth.OAuthUtility.ComputeHash = (key, buffer) =>
{
var crypt = Windows.Security.Cryptography.Core.MacAlgorithmProvider.OpenAlgorithm("HMAC_SHA1");
var keyBuffer = Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(key);
var cryptKey = crypt.CreateKey(keyBuffer);
var dataBuffer = Windows.Security.Cryptography.CryptographicBuffer.CreateFromByteArray(buffer);
var signBuffer = Windows.Security.Cryptography.Core.CryptographicEngine.Sign(cryptKey, dataBuffer);
byte[] value;
Windows.Security.Cryptography.CryptographicBuffer.CopyToByteArray(signBuffer, out value);
return value;
};
また、使いかたの詳しいサンプルは、GitHub上のソースコードからAsyncOAuth.ConsoleAppの中にTwitter.csとHatena.csがあるので、それを見てもらえればと思います。AccessToken取得までの、認証系の説明はここには書きませんが(OAuthAuthorizerという特別に用意してあるものを使う)、その具体的な書き方が乗っています。特にHatenaの認証はTwitterに比べるとかなりメンドーくさいので、メンドーくさい系のOAuthが対象の場合は参考になるかと思います。
ストリーミング、Single vs Multiple、或いはRxの再来
勿論、TwitterのストリーミングAPIにも対応できます。以下のようなコードを書けばOK。
public async Task GetStream(Action<string> fetchAction)
{
var client = OAuthUtility.CreateOAuthClient(consumerKey, consumerSecret, accessToken);
client.Timeout = System.Threading.Timeout.InfiniteTimeSpan; // ストリーミングなのでTimeoutで切られないよう設定しておくこと
using (var stream = await client.GetStreamAsync("https://userstream.twitter.com/1.1/user.json"))
using (var sr = new StreamReader(stream))
{
while (!sr.EndOfStream)
{
var s = await sr.ReadLineAsync();
fetchAction(s);
}
}
}
ほぅ、Actionですか、コールバックですか……。ダサい。使い勝手悪い。最悪。しかし、じゃあ何返せばいいんだよ!ということになる。Taskは一つしか返せない、でもストリーミングは複数。うーん、うーん、と、そこでIObservable<T>の出番です。Reactive Extensionsを参照して、以下のように書き換えましょう。
public IObservable<string> GetStream()
{
return Observable.Create<string>(async (observer, ct) =>
{
try
{
var client = OAuthUtility.CreateOAuthClient(consumerKey, consumerSecret, accessToken);
client.Timeout = System.Threading.Timeout.InfiniteTimeSpan; // ストリーミングなのでTimeoutで切られないよう設定しておくこと
using (var stream = await client.GetStreamAsync("https://userstream.twitter.com/1.1/user.json"))
using (var sr = new StreamReader(stream))
{
while (!sr.EndOfStream && !ct.IsCancellationRequested)
{
var s = await sr.ReadLineAsync();
observer.OnNext(s);
}
}
}
catch (Exception ex)
{
observer.OnError(ex);
return;
}
if (!ct.IsCancellationRequested)
{
observer.OnCompleted();
}
});
}
var client = new TwitterClient(consumerKey, consumerSecret, new AccessToken(accessTokenKey, accessTokenSecret));
// subscribe async stream
var cancel = client.GetStream()
.Skip(1)
.Subscribe(x => Console.WriteLine(x));
Console.ReadLine();
cancel.Dispose(); // キャンセルはDisposeで行う
といったように、自然にRxと繋げられます。コールバックのObservable化はObservable.Createで、そんなに難しくはない(ただしOnNext以外にちゃんとOnError, OnCompletedも記述してあげること)です。キャンセル対応に関しては、ちゃんとCancelleationToken付きのオーバーロードで行いましょう。そうしないと、Subscribeの解除はされていても、内部ではループが延々と動いている、といったような状態になってしまいますので。
ともあれ、asyncやCancellationTokenとRxがスムースに結合されていることは良くわかるかと思います。完璧!
こういった、単発の非同期はTaskで、複数の非同期はIObservable<T>で行う、というガイドはTPLチームからも示されています。先日のpfxteamからのスライドから引用すると(ちなみにこのスライドはTask系の落とし穴などが超丁寧に書かれているので必読!)
といった感じです。んねー。
まとめ
ReactiveOAuthはオワコン。HttpClient始まってる。Reactive Extensions自体は終わってない、むしろ始まってる。というわけで、色々と使いこなしていきましょう。
追記:リリースから一晩開けて、POST周りを中心にバグが発見されていてお恥ずかしい限りです。あらかた修正したとは思うのですが(NuGetのバージョンは随時上げています)、怪しい挙動見つけたら報告下さると嬉しいです。勿論、GitHubなのでPull Requestでも!
C#でぬるぽを回避するどうでもいい方法
- 2013-02-19
どうもペチパーです。嘘です逃げないで。まあ、どうでもいいPHPの例をまずは出しませう。あ、逃げないで、PHPの話はすぐやめるんで。
// ネストしてる配列
$hoge["huga"]["hage"]["tako"] = "なのなの";
// なのなの
$v = isset($hoge["huga"]["hage"]["tako"])
? $hoge["huga"]["hage"]["tako"]
: "ない";
// 途中で欠けてる配列
$hoge["huga"] = "なのなの";
// ない
$v = isset($hoge["huga"]["hage"]["tako"])
? $hoge["huga"]["hage"]["tako"]
: "ない";
全体的にキモいんですが、まあ無視してもらって、何が言いたいか、と言うとisset。これはネストしてる部分も一気に評価してくれるのです。フツーの関数だと常識的に考えて評価は先に内側で行うので配列の境界外で死ぬんですが、issetは関数みたいな見た目だけど実は言語構文なのだ!キモチワルイ。ともあれ、そんなわけでネストしてるものの有無を一気にチェックできるのです。
で、PHPのこと書いてるとサイト違うので、C#の話をしませう。
C#でネストネスト
PHPは何でも連想配列なのですが、C#だったらクラスのプロパティでしょうか。以下のようなシチュエーション。
// こういうドカドカした構造があるとして
class Hoge
{
public Huga Prop1 { get; set; }
}
class Huga
{
public Hage Prop2 { get; set; }
}
class Hage
{
public string Prop3 { get; set; }
}
// こっちプログラム本体
var hoge = new Hoge();
// とちゅーでヌルぽが発生すると死んじゃうんの回避が醜悪!
var prop3 = (hoge != null && hoge.Prop1 != null && hoge.Prop1.Prop2 != null && hoge.Prop1.Prop2.Prop3 != null)
? hoge.Prop1.Prop2.Prop3
: null;
!=nullの連鎖が面倒くさいですぅー。なんとかしてくださいぃー。ぴーHPに負けてるんじゃないですかぁー?とか言われてないですが言われてるってことにするので、しょうがないからエレガントな解決策を探してあげました、誰にも頼まれてませんが!
Love ExpressionTree
こーいう風に書ければいいんでしょ!下のhoge.GetValueOrDefaultってとこです。
// こんなHogeがあるとして
var hoge = new Hoge();
// すっきり!
var value = hoge.GetValueOrDefault(x => x.Prop1.Prop2.Prop3);
Console.WriteLine(value == null); // true
// 中身が詰まってたら
hoge = new Hoge { Prop1 = new Huga { Prop2 = new Hage { Prop3 = "ほげ!" } } };
var value2 = hoge.GetValueOrDefault(x => x.Prop1.Prop2.Prop3);
Console.WriteLine(value2); // ほげ!
すっごくスッキリしますね!イイね!
で、どーやってるかというと、ExpressionTreeでグルグルですよ。
public static class MonyaMonyaExtensions
{
public static TR GetValueOrDefault<T, TR>(this T value, Expression<Func<T, TR>> memberSelector)
where T : class
{
var expression = memberSelector.Body;
var memberNames = new List<string>();
while (!(expression is ParameterExpression))
{
if ((expression is UnaryExpression) && (expression.NodeType == ExpressionType.Convert))
{
expression = ((UnaryExpression)expression).Operand;
continue;
}
var memberExpression = (MemberExpression)expression;
memberNames.Add(memberExpression.Member.Name);
expression = memberExpression.Expression;
}
object value2 = value;
for (int i = memberNames.Count - 1; i >= 0; i--)
{
if (value2 == null) return default(TR);
var memberName = memberNames[i];
dynamic info = value2.GetType().GetMember(memberName)[0];
value2 = info.GetValue(value2);
}
return (TR)value2;
}
}
はい。というわけで、一つ言えるのは、これ、あんま速くないんで実用には使わないでくださいね、あくまでネタです、ネタ。
もにゃど
それもにゃど、という人はLINQでMaybeモナドでも検索しませう。既出なので私は書きません。
連打対策などりの同時アクセス禁止機構
- 2013-02-08
ゆるふわ連打対策のお時間です。連打されて無限にあーーーーーーー!という悲鳴を上げたり上げなかったりするとかしないとしても、何らかの対策したいよね!ということで、ASP.NETのお話。Application.Lock使ってSessionに、というのは複数台数あったら死ぬのでナシね(Application.Lockは当然、一台単位でのロックなので複数台数でロックは共有されてない)。そんなわけで、カジュアルな一手を打ちます。先に利用例から。
static void StandardUsage(string token)
{
// 複数サーバーで共有されるロックもどきの取得
using (var rock = DistributedLock.Acquire("StandardUsage-Lock-Token-" + token))
{
rock.ThrowIfLockAlreadyExists(); // 二重に取得された場合は即座に例外!
// 以下、本体を書けばいい
}
}
こんなふーに書けると、楽ですね。tokenは、まあ好きな単位で。ユーザー一人の単位だったら、認証済みなら何らかのIDを。非認証状態なら、POSTのHiddenにGUIDでも仕込んでおけばいい、と。ただの連打対策ってわけじゃなく、複数ユーザー間で同時処理されるのを抑えたければ、何らかのキーを、例えばソーシャルゲームだとチーム単位で、チームIDでかけたりとかします。
ロックもどきには↑の例ではMemcachedを使いました。単純に、Memcachedに指定キーでAddしにいく→Keyが既に存在していると上書きしないで追加に失敗→二重実行時は必ず失敗したという結果を受け取れる(bool:falseで)→Disposeで追加出来たときのみキーを必ず削除する(&保険でexpireもつけておく)
usingの部分は割と定型なので、毎回コントローラーを丸ごと囲むとかなら、属性作って、属性ペタッと貼るだけでOKみたいな形にするといいと思われます!
ド単純ですが、普通に機能して、結構幸せになれるかな?Memcachedならカジュアルに叩いても、相当耐えきれますから。あ、勿論、固定の台にリクエストが飛ぶの前提なのでノードがぐいぐい動的に追加削除されまくるよーな状況ではダメですよ、はい。あんまないでしょうが(Memcachedはクライアントサイドの分散で、複数台あってもキーが同一の場合は基本的に同じ台に飛ぶ)。
public class DistributedLockAlreadyExistsException : Exception
{
public DistributedLockAlreadyExistsException(string key)
: base("LockKey:" + key)
{ }
}
public class DistributedLock : IDisposable
{
static MemcachedClient client = new MemcachedClient();
static readonly TimeSpan DefaultExpire = TimeSpan.FromSeconds(5);
public bool IsAcquiredLock { get; private set; }
string key;
bool disposed;
private DistributedLock(string key, TimeSpan expire)
{
this.key = key;
this.IsAcquiredLock = client.Store(StoreMode.Add, key, DateTime.Now.Ticks, expire);
}
public static DistributedLock Acquire(string key)
{
return Acquire(key, DefaultExpire);
}
public static DistributedLock Acquire(string key, TimeSpan expire)
{
return new DistributedLock(key, expire);
}
public async Task<bool> WaitAndRetry(int retryCount, TimeSpan waitTime)
{
var count = 0;
while (count++ < retryCount && !IsAcquiredLock)
{
await Task.Delay(waitTime);
IsAcquiredLock = client.Store(StoreMode.Add, key, DateTime.Now.Ticks, DefaultExpire);
}
return IsAcquiredLock;
}
public void ThrowIfLockAlreadyExists()
{
if (!IsAcquiredLock)
{
throw new DistributedLockAlreadyExistsException(key);
}
}
public void Dispose()
{
if (!disposed && IsAcquiredLock)
{
disposed = true;
var removeSuccess = client.Remove(key);
}
GC.SuppressFinalize(this);
}
~DistributedLock()
{
Dispose();
}
}
MemcachedのライブラリはEnyimMemcachedです。
Asyncとリトライ
取得に失敗したら、間隔おいてリトライぐらいはしたいですよね、いや、連打対策なら不要ですが、そうでないように使う場合は。でも、ベタにThread.Sleepでまったりしたくないよねえ、という、そこでasyncですよ!async!
async static Task TaskUsage(string token)
{
using (var rock = DistributedLock.Acquire("TaskUsage-Lock-Token-" + token))
{
if (!rock.IsAcquiredLock)
{
// 200ミリ秒感覚で3回取得に挑戦する
await rock.WaitAndRetry(3, TimeSpan.FromMilliseconds(200));
rock.ThrowIfLockAlreadyExists(); // それでもダメなら例外投げるん
}
// 以下、本体を書けばいい!
}
}
WaitAndRetryメソッドではawait Task.Delay(waitTime)によって待機させています。少し前だとまんどくせ、と思って書く気のしない処理も、C# 5.0のお陰でカジュアルに書けるようになっていいですね。
Memcachedを立てないサーバー一台の場合
サーバー一台の場合は、わざわざMemcached立てるのも馬鹿らしいので、インメモリなキャッシュを代替として使えばいいと思われます。HttpRuntime.Cacheでも、System.Runtime.Caching.MemoryCacheでも、なんでもを、client.Storeのとこに差し替えてもらえれば。ただ、MemoryCacheは何かちょっと今回試すためにもぞもぞ弄ってたんですが、Addまわりの挙動がすんごく怪しくて信用ならない気がするので私は使うのパス。大丈夫なのかなあ。
まとめ
うーん、まんま、かつ、ゆるふわ単純な話なので特にまとめる話はないかしらん。
ので、We're Hiringということで謎社のほめぱげが少しだけリニューアル、ただしリクルートページが諸事情でまだ工事中!メールフォーム入れるつもりなので、↑のような感じにC# 5.0をすぐに振り回すような最先端な環境のC#でウェブな開発がやりたい方は、是非応募してください。相当本気で人が欲しいところですねー。現状ですけれど、リリース2週間で早くもランキング3位を獲得などと、あまり細かくは言えないのですけれど、まあ非常に好調ですので、安心して&是非とも一緒に加速させましょう。
Razorで空テンプレートとセパレータテンプレート
- 2013-02-02
Razorに限らずT4でもなんでもいいんですが、テンプレートで素のforeachだと、セパレータだったり空の時の代替テンプレートだったりを、どういう風に表現すればいいのかなあ、と悩ましいのです、どうなっているのでしょう実際世の中的に。
WebFormsのRepeaterだとSeparatorTemplateタグと、拡張すればEmptyTemplateなども作れますね。Smarty(PHPのテンプレート、最近ペチパーなので)には{foreachelse}で配列が空の時のテンプレートが吐かれます。カスタムの構文を定義すれば、勿論なんだってありです。
RepeaterにせよSmartyにせよ、よーするところ独自のテンプレート構文だから好き放題できますが、俺々構文って、それ自体の覚える手間もあり、あんまスッキリしないんですよねえ。RazorのIs not a new language、だからEasy to Learn。は大事。また、そういった独自拡張がないからこそ、Compact, Expressive, and Fluidが実現できる(開き@だけで閉じタグレスはやっぱ偉大)し、フルにIntelliSenseなどエディタサポートも効くわけだし。
やりたいことって、コード上のノイズが限りなく少なく、かつ、HTMLという"テキスト"を最大限コントロールの効く形で吐くこと。なわけで、その辺を損なっちゃあ、見失っちゃあ、いけないね。
で、しかしようするところ、やりたいのはforeachを拡張したい。foreachする時に空の時の出力とセパレータの時の出力を足したい。あと、どうせならインデックスも欲しい。あと、最初の値か、とか最後の値か、とかも欲しい(最初はともかく「最後」はindexがないものを列挙すると大変)
そのうえで、Razorの良さである素のC#構文(と、ほぼほぼ同じものとして扱える)というのを生かしたうえで、書きやすくするには(例えばHtmlヘルパーに拡張メソッド定義して、引数でテンプレートやラムダ渡したり、というのは閉じカッコが増えたり空ラムダが出たりして書きづらいしグチャグチャしてしまいクリーンさが消える)、と思って、考えたのが、foreachで回すアイテム自体に情報載せればいいな、と。
<table>
@foreach (var item in source.ToLoopItem(withEmpty: true, withSeparator: true))
{
// empty template
if (item.IsEmpty)
{
<tr>
<td colspan="2">中身が空だよ!</td>
</tr>
}
// separator
if (item.IsSeparator)
{
<tr>
<td colspan="2">------------</td>
</tr>
}
// body
if (item.IsElement)
{
<tr style="@(item.IsLast ? "background-color:red" : null)">
<td>@item.Index</td>
<td>@item.Item</td>
</tr>
}
}
</table>
何も足さない何も引かない。とはいえどっかに何か足さなきゃならない。C#として崩さないで足すんなら、単独の要素の一つ上に包んで情報を付与してやりゃあいいんだね、と。foreachで回す時にToLoopItem拡張メソッドを呼べば、情報を足してくれます。
IsEmptyは全体が空の時、IsSeparatorは要素の間の時、IsElementが本体の要素の列挙の時、を指します。Elementの時は、更にIsFirst, IsLast, Indexが取れる。item.Itemはちょっと間抜けか。ともあれ、実際にRazorで書いてみた感触としても悪くなく収まってる。
Emptyだけならばループの外で@if(!source.Any()) /* 空の時のテンプレート */ としてやればいいし、そのほうが綺麗感はある。けれど、それだとsourceがIEnumerableの時キモチワルイ(二度列挙開始が走る)とかもあるし、コレクションに関わるものはforeachのスコープ内に全部収まったほうがスッキリ感も、なくもない。
IndexとIsLastだけが欲しいなら、空テンプレートとセパレータはオプションだから、withEmpty, withSeparatorを共にfalseにすれば、全部Elementなので、if(item.IsElement)は不要になる。
それにしてもRazor V2で属性にnull渡すと属性自体を吐かないでくれる機能は素敵ですなあ。クリーンは正義!
実装はこんな感じ。
public struct LoopItem<T>
{
public readonly bool IsEmpty;
public readonly bool IsSeparator;
public readonly bool IsElement;
public readonly bool IsFirst;
public readonly bool IsLast;
public readonly int Index;
public readonly T Item;
public LoopItem(bool isEmpty = false, bool isSeparator = false, bool isElement = false, bool isFirst = false, bool isLast = false, int index = 0, T item = default(T))
{
this.IsEmpty = isEmpty;
this.IsSeparator = isSeparator;
this.IsElement = isElement;
this.IsFirst = isFirst;
this.IsLast = isLast;
this.Index = index;
this.Item = item;
}
public override string ToString()
{
return (IsEmpty) ? "Empty"
: (IsSeparator) ? "Separator"
: Index + ":" + Item.ToString();
}
}
public static class LoopItemEnumerableExtensions
{
public static IEnumerable<LoopItem<T>> ToLoopItem<T>(this IEnumerable<T> source, bool withEmpty = false, bool withSeparator = false)
{
if (source == null) source = Enumerable.Empty<T>();
var index = 0;
using (var e = source.GetEnumerator())
{
var hasNext = e.MoveNext();
if (hasNext)
{
while (true)
{
var item = e.Current;
hasNext = e.MoveNext();
if (hasNext)
{
yield return new LoopItem<T>(index: index, isElement: true, isFirst: (index == 0), item: item);
}
else
{
yield return new LoopItem<T>(index: index, isElement: true, isFirst: (index == 0), isLast: true, item: item);
break;
}
if (withSeparator) yield return new LoopItem<T>(index: index, isSeparator: true);
index++;
}
}
else
{
if (withEmpty)
{
yield return new LoopItem<T>(isEmpty: true);
}
}
}
}
}
大事なのは、IEnumerable<T>へのループは必ず一回にすること、ね。よくあるAny()で調べてから、ループ本体を廻すと、二度列挙実行が走る(Anyは最初を調べるだけですが、もしIEnumerable<T>が遅延実行の場合、そのコストは読めない)というのは、精神衛生上非常に良くない。
あとIsLastを取るために、一手先を取得してからyield returnをしなければならないので、少しゴチャついてしまいましたが、まあ、こういうのがViewの表面上に現れる苦難を思えば!
最近、イミュータブルな入れ物を作りたい時はコンストラクタにずらずら引数並べるでファイナルアンサー。と思うようになりました、一周回って。名前付き引数で書かせれば、数が多くても可読性落ちたりとかないですし、これでいいでしょう。名前付きで書かせることを強制したいけれど、それは無理なので適度に諦めるとして。
最後にユニットテストを置いておきます。例によってMSTest + Chaining Assertionで。
[TestClass]
public class LoopItemTest
{
[TestMethod]
public void Empty()
{
Enumerable.Empty<int>().ToLoopItem(withEmpty: false).Any().IsFalse();
Enumerable.Empty<int>().ToLoopItem(withEmpty: true).Is(new LoopItem<int>(isEmpty: true));
((IEnumerable<int>)null).ToLoopItem(withEmpty: false).Any().IsFalse();
((IEnumerable<int>)null).ToLoopItem(withEmpty: true).Is(new LoopItem<int>(isEmpty: true));
}
[TestMethod]
public void Separator()
{
Enumerable.Range(1, 3).ToLoopItem(withSeparator: false).Is(
new LoopItem<int>(index: 0, item: 1, isFirst: true, isElement: true),
new LoopItem<int>(index: 1, item: 2, isElement: true),
new LoopItem<int>(index: 2, item: 3, isLast: true, isElement: true)
);
Enumerable.Range(1, 1).ToLoopItem(withSeparator: true).Is(
new LoopItem<int>(index: 0, item: 1, isFirst: true, isLast: true, isElement: true)
);
Enumerable.Range(1, 3).ToLoopItem(withSeparator: true).Is(
new LoopItem<int>(index: 0, item: 1, isFirst: true, isElement: true),
new LoopItem<int>(index: 0, isSeparator: true),
new LoopItem<int>(index: 1, item: 2, isElement: true),
new LoopItem<int>(index: 1, isSeparator: true),
new LoopItem<int>(index: 2, item: 3, isLast: true, isElement: true)
);
Enumerable.Range(1, 4).ToLoopItem(withSeparator: true).Is(
new LoopItem<int>(index: 0, item: 1, isFirst: true, isElement: true),
new LoopItem<int>(index: 0, isSeparator: true),
new LoopItem<int>(index: 1, item: 2, isElement: true),
new LoopItem<int>(index: 1, isSeparator: true),
new LoopItem<int>(index: 2, item: 3, isLast: false, isElement: true),
new LoopItem<int>(index: 2, isSeparator: true),
new LoopItem<int>(index: 3, item: 4, isLast: true, isElement: true)
);
}
}
structだと同値比較のために何もしなくていいのが楽ですね、けれどChaining AssertionならIsStructuralEqualがあるので、もしclassでも、やっぱり楽です!
まとめ
RazorだけじゃなくT4でコレクション回す時なんかにも使えます。なにかと毎度毎度、悩みの種なんですよねー。他に、こういうやり方もいいんでないー?とかあったら教えてください。