プログラミング C# - 翔ソフトウェア (Sho's)

C#/.NET/ソフトウェア開発など

『2018 Microsoft MVP Global Summit』に参加してきた | 出発前~ポートランド編 (2018/03/02-04)

2018 MVP Global Summit』に参加する前に、オレゴン州ポートランド(Portland, Oregon)に行ってきた。

ここは、2016年 MVP Global Summit のときに訪れて以来、2回目の滞在だったが、とても良いところだった。

全米一住みやすい都市としても知られるポートランドは、アメリカ合衆国オレゴン州最大の都市だが、シアトルに比べてこじんまりしている。

次のような特徴がある。

  • 緑にあふれた町並み
  • 住民がフレンドリー
  • 消費税0% (オレゴン州)
  • 飲み物 (珈琲、ビール、ワイン) が美味しい
  • シアトル同様雨が多い

出国前からポートランド滞在までを記しておきたい。

1. 出発前

米国でネットに接続する方法は、フリーWiFi、海外Wi-Fiレンタル、国際ローミングなど、いくつかの方法があるが、今回は、T-Mobile SIM を日本の Amazon で購入し、iPhone 6s で使用した。 15日間使い放題で3千円台。

日本にいる間に業者にアクティベーションしてもらい、行きの機内で入れ替えた。 特に設定もプロファイルの削除や設定も必要なく、そのまま快適に LTE で接続することができ、とても快適だった。

電話番号や SMS も使え、テザリングも問題なかったのでお薦めだ。

 

T-Mobile の SIM を買った。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

気分を盛り上げるため前日にポートランド ビール。

 

Porland IPA.

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

2. 渡米

成田空港
 

オレゴン州に向けて移動開始。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

成田空港
 

成田空港に到着。Delta を利用する。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

1週間日本を離れるので最後にトムヤンクン。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

デルタ・コンフォートプラス(Delta Comfort+)を利用
デルタ・コンフォートプラス(Delta Comfort+)を利用

3. ポートランド

ポートランド 1日目

ポートランドTrimet という公共交通機関が発達していて、移動がとても楽。 MAX ライトレール(MAX Light Rail) という電車やバスを大人は1日5ドル、2時間半2.5ドルでいくらでも乗ることができる。 Hop Fastpass という Suica のような非接触型ICカードシステムもあるが、利用しなかった。

 

到着。これから Trimet MAX で、ポートランドのダウンタウンへ。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

Trimet のチケット (2.5時間用)
Trimet のチケット (2.5時間用)

まずはポートランド名物のひとつ珈琲を楽しんだ。 定番の Stumptown Coffee Roasters へ。

 

Portland はコーヒーも有名らしい。エース ホテルの隣の Stumptown Coffee に来た。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

ウォーターフロントへ。 世界一小さい公園「Mill Ends Park」があった。

 

社本さんに教えていただいた世界一小さい公園 Mill Ends Park。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

市の中心にあるパイオニア・コートハウス・スクウェア(Pioneer Courthouse Square)

 

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

ポートランドにはフードカート (屋台) がいっぱいある。

フードカート (屋台) 村
フードカート (屋台) 村

ラン・スー中国庭園(Lan Su Chinese Garden)

 

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

Portland Union StationAMTRAK の駅。

 

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

Powell's Books。 100万冊を超える本があるというとてもユニークな書店。

 

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

70以上のクラフト ビール ブルワリーがあるポートランド。 「世界のベストビール都市」第1位にも選ばれている。 6種類を味見できるトレイが人気。

 

Portland beer tasting tray.

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

Dinner.

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

Deschutes Brewery Portland Public House というバーで、12種類試飲した。 下のメニューの1~6の番号が振ってある2つの列。

Deschutes Brewery Portland Public House のトレイのメニュー
Deschutes Brewery Portland Public House のトレイのメニュー

こちらは、別のバー Fat Head's Portland のトレイ。

Fat Head's Portland のトレイ
Fat Head's Portland のトレイ

こちらは下のメニューの#1~#5。

Fat Head's Portland のトレイのメニュー
Fat Head's Portland のトレイのメニュー

初日にして、ちょっとしたトラブルも発生。

ポートランド 2日目
ポートランド ファーマーズ マーケット(Portland Farmers Market)

ポートランド名物のひとつにファーマーズ マーケットがあり、週末には地方の季節ごとの新鮮な食材が並ぶ。 最大規模のポートランド州立大学(PSU: Portland State University) で開催されているマーケットに行ってきた。

 

土曜日に開催される Portland Farmers Market PSU。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

Portland Farmers Market PSU.

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

MIZUNA.

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

ポートランド美術館(Portland Art Museum)

全米最古の美術館のひとつ (1892年創立) で、3万2千点もの所蔵品があるとても大きな美術館。

ストップモーション・アニメーション映画の製作で有名なライカ(Laika)の展示が見事だった。

 

“The Boxtrolls” by LAIKA. 映画制作会社ライカのパペットの実物が沢山。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

“Kubo and Two Strings” by LAIKA.

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

“Coraline” by LAIKA.

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

“Kubo and Two Strings” by LAIKA.

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

ブルワリー ツアーにも参加。

ブルワリー ツアー
ブルワリー ツアー
ブルワリー ツアー
ブルワリー ツアー
 

ポートランドで Brewery ツアーに参加。自分以外は米国人。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

ポートランドでは、Dossier というホテルに2泊したが、ここは夕方にハッピータイムで地ビールがサービスされ、他の宿泊客と交流できる。

 

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

ポートランド 3日目

ポートランドからシアトル (Seattle)へ移動。

 

ポートランド最高だった。次はシアトル。 Trimet MAX でポートランド空港へ。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

※ 続きは、「『2018 Microsoft MVP Global Summit』に参加してきた (2018/03/04-08)」

「BuriKaigi2018」 (2018年2月3日)を開催しました

BuriKaigi2018 集合写真

富山県氷見市で、毎年恒例の勉強会「BuriKaigi2018」を開催しました。

78名もの方に参加していただきました。 雪が多い季節にも関わらず、今年も遠方からも多くのスピーカーや参加者の方がいらしてくださいました。 本当にありがとうございました。

当日の盛り上がりは Twitter などで見ることができます。

開催概要

BuriKaigi2018
日時 2018/03/02(土) 13:00-18:30 (終了後懇親会)
会場
氷見いきいき元気館 (富山県氷見市)
会場となった氷見いきいき元気館
主催・共催 Hokuriku.NET
北陸エンジニアグループ
スポンサー Forkwell
詳細 BuriKaigi2018 | connpass
スピーカー陣
スピーカー陣
受付付近
受付付近

内容

.NET トラック
.NET側の会場の様子
.NET側の会場の様子
タイトル 概要 スピーカー
Visual Studio 2017 新機能wrap up」 VS2017が登場しておよそ1年が経過したので、リリース後に追加された新機能をまとめてみましょう。 森 博之 氏 @hiroyuki_mori
Microsoft MVP for Visual Studio and Development Technologies
 
森 博之 氏
「Introduction to Xamarin.Forms for MacOS - あるいは UWP から Mac App への布石 -」 初めてXamarin.Formsを触ってみたのが2014年3月。
UWP、AndroidiOSの他にPreviewでMacOSがあるらしい? ので、ちょっと触ってみたのゆるふわセッション。
蜜葉 優 氏 @mitsuba_yu
Microsoft MVP for Windows Development
蜜葉 優 氏
「ItemsStackPanelの気持ちに寄り添ってパフォーマンスを改善」 UWPのListViewは ItemsStackPanelがよしなに仮想化してくれます。
ところが、場合によってはうまく仮想化してもらえません。
ItemsStackPanelの気持ちの寄り添って、パフォーマンスを改善してみます。
@tmyt
Microsoft MVP for Windows Development
 
「コントロールベンダー視点での Command Line Interface (CLI)」 ここ数年?の CLIの隆盛?を UIコンポーネントベンターの一員として10年間在籍している中から考えます。 池原 大然 氏 @Neri78
 
池原 大然 氏
「デスクトップ アプリがこの先生きのこるには 2018」 ここ1年間、著名なデスクトップ アプリがWindowsストアに並ぶなど、UWP以外のWindowsアプリ開発を取り巻く環境が変化しました。
2017年を振り返りつつ、デスクトップ アプリ開発の立ち位置について考えてみます。
@Grabacr07
Microsoft MVP for Windows Development
Microsoft MVP for Visual Studio and Development Technologies
 
Windows Serverの新しい管理ツールの紹介」 Windows Serverは、Server Coreで提供されます。
Server Coreは、コンパクトで身軽な反面デスクトップエクスペリエンスが提供されないため手軽なサーバー管理が難しくなります。
オンプレミスの環境であれば、RSATなどを利用できますが、クラウド環境では難しい状況あります。
これらの問題を解決する新しいWebベースのサーバー管理ツール (Honolulu プロジェクト) について紹介します。
澤田 賢也 氏 @masayasawada
Microsoft MVP for Cloud and Datacenter Management
 
澤田 賢也 氏
見える化、言える化、やりきれる化! Dynamics365 北陸へ拡散」   吉島 良平 氏 @navisionaxapta
Microsoft MVP for Business Solutions

杉本 和也 氏 @sugimomoto
Microsoft MVP for Business Solutions
 
吉島 良平 氏と杉本 和也 氏
C#大好きMVPによるドキドキ ライブ コーディング」   石野 光仁 氏 @ailight
Microsoft MVP for Visual Studio and Development Technologies

鈴木 孝明 氏 @xin9le
Microsoft MVP for Visual Studio and Development Technologies

小島 富治雄 氏 @Fujiwo
Microsoft MVP for Visual Studio and Development Technologies

室星 亮太 氏 @ryotamurohoshi
Microsoft MVP for Visual Studio and Development Technologies
石野 光仁 氏、小島 富治雄 氏、鈴木 孝明 氏、室星 亮太 氏 小島 富治雄 氏、鈴木 孝明 氏、室星 亮太 氏 小島 富治雄 氏、鈴木 孝明 氏、室星 亮太 氏 石野 光仁 氏、小島 富治雄 氏、鈴木 孝明 氏、室星 亮太 氏
Java 他トラック
タイトル 概要 スピーカー
「JavaOne 2017フィードバック - JDKリリースモデル変更とJava EEEclipse Foundationへの移行」 昨年10月初旬に開催されたJavaOne 2017はJava SE 9とJava EE 8が大きなテーマでしたが、開催に合わせて重要な発表が2つありました。
このセッションでは、その2つの発表 - JDKリリースモデル変更とJava EEEclipse Foundationへの移行、EE4Jプロジェクトの設立について、その内容をご説明します。
今後Javaの活用がどう変わるのかがわかります。
伊藤 敬 氏@itakash
Oracle
「モジュール移行の課題と対策」 Java SE 9で導入されたProject Jigsawにより、JARファイルの扱いの煩雑さから解放されました。
その一方でモジュールを使用していないアプリケーションであっても、モジュール導入の影響によりビルドできなかったり、動作しなかったりする問題があります。
また、モジュールを導入するにしても、現状ではモジュールに対応したライブラリと非モジュールのライブラリを併用しなくてはなりません。
本セッションではモジュールについて説明し、モジュールを使用する際の課題とそれに対する対応についても解説していきます。
櫻庭 祐一 氏@skrb
「Running Kubernetes on Azure(仮)」 Azure 上で Kubernetes の動かし方 Docker などのコンテナに興味のある方向け。 山本 誠樹 (やまもと まさき) 氏 @nnasaki
Microsoft MVP for Microsoft Azure
「日本語入力の落とし穴」 日本語を受け取るアプリケーションがおちいりがちな落とし穴について紹介する。
「テキスト入力をハンドリングするアプリケーションを作る人」「日本語入力がうまくできないアプリケーションを修正したい人」「独自の日本語入力を作りたい人」などに役に立つと思う。
@mzp
日本語入力の落とし穴 #burikaigi | みずぴー日記  
「JetBrainsでもっと楽しくコーディング、ワークフロー」 IDEベンダとして有名なJetBrainsですが、IDEのみならずチームツールもリリースしています。
JetBrainsのIDEはもちろんのこと、10名までは無償で使えるチームツールを組み合わせたワークフローまでご紹介いたします。
山本ゆうすけ氏 @yusukey
株式会社サムライズム
Excel芸2018」 Scala関数型プログラミングもできる、マルチパラダイムプログラミング言語です。
本発表では、関数プログラミングによって何ができるようになるかを、SIerにとってなじみが深いであろう話題を交えて紹介します。
Excel絶対殺すマン @bleis
「Asciidocの紹介」 文書は気楽に記述でき、バージョン管理しやすいと便利です。
今回はソフトウェア開発者が記述するような文書のために開発されたAsciidocというツールを紹介します。
Javaさえ入っていれば使えるので、Windowsでも気軽に使うことができます。
セッションでは、環境構築から文法、そして便利な周辺ツールなどを紹介します。

Learning Object

Asciidocによる文書管理
Groovyによるハック
@kyon_mm
LT (ライトニングトーク)
タイトル スピーカー
スポンサービデオ Forkwell
スポンサーのForkwell様
 
リモートデスクトップエンジニアの生態2018 金子雄一 氏 @oskaneko
Microsoft MVP for Enterprise Mobility
金子雄一 氏
 
Office 365ってな~に?
最新のWindowsでOffice製品を適切に使うために知っていて欲しい3つのこと
さくしま たかえ 氏 @RamuMystery
Microsoft MVP for Windows and Devices for IT
Windows Insider MVP
さくしま たかえ 氏
 
データプラットフォーム最新情報をざっくり 山本 美穂 氏 @mihochannel
日本マイクロソフト株式会社
山本 美穂 氏
 
とあるマーケティング部隊とエンジニアとScalaの導入 @grimrose
とあるマーケティング部隊とエンジニアとScalaの導入 | GitHub
 

懇親会

刺身の船盛
刺身の船盛
ぶりしゃぶ
ぶりしゃぶ

翌日のUFOツアー

翌日の観光ツアーで行ったコスモアイル羽咋
翌日の観光ツアーで行ったコスモアイル羽咋

参加レポート

Playing C♯

C丼

C# Advent Calendar 2017 の12月23日の記事。

今回は、C# で C♯ を演奏してみた、と言いたいだけの記事。

C♯

プログラミング言語である C# は、通常 "C#" と表記し "c sharp" と読む。

※ "C" を進化させた "C++" を更に進化させたという意味の "C++++" が由来、というのも有名な話だ。 これは、C# の父である Anders Hejlsberg 氏から直に聞いたので間違いない。

C++++

ご存知の方が多いと思うが、"C#" の # は通常「ナンバーサイン」とか「番号記号」とか「井桁」とか呼ばれるもので、英語圏でも "number" とか "pound" とか "hash" などと発音される。 音楽記号である(シャープ)とは別の文字だ。

そして、筆者はよく音楽の演奏をするのだが、C(シー)(シャープ) と書かれていれば、それは嬰ハ長調、または、嬰ハの音 (ハ長調のド) を表していることが多い。

嬰ハ長調
C♯

というわけで (謎)、C# で C♯ を演奏してみたい。

C# で C♯ を鳴らす

C# で音階を演奏するため、2つの方法を試してみる。

C# で C♯ を鳴らす (Beep)

一つ目は、System.Console.Beep(int frequency, int duration) を用いる方法だ。 このメソッドは、音の周波数と長さを渡して演奏するようになっている。

C# で 音名 C♯ の音を鳴らす (Beep)

音は1オクターブ上がると周波数が2倍になる。平均率の場合、音階の中の全部の半音は同じだけ音が上がっていく。 1オクターブは半音12個分だから、半音上がるごとに周波数は「2の1/12乗」倍になる計算だ。 全音上がるごとだと「2の1/6乗」倍。

なので、C# で音名 C♯ の四分音符を演奏すると、例えば次のようになる。

using System;

static class MusicalScale
{
    public static double A4Frequency { get; set; } = 440.0; // ベースとなる音 A4 (デフォルトは440Hz)

    // 平均律 (equal temperament) | Wikipedia
    // https://ja.wikipedia.org/wiki/%E5%B9%B3%E5%9D%87%E5%BE%8B
    // 1オクターブ (6音) で周波数が2倍になり、半音ごとに周波数は「2の1/12乗」倍になる
    public static double EqualTemperamentFrequency(double relativePositionInScale /* ベースとなる音 A4 から何音上か */)
        => A4Frequency * System.Math.Pow(2.0, relativePositionInScale * 2.0 / 12.0);
}

// 音符の長さ
enum Duration
{
    Whole     = 1600       , // 全音符
    Half      = Whole   / 2, // 二分音符
    Quarter   = Half    / 2, // 四分音符
    Eighth    = Quarter / 2, // 八分音符
    Sixteenth = Eighth  / 2, // 十六分音符
}

class Program
{
    const double relativePositionOfCSharp = -4.0; //  C♯(嬰ハ) は A(イ) より4音下

    static void Main()
    {
        PlayCSharpNote();

        Console.ReadKey();
    }

    // 音名  C♯ の音を演奏 (Beep で)
    static void PlayCSharpNote()
        => Beep(frequency: MusicalScale.EqualTemperamentFrequency(relativePositionOfCSharp), duration: Duration.Quarter);

    // 指定された周波数/長さの音を鳴らす
    static void Beep(double frequency, Duration duration)
        => Console.Beep(frequency: (int)System.Math.Round(frequency), duration: (int)duration);
}

実行すると、C♯ が聞こえてくる。

C# で C♯ の音階を平均率で演奏する (Beep)

この要領で、平均律で C♯ の音階 (嬰ハ長調のドレミファソラシド) を演奏してみよう。

using System;
using System.Collections.Generic;

static class EnumerableExtentions
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (var element in @this)
            action(element);
    }
}

static class MusicalScale
{
    public static double A4Frequency { get; set; } = 440.0; // ベースとなる音 A4 (デフォルトは440Hz)

    // 平均律 (equal temperament) | Wikipedia
    // https://ja.wikipedia.org/wiki/%E5%B9%B3%E5%9D%87%E5%BE%8B
    // 1オクターブ (6音) で周波数が2倍になり、半音ごとに周波数は「2の1/12乗」倍になる
    public static double EqualTemperamentFrequency(double relativePositionInScale /* ベースとなる音 A4 から何音上か */)
        => A4Frequency * System.Math.Pow(2.0, relativePositionInScale * 2.0 / 12.0);
}

// 音符の長さ
enum Duration
{
    Whole     = 1600       , // 全音符
    Half      = Whole   / 2, // 二分音符
    Quarter   = Half    / 2, // 四分音符
    Eighth    = Quarter / 2, // 八分音符
    Sixteenth = Eighth  / 2, // 十六分音符
}

class Program
{
    const double relativePositionOfCSharp = -4.0; //  C♯(嬰ハ) は A(イ) より4音下

    static void Main()
    {
        //PlayCSharpNote();
        PlayCSharpEqualTemperament();

        Console.ReadKey();
    }

    //// 音名  C♯ の音を演奏 (Beep で)
    //static void PlayCSharpNote()
    //    => Beep(frequency: MusicalScale.EqualTemperamentFrequency(relativePositionOfCSharp), duration: Duration.Quarter);

    //  C♯ の音階を演奏 (平均律Beep で)
    static void PlayCSharpEqualTemperament()
        => new[] { 0.0, 1.0, 2.0, 2.5, 3.5, 4.5, 5.5, 6.0 } // ドレミファソラシドの各音がドから何音離れているか
            .ForEach(note => Beep(frequency: MusicalScale.EqualTemperamentFrequency(note + relativePositionOfCSharp), duration: Duration.Quarter));

    // 指定された周波数/長さの音を鳴らす
    static void Beep(double frequency, Duration duration)
        => Console.Beep(frequency: (int)System.Math.Round(frequency), duration: (int)duration);
}
C# で C♯ の音階を純正率で演奏する (Beep)

純正率でもやってみよう。

純正率では各音が単純な整数比になるため、和音にしたときに平均律のようにわずかな周波数のずれからくるうなりが生ずることがなく、綺麗にハモれる。

詳細は省略するが、純正率では、ドレミファソラシドの周波数が、それぞれ、1/1、9/8、5/4、4/3、3/2、5/3、15/8、2/1 となる。

これを踏まえて、C# で書いてみる。

using System;
using System.Collections.Generic;
using System.Linq;

static class EnumerableExtentions
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (var element in @this)
            action(element);
    }
}

static class MusicalScale
{
    public static double A4Frequency { get; set; } = 440.0; // ベースとなる音 A4 (デフォルトは440Hz)

    // 純正律の場合の各音 (ドレミファソラシド) の相対的な周波数の倍率
    readonly static double[] JustIntonationRelativeFrequencyScales = new[] {
            1.0 / 1.0, 9.0 / 8.0, 5.0 / 4.0, 4.0 / 3.0, 3.0 / 2.0, 5.0 / 3.0, 15.0 / 8.0, 2.0 / 1.0
        };

    // 平均律 (equal temperament) | Wikipedia
    // https://ja.wikipedia.org/wiki/%E5%B9%B3%E5%9D%87%E5%BE%8B
    // 1オクターブ (6音) で周波数が2倍になり、半音ごとに周波数は「2の1/12乗」倍になる
    public static double EqualTemperamentFrequency(double relativePositionInScale /* ベースとなる音 A4 から何音上か */)
        => A4Frequency * System.Math.Pow(2.0, relativePositionInScale * 2.0 / 12.0);

    // 純正律 (just intonation) - Wikipedia
    // https://ja.wikipedia.org/wiki/%E7%B4%94%E6%AD%A3%E5%BE%8B
    public static double JustIntonationFrequency(double keynoteRelativePositionInScale, int noteIndex /* ドレミファソラシドの各音のインデックス (0~7) */)
        => EqualTemperamentFrequency(keynoteRelativePositionInScale) * JustIntonationRelativeFrequencyScales[noteIndex];
}

// 音符の長さ
enum Duration
{
    Whole     = 1600       , // 全音符
    Half      = Whole   / 2, // 二分音符
    Quarter   = Half    / 2, // 四分音符
    Eighth    = Quarter / 2, // 八分音符
    Sixteenth = Eighth  / 2, // 十六分音符
}

class Program
{
    const double relativePositionOfCSharp = -4.0; //  C♯(嬰ハ) は A(イ) より4音下

    static void Main()
    {
        //PlayCSharpNote();
        //PlayCSharpEqualTemperament();
        PlayCSharpJustIntonation();

        Console.ReadKey();
    }

    //// 音名  C♯ の音を演奏 (Beep で)
    //static void PlayCSharpNote()
    //    => Beep(frequency: MusicalScale.EqualTemperamentFrequency(relativePositionOfCSharp), duration: Duration.Quarter);

    ////  C♯ の音階を演奏 (平均律Beep で)
    //static void PlayCSharpEqualTemperament()
    //    => new[] { 0.0, 1.0, 2.0, 2.5, 3.5, 4.5, 5.5, 6.0 } // ドレミファソラシドの各音がドから何音離れているか
    //        .ForEach(note => Beep(frequency: MusicalScale.EqualTemperamentFrequency(note + relativePositionOfCSharp), duration: Duration.Quarter));

    //  C♯ の音階を演奏 (純正率を Beep で)
    static void PlayCSharpJustIntonation()
        => Enumerable.Range(start: 0, count: 8) // ドレミファソラシドの各音のインデックス (0~7)
                     .ForEach(noteIndex => Beep(frequency: MusicalScale.JustIntonationFrequency(relativePositionOfCSharp, noteIndex), duration: Duration.Quarter));

    // 指定された周波数/長さの音を鳴らす
    static void Beep(double frequency, Duration duration)
        => Console.Beep(frequency: (int)System.Math.Round(frequency), duration: (int)duration);
}
C# で C♯ を鳴らす (Microsoft.SmallBasic.Library.Sound.PlayMusic)

もう一つ試そう。 Small Basic の機能を使う方法だ。

そのために、まず Visual Studio から NuGet で "SmallBasicLib" をインストールする。

NuGet で "SmallBasicLib" をインストール
NuGet で "SmallBasicLib" をインストール

このライブラリーの機能の一つに、MML (Music Macro Language) を演奏するというものがある。 Microsoft.SmallBasic.Library.Sound.PlayMusic というメソッドを使うことでテキストで記述された旋律を演奏できる。

これを使って、平均律で C♯ の音階 (ドレミファソラシド) を演奏してみよう。 詳しい MML の記述方法は、下記ソースコードのコメントを見てほしい。

C# で C♯ の音階を平均律で演奏する (Microsoft.SmallBasic.Library.Sound.PlayMusic)
using Microsoft.SmallBasic.Library;
using System;

class Program
{
    static void Main()
    {
        PlayCSharpMusic();

        Console.ReadKey();
    }

    //  C♯ の音階を演奏 (平均率を Small Basic Library で)
    static void PlayCSharpMusic() =>
        //  C♯ (嬰ハ長調) | Wikipedia
        // https://ja.wikipedia.org/wiki/%E5%AC%B0%E3%83%8F%E9%95%B7%E8%AA%BF
        // シャープ7箇所(F, C, G, D, A, E, B)
        //  C♯ D♯ F F♯ G♯ A♯ C  C♯ (嬰ハ 嬰二 ヘ 嬰ヘ 嬰ト 嬰イ ハ 嬰ハ)
        Sound.PlayMusic(notes: "L4 O4 C+ D+ F F+ G+ A+ > C C+");

    // Sound.PlayMusic の notes
    //
    // C, D, E, F, G, A, B: 音名
    // R : 休符
    // C+: Cは音名、音を半音上げる (♯)
    // C-: Cは音名、音を半音下げる (♭)
    // Cn: n は音の長さ (1: 全音符、2: 二分音符、2.: 付点二分音符、4: 四分音符、4.: 付点四分音符、…)
    // Ln: n はデフォルトの音の長さ
    // On: n はオクターブ (デフォルトは4)
    // > : 1オクターブ上に
    // < : 1オクターブ下に
}
C# で C♯ の曲を演奏する (Microsoft.SmallBasic.Library.Sound.PlayMusic)

実は C♯、つまり嬰ハ長調の曲というのはそんなに多くないのだが、例えば、バッハ の「平均律クラヴィーア 第1巻 プレリュード 第3番 嬰ハ長調 BWV 848」や「前奏曲とフーガ 第3番 嬰ハ長調 BWV 872」などがそうだ。

例えば下記で聴くことができる (執筆時点)。

試しに、一部だけ C# で書いてみよう。 C♯ の響きを雰囲気だけでも味わっていただければ幸いだ。

using Microsoft.SmallBasic.Library;
using System;

class Program
{
    static void Main()
    {
        PlayBWV848();
        //PlayCSharpMusic();

        Console.ReadKey();
    }

    // バッハ 平均律クラヴィーア 第1巻 プレリュード 第3番 嬰ハ長調 BWV 848
    // 雰囲気だけ
    static void PlayBWV848() =>
        Sound.PlayMusic("L16 O5 E+ C+ > G+ < C+ E+ C+" +
                        "F+ C+ F+ C+ F+ C+" +
                        "G+ C+ G+ C+ G+ C+" +
                        "A+ C+ A+ C+ A+ C+" +
                        "G+ C+ G+ C+ G+ C+" +
                        "F+ E+ D+ E+ F+ D+" +
                        "E+ D+ C+ D+ E+ C+" +
                        "D+ E+ D+ C+ < B+ A+" +
                        "< B+ G+ D+ G+ B+ G+" +
                        "> C+ < G+ > C+ < G+ > C+ < G+" +
                        "> D+ < G+ > D+ < G+ > D+ < G+" +
                        "> E+ < G+ > E+ < G+ > E+ < G+" +
                        "> D+ < G+ > D+ < G+ > D+ < G+" +
                        "> C+ < B+ A+ B+ > C+ < A+" +
                        "B+ A+ G+ A+ B+ G+" +
                        "A+ B > > F+ E+ D+ E+" +
                        "F+ D+ < A+ > D+ F+ D+" +
                        "G+ D+ G+ D+ G+ D+" +
                        "A+ D+ A+ D+ A+ D+" +
                        "B D+ B D+ B D+" +
                        "A+ D+ A+ D+ A+ D+" +
                        "G+ F+ E+ F+ G+ E+" +
                        "F+ E+ D+ E+ F+ D+" +
                        "E+ F+ E+ D+ C+ < B+");

    ////  C♯ の音階を演奏 (平均率を Small Basic Library で)
    //static void PlayCSharpMusic() =>
    //    //  C♯ (嬰ハ長調) | Wikipedia
    //    // https://ja.wikipedia.org/wiki/%E5%AC%B0%E3%83%8F%E9%95%B7%E8%AA%BF
    //    // シャープ7箇所(F, C, G, D, A, E, B)
    //    //  C♯ D♯ F F♯ G♯ A♯ C  C♯ (嬰ハ 嬰二 ヘ 嬰ヘ 嬰ト 嬰イ ハ 嬰ハ)
    //    Sound.PlayMusic(notes: "L4 O4 C+ D+ F F+ G+ A+ > C C+");

    // Sound.PlayMusic の notes
    //
    // C, D, E, F, G, A, B: 音名
    // R : 休符
    // C+: Cは音名、音を半音上げる (♯)
    // C-: Cは音名、音を半音下げる (♭)
    // Cn: n は音の長さ (1: 全音符、2: 二分音符、2.: 付点二分音符、4: 四分音符、4.: 付点四分音符、…)
    // Ln: n はデフォルトの音の長さ
    // On: n はオクターブ (デフォルトは4)
    // > : 1オクターブ上に
    // < : 1オクターブ下に
}

『仙台IT文化祭』に参加してきた (2017/10/28-29)

仙台IT文化祭 2017』に参加してきた。

※ 『C# 大好き MVP による、C# ドキドキ・ライブコーディング!! (出張編)』で登壇。

仙台IT文化祭 2017
日時 2017/10/28(土) - 29日(日)
場所 東北大学川内南キャンパス (C19) 文科系総合講義棟1F&2F (仙台市青葉区川内27-1)
仙台IT文化祭2017 ポスター
仙台IT文化祭2017 ポスター
仙台IT文化祭2017 受付
仙台IT文化祭2017 受付
 

仙台へ。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

仙台に到着。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

会場に到着。 #sendaiitfes

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

昼は学食。安い。 #sendaiitfes

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

#sendaiitfes 2日目に参加中。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

今日のランチも学食。 #sendaiitfes

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

‪抽選会 #sendaiitfes‬

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

懇親会で食べた牛タン
懇親会で食べた牛タン
懇親会で食べた牛タン
懇親会で食べた牛タン
懇親会で食べたセリ鍋
懇親会で食べたセリ鍋
 

仙台を出発。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

小松に着いた。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

 

仙台土産にずんだ餅。家族に好評。

Fujio Kojimaさん(@fujiwo)がシェアした投稿 -

関連記事

資料
ビデオ
Twitter
ブログ

Shos.CsvHelper (CSV を読み書きするためのシンプルなライブラリー)

CSV Icon

csv (Comma-Separated Values または Character-Separated Values) を読み書きするためのシンプルなライブラリーを書いてみた。

csv 形式のファイルは Excel などで表示/編集することができ、便利なので、そこそこ必要になる。 csv を読み書きするライブラリーは既に他にあるが、よりシンプルな使い勝手を目指してみた。

このライブラリーを使うと、プレーンなオブジェクトのコレクション (IEnumerable<Something>) を csv 形式で読み書きすることができる。

使い方

使い方の概要

まず、何か IEnumerable<TElement> なものを用意する:

    // 何か IEnumerable<TElement> なもの
    IEnumerable<ToDo> toDoes = new ToDoList();

例えばこんなクラス:

    class ToDoList : IEnumerable<ToDo> // サンプル コレクション
    {
        public IEnumerator<ToDo> GetEnumerator()
        {
            yield return new ToDo { Id = 1, Title = "filing tax returns", Deadline = new DateTime(2018, 12, 1) };
            yield return new ToDo { Id = 2, Title = "report of a business trip", Detail = "\"ASAP\"", DaySpan = new DaySpan(3), Priority = Priority.High };
            yield return new ToDo { Id = 3, Title = "expense slips", Detail = "book expenses: \"C# 6.0 and the .NET 4.6 Framework\",\"The C# Programming\"", Priority = Priority.Low, Done = true };
            yield return new ToDo { Id = 4, Title = " wish list ", Detail = " \t (1) \"milk\"\n \t (2) shampoo\n \t (3) tissue ", Priority = Priority.High };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

これを、次のように書きこめる:

    const string csvFileName = "todo.csv";
    await toDoes.WriteCsvAsync(csvFileName);

出来上がった csv ファイルは次のようになる:

  • 値がカンマや区切り文字、改行(<CR>、<LF>)、ダブルクォーテーションを含んでいる場合は、ダブルクォーテーションで囲まれる
  • 値の中のダブルクォーテーションは、ダブルクォーテーション2つに置き換わる
Id,Title,Deadline,Done,Priority,Details,DaySpan
1,filing tax returns,2018/12/01 0:00:00,False,Middle,,0
2,report of a business trip,2017/07/12 13:13:01,False,High,"""ASAP""",3
3,expense slips,2017/07/12 13:13:01,True,Low,"book expenses: ""C# 6.0 and the .NET 4.6 Framework"",""The C# Programming""",0
4, wish list ,2017/07/12 13:13:01,False,High," 	 (1) ""milk""
 	 (2) shampoo
 	 (3) tissue ",0

 

作られた csv ファイルを Excel で 開いた様子
作られた csv ファイルを Excel で 開いた様子

読みこみは次のような感じ:

    IEnumerable<ToDo> newToDoes = await CsvSerializer.ReadCsvAsync<ToDo>(csvFileName);

読み書きするもの

コレクションの要素の "get" と "set" の両方を持つ public なプロパティが csv ファイルに読み書きされる。

書きこみ時

書きこみ時には、型を問わず、"ToString()" メソッドで文字列に変換される。

読みこみ時

読みこみ時には、文字列型はそのまま、enum (列挙型) も文字列の通りの値として読みこまれる。 それ以外の型のときは、"TryParse" か "Parse" を使って文字列を値に変更しようとする。 このいずれもできない型は読みこまれない。

"get" と "set" の両方を持っていて、かつ、次のいずれかの型であるプロパティが読みこまれる。

  • 文字列型
  • enum
  • デフォルト コンストラクターを持ち "TryParse" か "Parse" ができる型 (int などの基本的な数値型や DateTime、"TryParse" か "Parse" を持つユーザー定義型)
その他のルール
  • [CsvIgnore()] 属性が付けられたプロパティは、読み書きされない
  • [ColumnName("列名")] 属性が付けられたプロパティは、csv ファイルでの列名が指定されたものに変更される

例えば、上記 ToDo クラスはこんな型:

    // 要素の型のサンプル
    // 〇 の付いたプロパティ: 読み書きされる
    // × の付いたプロパティ: 読み書きされない
    class ToDo
    {
        public int      Id       { get; set; }                     // 〇
        public string   Title    { get; set; } = "";               // 〇
        public DateTime Deadline { get; set; } = DateTime.Now;     // 〇
        public bool     Done     { get; set; }                     // 〇
        public Priority Priority { get; set; } = Priority.Middle;  // 〇 ユーザー定義 enum 
        [ColumnName("Details")]
        public string   Detail   { get; set; } = "";               // 〇 [ColumnName("Details")] で csv ファイルでの列名が "Details" に変更される
        public DaySpan  DaySpan  { get; set; }                     // 〇 "Parse" を持つユーザー定義型 ("TryParse" は持たない)
        [CsvIgnore()]
        public string   Option   { get; set; } = "";               // × [CsvIgnore()] が付けられているので無視される
        public string   Version => "1.0";                          // × get しかできないプロパティなので無視される
    }

上記 ToDo クラスで使われているユーザー定義型は次の通り:

    // ユーザー定義の enum の例
    enum Priority { High, Middle, Low }

    // "Parse" を持つユーザー定義型 ("TryParse" は持たない) の例
    struct DaySpan
    {
        public int Value { get; private set; }

        public DaySpan(int value) => Value = value;
        public static DaySpan Parse(string text) => new DaySpan(int.Parse(text));
        public override string ToString() => Value.ToString();
    }

ヘッダーの有無

csv の読み書きは、ヘッダー部分 (例えば、上記 csv ファイルの一行目) がない場合でも可能になっている。

ただし、ヘッダー部分がある場合は、列が入れ替わっていてもヘッダー部を照合することで読みこみ可能だが、ヘッダー部分がない場合は、列を入れ替えると読みこむことができない。

ヘッダー部分無しでの書きこみの例:

    await toDoes.WriteCsvAsync(csvFilePathName: csvFileName, hasHeader: false);

出来上がった csv ファイル:

1,filing tax returns,2018/12/01 0:00:00,False,Middle,,0
2,report of a business trip,2017/07/06 18:08:13,False,High,"""ASAP""",3
3,expense slips,2017/07/06 18:08:13,True,Low,"book expenses: ""C# 6.0 and the .NET 4.6 Framework"",""The C# Programming""",0
4, wish list ,2017/07/12 13:13:01,False,High," 	 (1) ""milk""
 	 (2) shampoo
 	 (3) tissue ",0

 

作られた csv ファイルを Excel で 開いた様子
作られた csv ファイルを Excel で 開いた様子

こうしたヘッダー部分の無い csv ファイルを読みこむときは次のようにする:

    IEnumerable<ToDo> newToDoes = await CsvSerializer.ReadCsvAsync<ToDo>(csvFilePathName: csvFileName, hasHeader: false);    

その他の指定方法

文字コードは変更可能 (デフォルトは UTF8)。

    CsvSerializer.Encoding = Encoding.GetEncoding(0);

区切り文字も変更可能 (デフォルトは ',')。

    CsvSerializer.Separator = '\t';

ファイル名の代わりに stream も指定できる。

    using (var stream = new FileStream(csvFileName, FileMode.Create))
        await collection.WriteCsvAsync(stream);

leaveOpen を指定することで、読み書きの後に stream を開いたままにすることもできる。

    using (var stream = new FileStream(csvFileName, FileMode.Create))
        await collection.WriteCsvAsync(stream: stream, bufferSize: 1024, leaveOpen:true, hasHeader: true);

非同期メソッド以外に同期メソッドもある。

    toDoes.WriteCsv(csvFileName);

NuGet と GitHub

各ライブラリーは NuGet に公開されているので、Visual Studio からインストールできる。

ソースコードは次の場所で公開:

含まれるプロジェクトは次の通り:

Shos.CsvHelper

  • Csv ライブラリー
  • .NET Standard ライブラリー版
  • .NET Standard 1.3 以降でビルドできる
  • .NET Network 4.6 以降、または .NET Core 1.1 以降対象
  • NuGet パッケージとしてインストールできる: NuGet Gallery | Shos.CsvHelper

Shos.CsvHelper.NetFramework

Shos.CsvHelperSample.NetCore

  • Shos.CsvHelper を利用する .NET Core コンソール アプリケーションのサンプル

Shos.CsvHelperSample.NetFramework

  • Shos.CsvHelper.NetFramework を利用する .NET Framework コンソール アプリケーションのサンプル

Microsoft MVP を再受賞しました (My 13th MVP Award from Microsoft)

MVP_Logo_Horizontal_Preferred_Cyan300_CMYK_72ppi.png

Microsoft MVP Award を再受賞しました。13年目になります。

My MVP Profile

素晴らしい機会とこの機会によって関われた皆様に感謝です。

Tips: interface と partial class で横断的関心事を分離

C丼

C# Advent Calendar 2016 の12月23日の記事。

以前、「C# Tips: interface を 抽象クラス (abstract class) とどう使い分けるか」という記事を書いた。 その中で、「アスペクトの実装を便宜上 (言語の都合上) interface で行う」というイディオムについて触れた。 この記事はその続きだ。 より具体的にこのイディオムを紹介する。

分割攻略と疎結合/高凝集

ソフトウェア開発というものは往々にして複雑さとの戦いになるものだが、プログラムの設計において複雑さに立ち向かうための基礎となる考え方に、分割攻略 (Divide and Conquer、分割統治) というものがある。 大きく複雑な問題をそのまま解くのは大変なので、より小さくシンプルな問題に分けることで、全体としての複雑さを下げて解こう、という戦略だ。

大きく複雑な問題をそのまま解く
大きく複雑な問題をそのまま解く

上の図のようにすると大変なので、たとえば次のようにする。 10倍の大きさの問題の複雑さは10倍ではきかない、というのが基本的なアイディアだ。

分割攻略: 大きく複雑な問題を複数のより小さくシンプルな問題に分けて解く
分割攻略: 大きく複雑な問題を複数のより小さくシンプルな問題に分けて解く

分割するときに重要となるのが、「どう分けるか」だ。 一般的には次のようになれば良いとされている。

  • 疎結合 (low coupling): 分割されたもの同士の結び付きを少なく/弱く・
  • 高凝集 (high cohesion): 一つの分割単位の中が一つの関心事だけになり、かつ、その関心事がその分割単位の中だけにある

ソフトウェアの設計では、疎結合で高凝集になるように分割することが大切、ということだ。 いくら小さな単位に分けても、問題が互いにからみあっていたり、シンプルな単位に分かれていなかったりすると、うまく複雑さが減ってくれないからだ。

そして、これを実現するために、様々な設計の考え方がある。 オブジェクト指向という考え方を使っても、ある程度これを行うことができる。 オブジェクト指向では、クラスやオブジェクトなどという単位に分けることでこれを行うわけだ。

オブジェクト指向と横断的関心事

最初の CAD の設計

ところが、オブジェクト指向といっても万能なわけではなく、クラスやオブジェクトなどに分けるというだけでは、必ずしも高凝集にならない。

例をみてみよう。 以下は、C# による CAD (Computer-Aided Design) の設計の例だ。

CAD のクラス図の例
CAD のクラス図の例

図の中の赤いクラスがモデル (Model) で、抽象図形クラス Figure と、そこから継承した複数の具象図形クラス LineFigure・EllipseFigure がある。

青いクラスがビュー (View) で、モデルの IEnumerable<Figure> というインタフェイスを使ってモデルとデータバインドし、個々の図形を描画する。

C# によるソース コードは、たとえば次のようなものになる (説明に必要のない部分は省略)。

MiniCad.cs
using System;
using System.Collections;
using System.Collections.Generic;

abstract class Figure
{
    public abstract void Draw();
}

class LineFigure : Figure
{
    public override void Draw() => Console.WriteLine("Line!"); // 仮実装
}

class EllipseFigure : Figure
{
    public override void Draw() => Console.WriteLine("Ellipse!"); // 仮実装
}

class CadModel : IEnumerable<Figure>
{
    public IEnumerator<Figure> GetEnumerator()
    {
        yield return new LineFigure   ();
        yield return new LineFigure   ();
        yield return new EllipseFigure();
        yield return new LineFigure   ();
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

class CadView
{
    public IEnumerable<Figure> DataSource {
        set { value.ForEach(figure => figure.Draw()); }
    }
}

class Program
{
    static void Main()
    {
        new CadView().DataSource = new CadModel();
    }
}

static class EnumerableExtensions
{
    public static void ForEach<TElement>(this IEnumerable<TElement> @this, Action<TElement> action)
    {
        foreach (var element in @this)
            action(element);
    }
}

この実装では仮にコンソールに文字列を出力しているだけなので、実行結果は次のようになる。

Line!
Line!
Ellipse!
Line!

この段階ではまだ単純な設計であるため、オブジェクト指向を使うことでそこそこ高凝集になっている。

CAD の設計の変更

さて、ここまで作った後で、ファイルなどに保存するときのためにシリアライズ機能を付けたくなったとする。 CAD は一般的に様々なフォーマットをサポートすることが多いものだが、ここでは仮に SVG (Scalable Vector Graphics) 形式と独自のバイナリ―形式をサポートするものとしよう。

このために、それぞれの図形クラスに、2つの形式のシリアライズのための SvgSerialize と BinarySerialize という2つのメソッドを追加してみるとどうなるだろうか。

シリアライズ機能を付けた CAD のクラス図の例
シリアライズ機能を付けた CAD のクラス図の例

C# によるソース コードは、たとえば次のように変わるだろう。

MiniCad.cs
using System;
using System.Collections;
using System.Collections.Generic;

abstract class Figure
{
    public abstract void Draw();
    public abstract void SvgSerialize();
    public abstract void BinarySerialize();
}

class LineFigure : Figure
{
    public override void Draw() => Console.WriteLine("Line!"); // 仮実装
    public override void SvgSerialize() => Console.WriteLine("SvgSerialize line!"); // 仮実装
    public override void BinarySerialize() => Console.WriteLine("BinarySerialize line!"); // 仮実装
}

class EllipseFigure : Figure
{
    public override void Draw() => Console.WriteLine("Ellipse!"); // 仮実装
    public override void SvgSerialize() => Console.WriteLine("SvgSerialize ellipse!"); // 仮実装
    public override void BinarySerialize() => Console.WriteLine("BinarySerialize ellipse!"); // 仮実装
}

class CadModel : IEnumerable<Figure>
{
    public void SvgSerialize() => this.ForEach(figure => figure.SvgSerialize());
    public void BinarySerialize() => this.ForEach(figure => figure.BinarySerialize());

    public IEnumerator<Figure> GetEnumerator()
    {
        yield return new LineFigure   ();
        yield return new LineFigure   ();
        yield return new EllipseFigure();
        yield return new LineFigure   ();
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

class CadView
{
    public IEnumerable<Figure> DataSource {
        set { value.ForEach(figure => figure.Draw()); }
    }
}

class Program
{
    static void Main()
    {
        var model = new CadModel();
        new CadView().DataSource = model;
        model.SvgSerialize   ();
        model.BinarySerialize();
    }
}

static class EnumerableExtensions
{
    public static void ForEach<TElement>(this IEnumerable<TElement> @this, Action<TElement> action)
    {
        foreach (var element in @this)
            action(element);
    }
}

そして、これが実行結果だ。

Line!
Line!
Ellipse!
Line!
SvgSerialize line!
SvgSerialize line!
SvgSerialize ellipse!
SvgSerialize line!
BinarySerialize line!
BinarySerialize line!
BinarySerialize ellipse!
BinarySerialize line!

しかし、この設計では、高凝集になっていない部分がでてきてしまっているのがお分かりだろうか。

図形クラスという部分に関しては、ある程度高凝集になっている。 各図形に関する仕事 (それぞれの図形の描画やシリアライズ) は、ちゃんと各図形でやっているように見える。

ところが、SVG シリアライズ、あるいはバイナリー シリアライズという関心事に目を向けてみると、そちらはどうなっているだろう。

たとえば、SVG シリアライズに関するメソッドは Figure、LineFigure、EllipseFigure、CadModel という複数のクラスにまたがってしまっている。 バイナリー シリアライズについても同様だ。 これでは、高凝集、すなわち「一つの分割単位の中が一つの関心事だけになり、かつ、その関心事がその分割単位の中だけにある」とは言えないだろう。

このように、オブジェクト指向では複数のクラスをまたがる関心事というものがでてきて設計に困ることがある。 この場合だと、それぞれの図形への関心事とシリアライズという関心事が直交してしまっている。 そのため、一方の視点での分割によるかたまりが、他方の視点での分割をまたがってしてしまうのだ。 このような関心事は、横断的関心事 (crosscutting concern) と呼ばれる。

この例では、はじめは図形のクラス設計を行っていて、後からSVG シリアライズバイナリー シリアライズといった横断的関心事がでてきてしまったのだ。

interface と partial class で横断的関心事を分離

なんとか、それまでのクラスの設計をこわすことなく、この横断的関心事を分離できないものだろうか。

この記事では、C# の interface と partial class を使ったイディオムをご紹介したい。

先のコードのようにいきなりシリアライズが必要なそれぞれのクラスの中に SvgSerialize メソッドと BinarySerialize メソッドを追加するのではなく、先ず partial class とだけしてみる。

MiniCad.cs
using System;
using System.Collections;
using System.Collections.Generic;

abstract partial class Figure
{
    public abstract void Draw();
}

partial class LineFigure : Figure
{
    public override void Draw() => Console.WriteLine("Line!"); // 仮実装
}

partial class EllipseFigure : Figure
{
    public override void Draw() => Console.WriteLine("Ellipse!"); // 仮実装
}

partial class CadModel : IEnumerable<Figure>
{
    public IEnumerator<Figure> GetEnumerator()
    {
        yield return new LineFigure   ();
        yield return new LineFigure   ();
        yield return new EllipseFigure();
        yield return new LineFigure   ();
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

class CadView
{
    public IEnumerable<Figure> DataSource {
        set { value.ForEach(figure => figure.Draw()); }
    }
}

class Program
{
    static void Main()
    {
        var model = new CadModel();
        new CadView().DataSource = model;
        model.SvgSerialize   ();
        model.BinarySerialize();
    }
}

static class EnumerableExtensions
{
    public static void ForEach<TElement>(this IEnumerable<TElement> @this, Action<TElement> action)
    {
        foreach (var element in @this)
            action(element);
    }
}

この Main メソッドでは、SvgSerialize メソッドを BinarySerialize メソッドを使っているが、この時点ではどこにも実装がない。

次に、SvgSerialize メソッドを持つという interface である ISvgSerializable を作り、それを各モデル クラスで実装する。そして、 この実装を partial class の機能を用いて別ファイルで行うことにする。 こんな具合だ。

ISvgSerializable.cs
using System;

interface ISvgSerializable
{
    void SvgSerialize();
}

partial class Figure : ISvgSerializable
{
    public abstract void SvgSerialize();
}

partial class LineFigure
{
    public override void SvgSerialize() => Console.WriteLine("SvgSerialize line!"); // 仮実装
}

partial class EllipseFigure
{
    public override void SvgSerialize() => Console.WriteLine("SvgSerialize ellipse!"); // 仮実装
}

partial class CadModel : ISvgSerializable
{
    public void SvgSerialize() => this.ForEach(figure => figure.SvgSerialize());
}

こうすると、SVG シリアライズという責務を記述するコードがすべてこのファイルに集まることになる。

BinarySerialize メソッドの方も、同様に別ファイルを用意する。 そこで IBinarySerializable という interface を作り、それを各モデル クラスで実装する。

IBinarySerializable.cs
using System;

interface IBinarySerializable
{
    void BinarySerialize();
}

partial class Figure : IBinarySerializable
{
    public abstract void BinarySerialize();
}

partial class LineFigure
{
    public override void BinarySerialize() => Console.WriteLine("BinarySerialize line!"); // 仮実装
}

partial class EllipseFigure
{
    public override void BinarySerialize() => Console.WriteLine("BinarySerialize ellipse!"); // 仮実装
}

partial class CadModel : IBinarySerializable
{
    public void BinarySerialize() => this.ForEach(figure => figure.BinarySerialize());
}

実行結果は変わらない。 クラス図も ISvgSerializable と IBinarySerializable という2つのインタフェイスが加わっただけだ。

インタフェイス追加後の CAD のクラス図の例
インタフェイス追加後の CAD のクラス図の例

これは、オブジェクト指向で横断的関心事を分離したわけではない。 オブジェクト指向にも限界がある。 ここでは、横断的関心事をクラスにマッピングすることではうまく分離できなかった。

そのため、ここでは C# の機能を使い、それまでの関心事に直交した新たな関心事をファイルにマッピングすることで分離した、ということだ。