VB.NET JAPAN TOUR では、実際に VB の開発をしているジョーさんとアマンダさんから最新の VB の情報を二時間半に渡ってお話頂いた。
○ 内容
・VB 2003
・Visual Basic Power Pack
・Visual Basic .NET Resource Kit
・Tablet PC
・Vusial Studio Tools for Office
・.NET Compact Framework
・VB 2005
ClickOnce、My クラス、Edit&Continue 等
・Q&A
○ お土産
・Net Compact Framework Guide (Pocket Reference (O'Reilly))
・Visual Basic .NET Resource Kit CD
・Microsoft Mobile DevCon 2004 Conference DVD
・VB.NET World Tour Tシャツ
class Employee : ICloneable
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Employee(string name)
{ Name = name; }
public object Clone()
{
return new Employee(Name);
}
}
abstract class Person
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Person(string name)
{ Name = name; }
public abstract void DoSomething();
}
class Employee : Person
{
public Employee(string name) : base(name)
{}
public override void DoSomething()
{ /* Do good job. */ }
}
機能上の大きな違いとしては、
抽象クラスは、実装を持つこともできる。上記の例でいうと、name や Name や Person(string name)。
class Context
{
State state = null;
public State State
{
set { state = value; }
}
public void DoSomething()
{
if (state != null)
state.DoSomething();
}
}
abstract class State
{
public abstract void DoSomething();
}
class ConcreteState1 : State
{
public override void DoSomething()
{ /* 省略 */ }
}
class ConcreteState2 : State
{
public override void DoSomething()
{ /* 省略 */ }
}
・interface を使った場合の例:
class Context
{
State state = null;
public State State
{
set { state = value; }
}
public void DoSomething()
{
if (state != null)
state.DoSomething();
}
}
interface State
{
void DoSomething();
}
class ConcreteState1 : State
{
public void DoSomething()
{ /* 省略 */ }
}
class ConcreteState2 : State
{
public void DoSomething()
{ /* 省略 */ }
}
Welcome to the Visual Studio 2005, SQL Server 2005, and BizTalk Server 2006 launch portal. On this site you'll find links to technical content designed to help you learn about the new products. In addition, this site serves as the home for our online launch day activities. Launch is the week of November 7, 2005...and on launch day we’ll have:
米Microsoftは6月7日(現地時間)、現在米フロリダ州オーランドで開催されている同社主催の開発者会議「Tech・Ed 2005」において、データベースの「SQL Server 2005」、開発ツールスイートの「Visual Studio 2005」、Webサービス構築や異なるアプリケーション間の連携を行う「BizTalk Server 2006」の3製品の出荷日が、11月7日になると正式に発表した。また同日、SQL Server 2005向けの開発者プレビュー版(June CTP)が提供されたことも発表した。同CTP版はMicrosoftの SQL Serverサイト <http://www.microsoft.com/sql/downloads/>からダウンロード可能だ。
米Microsoftは,データベース・ソフトウエア「SQL Server 2005」,アプリケーション開発環境「Visual Studio 2005」,電子商取引向けサーバー・ソフトウエア「BizTalk Server 2006」の最終版を11月の第2週にリリースする。Microsoft社が米国時間6月7日に明らかにしたもの。
8/3 と 8/4 にスタディーホールに INETA Japan コミュニティ リーダーとして参加。「INETA Japan Event 2005!」の抽選券と交換できる名刺を配ったりした。
また、8/4 の「Welcome to INETA Japan Event 2005! 『デスマーチからの脱出 ~徹夜からの帰還~』」に「人間系」チームで参加。350名位の方々に聴きにきて頂き、満員御礼だった。このセッションの準備では、色々な問題があったが、セッション自体は各人の能力によって成功だったと思う。反省点は多いが。
Team Foundation からチーム プロジェクトを削除します。このコマンドを使用するには、Team Foundation Server 管理者グループのメンバであるか、または削除するプロジェクトのプロジェクト管理者グループのメンバでなければなりません。チーム プロジェクトを削除した後、復元することはできないため、注意してこのコマンドを使用してください。
[/q] - 確認用のメッセージを表示しません。
</TeamFoundationServer:<サーバー名>> - Team Foundation Server の名前です。
[/force] - 削除できないデータがある場合でも続行します。
<チーム プロジェクト名> - プロジェクトの名前です。名前に空白が含まれる場合は引用符を使用します。
C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies>
コマンドライン上でのツールを使って基本的なことを説明した後で、Visual Studio 上で同じことをやってみせるやり方は分かりやすくて良かった。C# や、ASP.NET、XAML の説明のときにもメモ帳でソースを書くところから説明をしていた。共感できる。単なるドラッグ&ドロップの連続での説明だと見た目は派手だが内容に欠けるのだ。
途中でシャルロット・ジョーンズ氏による Windows 7 ベータと Windows Mobile と Windows Liveのデモがあります。
# 他に、ロビー バック氏による Zune と Xbox のデモ、ジャネット ガロアによる Microsoft Research の未来のコンピュータのデモもあります。
2010 Community Open Day with INETA Japan - Osaka ~ Microsoft Visual Studio 2010 編
『2010 Community Open Day with INETA Japan - Osaka ~ Microsoft Visual Studio 2010 編』
■ 日時: 2010/04/03(土) 13:00-19:00
■ 場所: マイクロソフト株式会社 関西支店 セミナールーム
■ 主催: マイクロソフト株式会社、アイネタジャパン
this._adapter.InsertCommand.CommandText = "INSERT INTO [dbo].[Item] ([Name]) VALUES (@Name);\r\nSELECT Id, Name FROM Item WHERE (Id = SCOPE_IDENTITY())";
今度は、INSERT の後に "SCOPE_IDENTITY()" と一致する Id を持った行を SELECT
しているのが分かります。
デバッグ実行して結果を確認してみます。
今度は、自動生成された Id の値が取得できています。
■ 4. LINQ to SQL の場合
LINQ to SQL の場合はどうでしょう。試してみましょう。
Visual Studio で、新しい「コンソール アプリケーション」を作成し、「ソリューション エクスプローラー」でプロジェクト名を右クリックします。
ポップアップ メニューから、「追加」 - 「新しい項目」を選択します。
「新しい項目の追加」ダイアログ ボックスが開きます。「Visual C# アイテム」 - 「LINQ to SQL クラス」を選択します。
WebMatrix 自体も Microsoft Web Platform Installer によって簡単にインストールできる。
必要なコンポーネント (適切なバージョンの .NET Framework、SQL Server 関連コンポーネント、IIS Express 等) も自動でインストールされる。
ASP.NET ベースや PHP ベースの Web アプリケーション (CMS 等) のインストールも行うことができる。
次に、Visual Studio でプロジェクトを作り、EmployeeDataClasses.dbml という名前で「LINQ to SQL クラス」を追加した。
・LINQ to SQL - ラムダ式で書いた場合
では、ラムダ式を使ってデータ アクセスを行う例から見てみよう。
// LINQ to SQL の例 - ラムダ式で書いた場合
using LambdaExpressionSample; // EmployeeDataClassesDataContext の名前空間
using System;
using System.Linq;
class Program
{
static void Main()
{
var dataContext = new EmployeeDataClassesDataContext();
dataContext.Log = Console.Out; // 発行された SQL をモニターする為に、コンソールに出力
var data1 = dataContext.Employee.Where(employee => employee.Name.Contains("山"));
var data2 = data1.OrderBy(employee => employee.Name);
var data3 = data2.Select(employee => employee.Name);
data3.ToList().ForEach(Console.WriteLine);
}
}
この例で発行された SQL は、以下の通り。
SELECT [t0].[Name]
FROM [dbo].[Employee] AS [t0]
WHERE [t0].[Name] LIKE @p0
ORDER BY [t0].[Name]
-- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [%山%]
Name はなく、NodeType は Lambda だ。名前がないと云うことと、式の種類がラムダ式であることを表している。 Type は Func`3、つまり TResult Func<in T1, in T2, out TResult> になっている。
Body の中を見てみよう。
ラムダ式の中の => より後ろの x + y の部分であることが判る。引数の x が見えている。NodeType は Add で足し算の式であることを表している。 Type は Int32、つまり
int だ。 Left は x、Right は y となっていて、二項演算である足し算の左オペランドが x、右オペランドが y であることを表している。
class Program
{
public static void Main()
{
Expression<Func<int, int, int>> expression = (x, y) => x + y;
((Expression)expression).Show();
}
}
実行してみると、こうなる。
☆Expression: (x, y) => (x + y)
|-- ノードタイプ: Lambda
|-- 名前:
|-- 戻り値の型: System.Int32
|-- 引数群
|-- 引数1
|-- ☆Expression: x
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: x
|-- 引数2
|-- ☆Expression: y
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: y
|-- 本体: (x + y)
|-- ☆Expression: (x + y)
|-- ノードタイプ: Add
|-- 型: System.Int32
|-- 左オペランド: x
|-- ☆Expression: x
|-- ノードタイプ: Parameter
|-- 右オペランド: y
|-- ☆Expression: y
|-- ノードタイプ: Parameter
「☆Expression」とあるところが式 (Expression) だ。ツリー構造になっている。
「引数群」のところでは、x と y がそれぞれ引数という種類の式としてネストしている。
「本体」は x + y の部分で、足し算を表す式としてネストしている。
その足し算の左オペランドである x と右オペランドである y が、それぞれ引数タイプの式として更にネストしている。
次に、ちょっとだけラムダ式を複雑にしてみよう。(x, y) => x + y を (x, y, z) => x + y + z に変えてみる。
こうだ。
class Program
{
public static void Main()
{
Expression<Func<int, int, int, int>> expression = (x, y, z) => x + y + z;
((Expression)expression).Show();
}
}
さて、結果はどう変化するだろうか。
☆Expression: (x, y, z) => ((x + y) + z)
|-- ノードタイプ: Lambda
|-- 名前:
|-- 戻り値の型: System.Int32
|-- 引数群
|-- 引数1
|-- ☆Expression: x
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: x
|-- 引数2
|-- ☆Expression: y
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: y
|-- 引数3
|-- ☆Expression: z
|-- ノードタイプ: Parameter
|-- 引数の型: System.Int32, 引数の名前: z
|-- 本体: ((x + y) + z)
|-- ☆Expression: ((x + y) + z)
|-- ノードタイプ: Add
|-- 型: System.Int32
|-- 左オペランド: (x + y)
|-- ☆Expression: (x + y)
|-- ノードタイプ: Add
|-- 型: System.Int32
|-- 左オペランド: x
|-- ☆Expression: x
|-- ノードタイプ: Parameter
|-- 右オペランド: y
|-- ☆Expression: y
|-- ノードタイプ: Parameter
|-- 右オペランド: z
|-- ☆Expression: z
|-- ノードタイプ: Parameter
一段ネストが深くなったのがお判りだろうか。
x + y + z のところが (x + y) + z、即ち「『x + y という足し算の式』を左オペランドとし、z を右オペランドとする足し算の式」になっている。
このラムダ式は、「int 二つを引数とし int を返すメソッド」を「double 二つを引数とし double を返すメソッド」に変換する。 double を対象とした足し算を int を対象とした足し算に変換したり、double
を対象とした引き算を int を対象とした引き算に変換したりできることになる。
余り意味のない例だが、こんな感じだ。
using System;
static class Program
{
static double Add(double x, double y)
{
return x + y;
}
static double Subtract(double x, double y)
{
return x - y;
}
public static void Main()
{
Func<Func<double, double, double>, Func<int, int, int>> convertFunc = func => ((x, y) => (int)func((double)x, (double)y));
int answer1 = convertFunc(Add )(1, 2);
int answer2 = convertFunc(Subtract)(4, 3);
}
}
このラムダ式を ExpressionViewer クラスを使ってダンプしてみよう。
class Program
{
public static void Main()
{
Expression<Func<Func<double, double, double>, Func<int, int, int>>> expression = func => ((x, y) => (int)func((double)x, (double)y));
((Expression)expression).Show();
}
}
次に、Visual Studio のデバッガーなどを駆使して、Body の式の種類を調べよう。Body は MemberExpression であることが判る筈だ。
MemberExpression であれば、プロパティの内の Member の Name で名前が取得できる。
実際にメソッドを作ってみるとこんな感じだ:
using System;
using System.Linq.Expressions;
public static class ObjectExtensions
{
// Expression からメンバー名を取得
public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
}
早速使ってみよう。
using System;
class Item
{
public int Id { get; set; }
}
class Program
{
static void Test()
{
var memberName = new Item().GetMemberName(item => item.Id);
Console.WriteLine(memberName);
}
public static void Main()
{
Test();
}
}
using System;
using System.Linq.Expressions;
public static class ObjectExtensions
{
// Expression からメンバー名を取得
public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
// Expression からメンバー名を取得
public static string GetMemberName<MemberType>(Expression<Func<MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
}
Item クラス内部に Test 用のメソッドを追加し、そこから呼んでみる。
using System;
class Item
{
public int Id { get; set; }
public void Test()
{
var memberName = ObjectExtensions.GetMemberName(() => Id);
Console.WriteLine(memberName);
}
}
class Program
{
static void Test()
{
var memberName = new Item().GetMemberName(item => item.Id);
Console.WriteLine(memberName);
}
public static void Main()
{
Test();
new Item().Test();
}
}
class Super {}
class Sub : Super {}
class Program
{
static void Main()
{
dynamic item = new Dynamic();
item.Property1 = new Super(); // OK
item.Property1 = new Sub(); // OK
item.Property2 = new Sub(); // OK
item.Property2 = new Super(); // 実行時エラー (型が違うし、派生クラスでもない)
}
}
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
item.Id = 100; // OK
Console.WriteLine(item.Id); // OK
item.Id = "田中一郎"; // OK
Console.WriteLine(item.Id); // OK
item.Name = "田中一郎"; // OK
Console.WriteLine(item.Name); // OK
//Console.WriteLine(item.Address); // 実行時エラー(設定していないプロパティの値を取得しようとしている)
}
}
例えば、ASP.NET や ASP.NET MVC では、セッション変数を利用する際、以下のように連想配列のように使う。
Session["Id"] = 100;
int? id = Session["Id"] as int?;
DynamicObject を利用したラッパー クラスを作ってみよう。
例えば、こんな感じだ。
using System.Dynamic;
using System.Web;
public class SessionObject : DynamicObject
{
readonly HttpSessionStateBase session;
public SessionObject(HttpSessionStateBase session)
{
this.session = session;
}
// プロパティに値を設定しようとしたときに呼ばれる
public override bool TrySetMember(SetMemberBinder binder, object value)
{
session[binder.Name] = value;
return true;
}
// プロパティから値を取得しようとしたときに呼ばれる
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = session[binder.Name];
return true;
}
}
すると、従来のこんな書き方が、
// ASP.NET MVC の場合の一例 (従来の書き方)
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
public class HomeController : Controller
{
public ActionResult Index()
{
var id = Session["Id"]; // まだ Session["Id"] は無いので、id は null
Session["Id"] = 100; // Session["Id"] に 100 を設定
id = Session["Id"]; // id には 100 が返ってくる
var item = Session["Item"]; // まだ Session["Item"] は無いので、item は null
Session["Item"] = new { Id = 200, Name = "田中一郎" }; // Session["Item"] に匿名型のオブジェクトを設定
id = DataBinder.Eval(Session["Item"], "Id"); // id には 200 が返ってくる
return View();
}
}
SessionObject クラスを使うことによって、こんな書き方になる。
// ASP.NET MVC の場合の一例 (SessionObject クラスを使った場合)
using System.Web;
using System.Web.Mvc;
public class HomeController : Controller
{
dynamic session = null;
public ActionResult Index()
{
session = new SessionObject(Session);
var id = session.Id; // まだ session.Id は無いので、id は null
session.Id = 100; // session.Id に 100 を設定
id = session.Id; // id には 100 が返ってくる
var item = session.Item; // まだ session.Item は無いので、item は null
session.Item = new { Id = 200 }; // session.Item に匿名型のオブジェクトを設定
id = session.Item.Id; // id には 200 が返ってくる
return View();
}
}
dynamic に書けることで、コードがややシンプルになった。
一応、Visual Studio でデバッグ実行し、デバッガーを使って値をチェックしてみると、次のようになった。
1. 「まだ session.Id は無いので、id は null」
2. 「id には 100 が返ってくる」
3. 「まだ session.Item は無いので、item は null」
4. 「id には 200 が返ってくる」
まあ、実用的な意味合いは余りないが、連想配列的な部分には、使える可能性がある、と云う一例だ。
■ ExpandoObject を使った例
次は、ExpandoObject だ。
・ExpandoObject にプロパティっぽいものを追加
前回は、ExpandoObject を使って動的なプロパティっぽいものを実現した。
再度やってみよう。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// プロパティっぽいものを追加 その1
item.Id = 10;
Console.WriteLine(item.Id);
}
}
実行結果は次の通り。
10
もうちょっと複雑な例。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// プロパティっぽいものを追加 その2
item.SubItem = new { Id = 100, Name = "田中一郎" };
Console.WriteLine(item.SubItem);
Console.WriteLine(item.SubItem.Id);
Console.WriteLine(item.SubItem.Name);
}
}
実行結果は次の通り。
{ Id = 100, Name = 田中一郎 }
100
田中一郎
・ExpandoObject に static メソッドっぽいものを追加
オブジェクトが何でも追加できるのであれば、デリゲートだって追加できる筈だ。
デリゲートが追加できるのであれば、メソッドっぽいものも実現できるのではないか。
試しに、static メソッドっぽいものの追加を試してみよう。
こんな感じ。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// static メソッドっぽいものを追加
item.SetId = new Action<dynamic, int>((o, value) => { o.Id = value; });
item.GetId = new Func<dynamic, int>(o => o.Id);
// 呼んでみる
item.SetId(item, 30);
Console.WriteLine(item.GetId(item));
}
}
実行結果は次の通り。
30
・ExpandoObject に イベントっぽいものを追加
デリゲートが追加できるのであれば、もうちょっとイベントっぽくしてみよう。
こんな感じだ。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// イベントっぽいものを追加
// 1. 先ず、Update と云う名のイベントっぽいものを用意
item.Update = null;
// 2. 次に、Update にイベントハンドラーっぽいものを追加
item.Update += new Action<dynamic, EventArgs>((sender, _) => Console.WriteLine(sender.Name));
// 3. そして、SetName と云うメソッドっぽいものを用意して、中で Update と云うイベントっぽいものを起こす
item.SetName = new Action<dynamic, string>(
(o, value) => {
o.Name = value;
if (o.Update != null)
o.Update(o, EventArgs.Empty);
}
);
// 4. では、試してみよう:
// SetName を呼ぶと Update が起きて Console.WriteLine(sender.Name) されるかな?
item.SetName(item, "田中次郎");
}
}
実行結果は次の通り。
田中次郎
イベントっぽい。
・ExpandoObject のメンバーの一覧
ここまでを纏めてみる。最後にメンバーの一覧を取ってみよう。
using System;
using System.Dynamic;
class Program
{
static void Main()
{
dynamic item = new ExpandoObject();
// プロパティっぽいものを追加 その1
item.Id = 10;
Console.WriteLine(item.Id);
// プロパティっぽいものを追加 その2
item.SubItem = new { Id = 20, Name = "田中一郎" };
Console.WriteLine(item.SubItem);
Console.WriteLine(item.SubItem.Id);
Console.WriteLine(item.SubItem.Name);
// static メソッドっぽいものを追加
item.SetId = new Action<dynamic, int>((o, value) => { o.Id = value; });
item.GetId = new Func<dynamic, int>(o => o.Id);
// 呼んでみる
item.SetId(item, 30);
Console.WriteLine(item.GetId(item));
// イベントっぽいものを追加
// 1. 先ず、Update と云う名のイベントっぽいものを用意
item.Update = null;
// 2. 次に、Update にイベントハンドラーっぽいものを追加
item.Update += new Action<dynamic, EventArgs>((sender, _) => Console.WriteLine(sender.Name));
// 3. そして、SetName と云うメソッドっぽいものを用意して、中で Update と云うイベントっぽいものを起こす
item.SetName = new Action<dynamic, string>(
(o, value) => {
o.Name = value;
if (o.Update != null)
o.Update(o, EventArgs.Empty);
}
);
// 4. では、試してみよう:
// SetName を呼ぶと Update が起きて、イベントハンドラーの中で Console.WriteLine(sender.Name) されるかな?
item.SetName(item, "田中次郎");
// メンバーの一覧の取得
Console.WriteLine("\nメンバーの一覧:\n");
foreach (var member in item)
Console.WriteLine(member);
}
}
実行結果は次のようになった。
10
{ Id = 20, Name = 田中一郎 }
20
田中一郎
30
田中次郎
メンバーの一覧:
[Id, 30]
[SubItem, { Id = 20, Name = 田中一郎 }]
[SetId, System.Action`2[System.Object,System.Int32]]
[GetId, System.Func`2[System.Object,System.Int32]]
[Update, System.Action`2[System.Object,System.EventArgs]]
[SetName, System.Action`2[System.Object,System.String]]
[Name, 田中次郎]
public なクラスを持ち、その中には、public な Name プロパティと public な Run() メソッドを持てば良い。
using System;
public class Plugin
{
public string Name
{
get { return "Sample Plugin"; }
}
public void Run()
{
Console.WriteLine("This is sample plugin!");
}
}
・プラグインが組み込まれる本体側の実装の例
次はプラグインが組み込まれる本体側だ。
using System;
using System.IO;
using System.Linq;
using System.Reflection;
class Program
{
// プラグインのフォルダー名
const string pluginFolderName = "Plugin";
// 拡張子が dll のものをプラグインと看做す
const string pluginFileName = "*.dll";
static void Main()
{
// プラグインのフォルダーへのフルパス名を取得し、
var pluginFolderName = GetPluginFolderName();
// もしプラグインのフォルダーが在ったら、
if (Directory.Exists(pluginFolderName))
// プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
Directory.GetFiles(pluginFolderName, pluginFileName).ToList().ForEach(Run);
}
// プラグインのフォルダーへのフルパス名を取得
static string GetPluginFolderName()
{
// 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
}
// プラグインを実行
static void Run(string pluginPath)
{
// アセンブリを読み込み、その中から public な最初のクラスを取り出す
var pluginType = Assembly.LoadFrom(pluginPath).GetExportedTypes().FirstOrDefault(type => type.IsClass);
if (pluginType != null)
// インスタンスを生成し、それをプラグインとして実行
Run(Activator.CreateInstance(pluginType));
}
// プラグインを実行
static void Run(dynamic plugin)
{
Console.WriteLine(plugin.Name); // プラグインの Name を表示し、
plugin.Run(); // プラグインを Run
}
}
Sample Plugin
This is sample plugin!
Sample Plugin
This is sample plugin!
両方のプラグインが実行される。
・interface を用いたプラグイン処理の実装の例
因みに、interface を用いた場合の例だと次のようになる。
・interface を用いたプラグイン処理の実装の例 - interface
先ず interface をクラス ライブラリとして準備する。
public interface IPlugin
{
string Name { get; }
void Run();
}
・interface を用いたプラグイン処理の実装の例 - プラグイン側
プラグイン側で interface のライブラリを参照し、クラスで IPlugin を実装する。
public class Plugin : IPlugin
{
public string Name
{
get { return "Sample Plugin"; }
}
public void Run()
{
Console.WriteLine("This is sample plugin!");
}
}
・interface を用いたプラグイン処理の実装の例 - プラグインが組み込まれる本体側
本体側でも interface のライブラリを参照する。
プラグイン側と本体側が同じ interface に依存することになる。
interface を持ったクラスを探して、interface によって Name や Run() にアクセスする。
先の例と違って、dynamic は使わない。
using System;
using System.IO;
using System.Linq;
using System.Reflection;
class Program
{
// プラグインのフォルダー名
const string pluginFolderName = "Plugin";
// 拡張子が dll のものをプラグインと看做す
const string pluginFileName = "*.dll";
static void Main()
{
// プラグインのフォルダーへのフルパス名を取得し、
var pluginFolderName = GetPluginFolderName();
// もしプラグインのフォルダーが在ったら、
if (Directory.Exists(pluginFolderName))
// プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
Directory.GetFiles(pluginFolderName, pluginFileName).ToList().ForEach(Run);
}
// プラグインのフォルダーへのフルパス名を取得
static string GetPluginFolderName()
{
// 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
}
// プラグインを実行
static void Run(string pluginPath)
{
// アセンブリを読み込み、その中から public で IPlugin インタフェイスを持った最初のクラスを取り出す
var pluginType = Assembly.LoadFrom(pluginPath).GetExportedTypes().FirstOrDefault(type => type.IsClass && typeof(IPlugin).IsAssignableFrom(type));
if (pluginType != null)
// インスタンスを生成し、それをプラグインとして実行
Run((IPlugin)Activator.CreateInstance(pluginType));
}
// プラグインを実行
static void Run(IPlugin plugin)
{
Console.WriteLine(plugin.Name); // プラグインの Name を表示し、
plugin.Run(); // プラグインを Run
}
}
続いて、アプリケーション部。ここには、Employee と EmployeeView、TextControl、そして Main を含んだクラス Program がある。
// C# による Oberver パターンの実装 その1
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set
{
if (value != number) {
number = value;
Update();
}
}
}
public string Name
{
get { return name; }
set
{
if (value != name) {
name = value;
Update();
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : IObserver // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public Employee DataSource
{
set { value.Add(this); }
}
public void Update(Observable observable) // データソースのどのプロパティが更新されてもここに来る
{
var employee = observable as Employee;
if (employee != null) {
numberTextControl.Text = employee.Number.ToString();
nameTextControl.Text = employee.Name;
}
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
・実行結果
実行してみると、次のようになる。
TextControl is updated: 100
TextControl is updated:
TextControl is updated: 100
TextControl is updated: 福井太郎
Employee の Number や Name が更新される度に、EmployeeView の Update が呼ばれ、二つの TextControl の表示が更新される。
Number の更新でも Name の更新でも同じ Update が呼ばれている。 ここは、出来ればそれぞれ処理したいところだ。
EmployeeView は、表示部として TextControl を二つ持っていて、それぞれに DataSource の Number と Name を表示する。
・実装 1
それでは、実装していこう (一部エラー処理を省略)。
// C# による Oberver パターンの実装 - event による実装 1
using System;
using System.Collections.Generic;
// アプリケーション部
// Model: 更新を監視される側
class Employee
{
public event Action<Employee> Update; // 更新イベント
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
if (Update != null)
Update(this);
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
if (Update != null)
Update(this);
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text {
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView // Employee 用の View: 更新を監視する側
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public Employee DataSource
{
set {
value.Update += Update; // データソースの更新イベントにハンドラを設定
}
}
void Update(Employee employee) // データソースのどのプロパティが更新されてもここに来る
{
numberTextControl.Text = employee.Number.ToString();
nameTextControl.Text = employee.Name;
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
・実行結果
実行してみると、次のようになる。
前回と同じ結果だ。
TextControl is updated: 100
TextControl is updated:
TextControl is updated: 100
TextControl is updated: 福井太郎
今回も矢張り、Employee の Number や Name が更新される度に、EmployeeView の Update が呼ばれ、二つの TextControl の表示が更新される。
■ C# での Observer パターンの実装 2 - event による実装 2
上では、Number と Name のどちらが更新されても両方の TextControl が更新されている。
これは、Number と Name のどちらが更新されても同じイベントが起きるようになっている為だ。
// C# による Oberver パターンの実装 - event による実装 2
using System;
using System.Collections.Generic;
// アプリケーション部
// Model: 更新を監視される側
class Employee
{
public event Action<Employee> UpdateNumber; // Number 更新イベント
public event Action<Employee> UpdateName; // Name 更新イベント
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
if (UpdateNumber != null)
UpdateNumber(this);
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
if (UpdateName != null)
UpdateName(this);
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text {
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView // Employee 用の View: 更新を監視する側
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public Employee DataSource
{
set {
// それぞれの更新イベントでそれぞれ TextControl を更新
value.UpdateNumber += employee => numberTextControl.Text = employee.Number.ToString();
value.UpdateName += employee => nameTextControl.Text = employee.Name;
}
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
・実行結果
実行してみると、次のようになる。
Number と Name それぞれの更新で、それぞれの TextControl が更新されている。
TextControl is updated: 100
TextControl is updated: 福井太郎
// C# による Oberver パターンの実装 その3
using System;
using System.Collections.Generic;
// フレームワーク部
public static class ObjectExtensions
{
// オブジェクトの指定された名前のプロパティの値を取得
public static object Eval(this object item, string propertyName)
{
var propertyInfo = item.GetType().GetProperty(propertyName);
return propertyInfo == null ? null : propertyInfo.GetValue(item, null);
}
}
abstract class Observable // 更新を監視される側
{
public event Action<string> Update;
protected void RaiseUpdate(string propertyName)
{
if (Update != null)
Update(propertyName);
}
}
abstract class Observer // 更新を監視する側
{
Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
Observable dataSource = null;
public Observable DataSource
{
set {
dataSource = value;
value.Update += Update;
}
}
protected void AddUpdateAction(string propertyName, Action<object> updateAction)
{
updateExpressions[propertyName] = updateAction;
}
void Update(string propertyName)
{
Action<object> updateAction;
if (updateExpressions.TryGetValue(propertyName, out updateAction))
updateAction(dataSource.Eval(propertyName));
}
}
・アプリケーション部の実装
続いて、アプリケーション部。Employee と EmployeeView、TextControl、そして Main を含んだクラス Program だ。
// C# による Oberver パターンの実装 その3
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate("Number");
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate("Name");
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : Observer // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
AddUpdateAction("Number", number => numberTextControl.Text = ((int)number).ToString());
AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
・クラス図 2
先のクラス図に、引数と戻り値の型を書き加えたものを示す。
「C# での Observer パターンの実装 3」のクラス図
・実行結果
実行してみると、次のようになる。
前回の二つ目と同じ結果だ。
TextControl is updated: 100
TextControl is updated: 福井太郎
Number と Name それぞれの更新で、それぞれの TextControl が更新されている。
// C# による Oberver パターンの実装 その3 (前回)
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate("Number"); // ☆ 文字列でプロパティを指定
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate("Name"); // ☆ 文字列でプロパティを指定
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : Observer // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
// ☆ AddUpdateAction の第一引数で文字列でプロパティを指定
AddUpdateAction("Number", number => numberTextControl.Text = ((int)number).ToString());
AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
}
}
これが、こうなる。
// C# による Oberver パターンの実装 その4
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate(() => Number); // ☆ RaiseUpdate がタイプセーフに
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate(() => Name); // ☆ RaiseUpdate がタイプセーフに
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : Observer<Employee> // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
// ☆ AddUpdateAction の第一引数がタイプセーフに
AddUpdateAction(employee => employee.Number, number => numberTextControl.Text = ((int)number).ToString());
AddUpdateAction(employee => employee.Name, name => nameTextControl.Text = (string)name);
}
}
文字列でプロパティを指定している箇所が消えた。
・Main を含んだクラス Program
Main を含んだクラス Program は、変更なし。
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
・実行結果
実行結果も変わらない。
TextControl is updated: 100
TextControl is updated: 福井太郎
矢張り、Number と Name それぞれの更新で、それぞれの TextControl が更新されている。
Windows Phone アプリと Windows Phone クラス ライブラリで参照している .NET
「.NET for Windows Phone」となっている。
・Windows ストア アプリの場合
Windows ストア アプリとWindows ストア クラス ライブラリで参照している .NET
こちらでは、「.NET for Windows Store apps」となっているのが判る。
■ IsSubclassOf 等による検証
つまり、これらで参照している .NET は、全てが共通な訳ではない。
コア部分は共通なのだろうか?
私が試してみたところ、System 名前空間の付近でも微妙な違いがあるようだ。
今回は、以下のようなコードを用いて、この辺りを検証してみたい。
// コンソール アプリケーション
// .NET Framework 4.5
using System.Reflection; // ※ GetRuntimeProperties に必要
class Program
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super));
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super));
bool result3 = sub is Super;
var properties1 = typeof(Sub).GetProperties();
var properties2 = typeof(Sub).GetRuntimeProperties();
}
static void Main()
{
Test();
}
}
これはコンソール アプリケーションのソースコードだが、正常にコンパイルでき、正常に動作する。
Visual Studio のデバッガーで値を確認してみると、次のようになった。
コンソール アプリケーションの場合の実行結果 1
コンソール アプリケーションの場合の実行結果 2
コンソール アプリケーションの場合の実行結果 3
・WPF と Silverlight の場合
同様のことを、WPF と Silverlight の場合で試してみると次のようになる。
先ず WPF から。
// WPF アプリケーション
// .NET Framework 4.5
using System.Windows;
namespace WpfApplication
{
using System.Reflection; // ※ GetRuntimeProperties に必要
public partial class App : Application
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK
bool result3 = sub is Super; // ○ OK
var properties1 = typeof(Sub).GetProperties(); // ○ OK
var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK
}
public App()
{
Test();
}
}
}
// Silverlight 5
using System;
using System.Windows;
namespace SilverlightApplication
{
using System.Reflection; // ※ GetRuntimeProperties に必要
public partial class App : Application
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK
bool result3 = sub is Super; // ○ OK
var properties1 = typeof(Sub).GetProperties(); // ○ OK
var properties2 = typeof(Sub).GetRuntimeProperties(); // × コンパイル エラー
}
public App()
{
Test();
// ... 以下省略 ...
}
// ... 以下省略 ...
}
}
// Windows Phone OS 8.0
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using PhoneApp.Resources;
using System.Diagnostics;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Navigation;
namespace PhoneApp
{
using System;
using System.Reflection; // ※ GetRuntimeProperties に必要
public partial class App : Application
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // ○ OK
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // ○ OK
bool result3 = sub is Super; // ○ OK
var properties1 = typeof(Sub).GetProperties(); // ○ OK
var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK
}
public App()
{
Test();
// ... 以下省略 ...
}
// ... 以下省略 ...
}
}
全て問題なくコンパイル・実行できる。
・Windows ストア アプリの場合
さて、Windows ストア アプリではどうなるだろうか。
こうなるのだ。
// Windows ストア アプリ
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace WindowsStoreApp
{
using System.Reflection; // ※ GetRuntimeProperties に必要
sealed partial class App : Application
{
class Super { }
class Sub : Super
{
public int Number { get; set; }
public string Name { get; set; }
}
static void Test()
{
var sub = new Sub();
bool result1 = sub.GetType().IsSubclassOf(typeof(Super)); // × コンパイル エラー
bool result2 = sub.GetType().IsAssignableFrom(typeof(Super)); // × コンパイル エラー
bool result3 = sub is Super; // ○ OK
var properties1 = typeof(Sub).GetProperties(); // × コンパイル エラー
var properties2 = typeof(Sub).GetRuntimeProperties(); // ○ OK
}
public App()
{
Test();
// ... 以下省略 ...
this.InitializeComponent();
this.Suspending += OnSuspending;
}
// ... 以下省略 ...
}
}
なんと、System 名前空間の Type 型が IsSubclassOf や IsAssignableFrom を持っていないようだ。
実際に調べてみると、Windows ストア アプリが参照している System 名前空間の Type 型は以下の public メンバーを持っている。
// Windows ストア アプリが参照している System 名前空間の Type 型
// アセンブリ System.Runtime.dll, v4.0.0.0
// Framework\.NETCore\v4.5\System.Runtime.dll
namespace System
{
public abstract class Type
{
public static readonly object Missing;
public abstract string AssemblyQualifiedName { get; }
public abstract Type DeclaringType { get; }
public abstract string FullName { get; }
public abstract int GenericParameterPosition { get; }
public abstract Type[] GenericTypeArguments { get; }
public bool HasElementType { get; }
public bool IsArray { get; }
public bool IsByRef { get; }
public abstract bool IsConstructedGenericType { get; }
public abstract bool IsGenericParameter { get; }
public bool IsNested { get; }
public bool IsPointer { get; }
public abstract string Name { get; }
public abstract string Namespace { get; }
public virtual RuntimeTypeHandle TypeHandle { get; }
public override bool Equals(object o);
public bool Equals(Type o);
public abstract int GetArrayRank();
public abstract Type GetElementType();
public abstract Type GetGenericTypeDefinition();
public override int GetHashCode();
public static Type GetType(string typeName);
public static Type GetType(string typeName, bool throwOnError);
public static Type GetTypeFromHandle(RuntimeTypeHandle handle);
public abstract Type MakeArrayType();
public abstract Type MakeArrayType(int rank);
public abstract Type MakeByRefType();
public abstract Type MakeGenericType(params Type[] typeArguments);
public abstract Type MakePointerType();
public override string ToString();
}
}
Caller Info 属性の一つである [CallerMemberName] を付けておくと、呼び出し元のメンバー名 (この場合はプロパティ名) がコンパイラーによって渡されてくる。
これを利用してみよう。
・フレームワーク部 - Observable (今回)
// C# による Oberver パターンの実装 その5
using System;
using System.Runtime.CompilerServices; // ☆ [CallerMemberName] の為に必要
abstract class Observable // 更新を監視される側
{
public event Action<string> Update;
protected void RaiseUpdate([CallerMemberName] string propertyName = "") // ☆ Caller Info 属性を利用
{
if (Update != null)
Update(propertyName);
}
}
すると、Observable の派生クラスで、プロパティ名を渡さなくて良くなる。
前回の Employee はこうだった。
・アプリケーション部 - Employee と EmployeeView (前回)
// C# による Oberver パターンの実装 その4
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate(() => Number); // ☆ RaiseUpdate がタイプセーフに
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate(() => Name); // ☆ RaiseUpdate がタイプセーフに
}
}
}
}
これがこうなる。
・アプリケーション部 - Employee と EmployeeView (今回)
// C# による Oberver パターンの実装 その5
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
}
}
}
}
// C# による Oberver パターンの実装 その5
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.CompilerServices; // ☆ [CallerMemberName] の為に必要
// フレームワーク部
public static class ObjectExtensions
{
// オブジェクトの指定された名前のプロパティの値を取得
public static object Eval(this object item, string propertyName)
{
var propertyInfo = item.GetType().GetProperty(propertyName);
return propertyInfo == null ? null : propertyInfo.GetValue(item, null);
}
// Expression からメンバー名を取得
public static string GetMemberName<ObjectType, MemberType>(this ObjectType @this, Expression<Func<ObjectType, MemberType>> expression)
{
return ((MemberExpression)expression.Body).Member.Name;
}
}
abstract class Observable // 更新を監視される側
{
public event Action<string> Update;
protected void RaiseUpdate([CallerMemberName] string propertyName = "") // ☆ Caller Info 属性を利用
{
if (Update != null)
Update(propertyName);
}
}
abstract class Observer<ObservableType> where ObservableType : Observable // 更新を監視する側
{
Dictionary<string, Action<object>> updateExpressions = new Dictionary<string, Action<object>>();
ObservableType dataSource = null;
public ObservableType DataSource
{
set {
dataSource = value;
value.Update += Update;
}
}
protected void AddUpdateAction<PropertyType>(Expression<Func<ObservableType, PropertyType>> propertyExpression, Action<object> updateAction)
{
AddUpdateAction(dataSource.GetMemberName(propertyExpression), updateAction);
}
void AddUpdateAction(string propertyName, Action<object> updateAction)
{
updateExpressions[propertyName] = updateAction;
}
void Update(string propertyName)
{
Action<object> updateAction;
if (updateExpressions.TryGetValue(propertyName, out updateAction))
updateAction(dataSource.Eval(propertyName));
}
}
// C# による Oberver パターンの実装 その5
using System;
// アプリケーション部
// Model
class Employee : Observable
{
int number = 0;
string name = string.Empty;
public int Number
{
get { return number; }
set {
if (value != number) {
number = value;
RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
}
}
}
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
RaiseUpdate(); // ☆ RaiseUpdate の中で文字列でプロパティを渡す必要がなくなった
}
}
}
}
class TextControl // テキスト表示用のUI部品 (ダミー)
{
public string Text
{
set { Console.WriteLine("TextControl is updated: {0}", value); }
}
}
class EmployeeView : Observer<Employee> // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
AddUpdateAction(employee => employee.Number, number => numberTextControl.Text = ((int)number).ToString());
AddUpdateAction(employee => employee.Name, name => nameTextControl.Text = (string)name);
}
}
class Program
{
static void Main()
{
var employee = new Employee(); // Model, Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = employee; // データバインド
employee.Number = 100; // Number を変更。employeeView に反映されたかな?
employee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
・実行結果
勿論、実行結果に変わりはない。
TextControl is updated: 100
TextControl is updated: 福井太郎
■ 今回のまとめと次回予告
今回は、前回のものに対して補足を行った。
Caller Info を使うことで、アプリケーション部でプロパティを指定する箇所が更にシンプルになった。
プラグインは、必ず string Name() と云う名称を返す public なメソッドを持っている。
プラグインは、必ず void Run() と云う public なメソッドによって動作する。
■ プラグイン側の実装の例
では、プラグイン側から実装してみよう。
今回用意するのは、以下の三種類だ。
・DLL プラグインの実装の例
DLL プラグインは、クラスライブラリとして作成し、ビルドして "Test.dll" とする。
public なクラスを持ち、その中には、public な Name() メソッドと public な Run() メソッドを持つ。
// DLL プラグイン (クラスライブラリとして作成し、ビルドして "Test.dll" に)
using System;
public class Plugin
{
public string Name()
{
return "DLL Plugin";
}
public void Run()
{
Console.WriteLine("DLL Plugin is running!");
}
}
・C# プラグインの実装の例
C# プラグインは、一つの cs ファイルだ。"Test.cs" として保存する。
public なクラスを持ち、その中には、public な Name() メソッドと public な Run() メソッドを持つ。
// C# プラグイン ("Test.cs" として保存)
using System;
public class Plugin
{
public string Name()
{
return "C# Plugin";
}
public void Run()
{
Console.WriteLine("C# Plugin is running!");
}
}
・Python プラグインの実装の例
Python プラグインは、一つの py ファイルだ。"Test.py" として保存する。
Name() メソッドと Run() メソッドを持つ。
# Python Plugnin (Save as "Test.py".)
def Name():
return "Python plugin"
def Run():
print "Python plugin is running!\n"
using System;
// アプリケーション部
class TextControl // テキスト表示用のUI部品 (ダミー)
{
string text = string.Empty;
public string Text
{
set { text = value; Console.WriteLine("TextControl is updated: {0}", value); }
get { return text; }
}
}
class EmployeeView : DynamicObserver // Employee 用の View
{
TextControl numberTextControl = new TextControl(); // Number 表示用
TextControl nameTextControl = new TextControl(); // Name 表示用
public EmployeeView()
{
AddUpdateAction("Number", number => numberTextControl.Text = number.ToString());
AddUpdateAction("Name", name => nameTextControl.Text = (string)name);
}
}
using System;
// アプリケーション部
class Program
{
static void Main()
{
var employee = new Employee(); // Model
dynamic observableEmployee = new DynamicObservable(employee); // Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = observableEmployee; // データバインド
observableEmployee.Number = 100; // Number を変更。employeeView に反映されたかな?
observableEmployee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
}
}
・実行結果
勿論、実行結果に変わりはない。
TextControl is updated: 100
TextControl is updated: 福井太郎
矢張り、Number と Name それぞれの更新で、それぞれの TextControl が更新されている。
今回は、もう少し念入りにチェックしてみよう。
using System;
// アプリケーション部
class Program
{
static void Main()
{
var employee = new Employee(); // Model
dynamic observableEmployee = new DynamicObservable(employee); // Observable
var employeeView = new EmployeeView(); // View, Observer
employeeView.DataSource = observableEmployee; // データバインド
observableEmployee.Number = 100; // Number を変更。employeeView に反映されたかな?
observableEmployee.Number = 200; // Number を再度変更。employeeView に反映されたかな?
observableEmployee.Name = "福井太郎"; // Name を変更。employeeView に反映されたかな?
observableEmployee.Name = "山田次郎"; // Name を再度変更。employeeView に反映されたかな?
observableEmployee.Number = 200; // Number を変更していない。employeeView は更新されない筈
observableEmployee.Name = "山田次郎"; // Name を変更していない。employeeView は更新されない筈
// 念の為値を確認
Console.WriteLine("employee - Number:{0}, Name:{1}", employee.Number, employee.Name);
Console.WriteLine("observableEmployee - Number:{0}, Name:{1}", observableEmployee.Number, observableEmployee.Name);
}
}
・実行結果
実行結果は予想通り。
この範囲ではうまく行っているようだ。
TextControl is updated: 100
TextControl is updated: 200
TextControl is updated: 福井太郎
TextControl is updated: 山田次郎
employee - Number:200, Name:山田次郎
observableEmployee - Number:200, Name:山田次郎
それでは、クラス ライブラリーを動的に読み込み、その中の Data クラスのインスタンスを生成し、それにイベント ハンドラーを追加してみよう。
クラス ライブラリーを動的に読み込み、その中の Data クラスのインスタンスを生成
using System;
using System.Collections.Generic;
using System.Reflection;
class Program
{
static void Main()
{
// アセンブリ名を使ってクラス ライブラリーを動的に読み込み
Assembly assembly = Assembly.Load("ClassLibrary");
// アセンブリ内のクラス Data の型情報を取得
Type dataType = assembly.GetType("ClassLibrary.Data");
// アセンブリ内のクラス Data のインスタンスを生成
var data = Activator.CreateInstance(dataType);
}
}
続いて、View のインスタンスを生成して DataSource に data を設定
// ...省略...
class Program
{
static void Main()
{
// ...省略...
// View のインスタンスを生成して DataSource に data を設定
var view = new View { DataSource = data as IEnumerable<string> };
}
}
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Dictionary<,> の型情報を取得
Type typeOfDictionary = typeof(Dictionary<,>);
Console.WriteLine(typeOfDictionary);
}
}
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Dictionary<string, int> の型情報を取得
Type typeOfDictionaryOfStringAndInt = typeof(Dictionary<string, int>);
Console.WriteLine(typeOfDictionaryOfStringAndInt);
}
}
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person (Object クラスから派生)
class Person // : Object
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
// Person の型情報を取得
Type type = typeof(Person);
// Person の型情報から、Person の全メンバーの情報を取得
MemberInfo[] memberInfos = type.GetMembers();
// 各メンバー情報の名前と種類を表示
foreach (MemberInfo member in memberInfos)
Console.WriteLine("Name: {0}, MemberType: {1}", member.Name, member.MemberType);
}
}
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
// Person の型情報を取得
Type type = typeof(Person);
// Person の型情報から、プロパティ Name の情報を取得
PropertyInfo propertyInfo = type.GetProperty("Name");
Console.WriteLine(propertyInfo);
// プロパティ Name の情報から、アクセサー メソッド (getter と setter) の情報を取得
MethodInfo[] methodInfos = propertyInfo.GetAccessors();
// アクセサー メソッド (getter と setter) の情報を表示
foreach (MethodInfo methodInfo in methodInfos)
Console.WriteLine(methodInfo);
}
}
実行結果:
System.String Name
System.String get_Name()
Void set_Name(System.String)
つまり、プロパティ Name があるときには、内部的にアクセサーとして get_Name と set_Name メソッドを持つことになる。
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
// Person の型情報を取得
Type type = typeof(Person);
var person = new Person();
PropertyInfo propertyInfo = type.GetProperty("Name");
// リフレクションを使って、person の Name の値を設定
propertyInfo.SetValue(person, "明智光秀", null); // .NET 4.5 未満の場合
// .NET 4.5 以上の場合は propertyInfo.SetValue(person, "明智光秀"); でも良い
// リフレクションを使って、person の Name の値を取得
string name = (string)propertyInfo.GetValue(person, null); // .NET 4.5 未満の場合
// .NET 4.5 以上の場合は string name = (string)propertyInfo.GetValue(person); でも良い
Console.WriteLine(name);
}
}
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
// Person の型情報を取得
Type type = typeof(Person);
var person = new Person();
// リフレクションを使って、person の set_Name を呼び出す
MethodInfo methodInfo = type.GetMethod("set_Name");
methodInfo.Invoke(person, new object[] { "織田信長" });
// リフレクションを使って、person の Name の値を取得
PropertyInfo propertyInfo = type.GetProperty("Name");
string name = (string)propertyInfo.GetValue(person, null); // .NET 4.5 未満の場合
// .NET 4.5 以上の場合は string name = (string)propertyInfo.GetValue(person); でも良い
Console.WriteLine(name);
}
}
using System;
using System.Reflection;
// プロパティ Name を持つクラス Person
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main()
{
var person = new Person();
person.set_Name("石田光成"); // コンパイル エラーになる
}
}
A. const なフィールドは static なフィールドとしてリフレクションで値を取得できるが、書き換えられる訳ではない。
試してみよう:
using System;
using System.Reflection;
class ConstantSample
{
public const double PI = 3.14159265358979 ; // const なフィールド
}
class Program
{
static void Main()
{
// Constant の型情報を取得
Type type = typeof(ConstantSample);
// Constant の型情報から、const なフィールド PI の情報を取得
FieldInfo piFieldInfo = type.GetField("PI");
// インスタンスを指定せずに static フィールドとして値を取得してみる
var pi = (double)piFieldInfo.GetValue(null);
Console.WriteLine(pi);
// インスタンスを指定せずに static フィールドとして値を設定してみる
piFieldInfo.SetValue(null, 3.0);
}
}
using System.ComponentModel;
class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int id = 0;
public int Id
{
get { return id; }
set {
if (value != id) {
id = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Id"));
}
}
}
string name = string.Empty;
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
}
using System.ComponentModel;
class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int id = 0;
public int Id
{
get { return id; }
set {
if (value != id) {
id = value;
PropertyChanged.Raise(this);
}
}
}
string name = string.Empty;
public string Name
{
get { return name; }
set {
if (value != name) {
name = value;
PropertyChanged.Raise(this);
}
}
}
}
■ コード スニペット
更に Visual Studio でコード スニペットを利用することで、タイピングの手間を減らすことができる。
このコード スニペットは、例えば次のような XML だ。
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Property for INotifyPropertyChanged</Title>
<Shortcut>propin</Shortcut>
<Description>Code snippet for property for INotifyPropertyChanged.
Use this class (for C# 5.0 or later):
using System.ComponentModel;
using System.Runtime.CompilerServices;
public static class PropertyChangedEventHandlerExtensions
{
public static void Raise(this PropertyChangedEventHandler onPropertyChanged, object sender, [CallerMemberName] string propertyName = "")
{
if (onPropertyChanged != null)
onPropertyChanged(sender, new PropertyChangedEventArgs(propertyName));
}
}</Description>
<Author></Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>type name</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>field</ID>
<ToolTip>field name</ToolTip>
<Default>myField</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>property name</ToolTip>
<Default>MyProperty</Default>
</Literal>
</Declarations>
<Code Language="csharp"><![CDATA[ $type$ $field$ = default($type$);
public $type$ $property$
{
get { return $field$; }
set {
if (!value.Equals($field$)) {
$field$ = value;
PropertyChanged.Raise(this);
}
}
}
$end$]]></Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
レジストリの HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer の Version の値や svcVersion の値で確認できる (Internet Explorer 9 以前は Version、Internet Explorer 10 は svcVersion)。
Windows のバージョンは、6.2.9200.0。
Windows のメジャー バージョンは、6。
Internet Explorer のバージョンは、10.0.9200.16635。
Internet Explorer のメジャー バージョンは、10。
続行するには何かキーを押してください . . .
using System;
using System.Windows;
namespace SingleDocumentSample.Wpf
{
partial class MainWindow : Window
{
public string DocumentPath
{
get { return browser.Source == null ? null : browser.Source.AbsolutePath; }
set {
browser.Source = new Uri(value, UriKind.RelativeOrAbsolute);
Title = value;
ToTop();
}
}
public MainWindow()
{ InitializeComponent(); }
// ウィンドウを最前面に表示
void ToTop()
{
if (WindowState == WindowState.Minimized)
WindowState = WindowState.Normal;
Activate();
}
}
}
■ Windows フォームの場合
そして、SingleDocumentHelper クラスを Windows フォームで使う場合は次のようになる。
SingleDocumentWindowsFormApplication.cs
Main メソッドで使用するためのクラス。
タイマーに System.Windows.Forms.Timer クラスを使う。
using System;
using System.Windows.Forms;
namespace SingleDocumentSample.WinForm
{
class SingleDocumentApplication
{
// ウィンドウズ フォーム用の ITicker の実装
// 指定された時間毎に Tick イベントが起きる
class Ticker : SingleDocumentHelper.ITicker
{
public event Action Tick;
public const int DefaultInterval = 1000;
readonly Timer timer;
public Ticker(int interval = DefaultInterval)
{ timer = new Timer { Interval = interval }; }
public void Start()
{
// Timer を使用して定期的に Tick イベントを起こす
timer.Tick += (sender, e) => RaiseTick();
timer.Start();
}
public void Dispose()
{ timer.Dispose(); }
void RaiseTick()
{
if (Tick != null)
Tick();
}
}
public event Action<string> OpenDocument;
string documentPath = null;
protected string DocumentPath
{
get { return documentPath; }
set {
documentPath = value;
if (OpenDocument != null)
OpenDocument(value); // ドキュメントを開く
}
}
public bool Run(Form mainForm)
{
var commandLineArgs = Environment.GetCommandLineArgs();
if (commandLineArgs.Length > 1) // コマンドライン引数があれば
DocumentPath = commandLineArgs[1]; // ドキュメント ファイルのパスとする
using (var singleDocumentApplication = new SingleDocumentHelper.Application()) {
if (singleDocumentApplication.Initialize(new Ticker(), DocumentPath, sourcePath => DocumentPath = sourcePath)) {
Application.Run(mainForm);
return true;
}
return false; // 他のプロセスが既に起動していれば終了
}
}
}
}
Program.cs
Main メソッド。
using System;
namespace SingleDocumentSample.WinForm
{
static class Program
{
[STAThread]
static void Main()
{
var application = new SingleDocumentApplication();
var mainForm = new MainForm();
application.OpenDocument += documentPath => mainForm.DocumentPath = documentPath;
application.Run(mainForm);
}
}
}
MainForm.cs (MainForm.Designer.cs は不要)
WebBrowser コントロールが貼ってある。
指定されたパスや URL を WebBrowser コントロールで表示する。
using System;
using System.Drawing;
using System.Windows.Forms;
namespace SingleDocumentSample.WinForm
{
class MainForm : Form
{
readonly WebBrowser browser;
public string DocumentPath
{
set {
browser.Navigate(new Uri(value, UriKind.RelativeOrAbsolute));
Text = value;
ToTop();
}
}
public MainForm()
{
SuspendLayout();
ClientSize = new Size(500, 500);
Text = "SingleDocumentSampleWinForm";
browser = new WebBrowser { Dock = DockStyle.Fill };
Controls.Add(browser);
ResumeLayout(false);
}
protected override void Dispose(bool disposing)
{
if (disposing)
browser.Dispose();
base.Dispose(disposing);
}
// ウィンドウを最前面に表示
void ToTop()
{
if (WindowState == FormWindowState.Minimized)
WindowState = FormWindowState.Normal;
Activate();
}
}
}
using System;
using System.Linq.Expressions;
static class Program
{
// Expression (式) による Add メソッドの生成
static Func<int, int, int> AddByExpression()
{
// 生成したい式
// (int x, int y) => x + y
var x = Expression.Parameter(type: typeof(int)); // 引数 x の式
var y = Expression.Parameter(type: typeof(int)); // 引数 y の式
var add = Expression.Add (left: x, right: y); // x + y の式
var lambda = Expression.Lambda (add, x, y ); // (x, y) => x + y の式
// ラムダ式をコンパイルしてデリゲートとして返す
return (Func<int, int, int>)lambda.Compile();
}
static void Main()
{
var addByExpression = AddByExpression(); // デリゲートを動的に生成
var answerByExpression = addByExpression(1, 2); // 生成したデリゲートの呼び出し
Console.WriteLine("answerByExpression: {0}", answerByExpression);
}
}
using System.Text;
// テスト用のクラス
public sealed class Book
{
public string Title { get; set; }
public int Price { get; set; }
// 文字列へ変換するメソッド ToString() をオーバーライド
public override string ToString()
{
return new StringBuilder()
.Append("Title: ").Append(Title)
.Append(", " )
.Append("Price: ").Append(Price)
.ToString();
// または:
//return string.Format("Title: {0}, Price: {1}", Title, Price);
}
}
XML や HTML への変換
次は、XML や HTML に変換するメソッドだ。
XML への変換の拡張メソッドと HTML の table への変換の拡張メソッドを、それぞれ作成してみる。
using System.Collections.Generic;
using System.Text;
public static class BookExtensions
{
// XML へ変換
public static string ToXml(this IEnumerable<Book> @this)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("<?xml version=\"1.0\"?>");
@this.ForEach(
book =>
stringBuilder.Append("<Book>" )
.Append("<Title>").Append(book.Title).Append("</Title>")
.Append("<Price>").Append(book.Price).Append("</Price>")
.Append("</Book>")
);
return stringBuilder.ToString();
}
// HTML の table へ変換
public static string ToHtmlTable(this IEnumerable<Book> @this)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append("<table><thead><tr><th>Title</th><th>Price</th></tr></thead><tbody>");
@this.ForEach(
book => stringBuilder.Append("<tr><td>").Append(book.Title).Append("</td>" )
.Append( "<td>").Append(book.Price).Append("</td></tr>"));
return stringBuilder.Append("</tbody></table>").ToString();
}
}
上のコードで呼び出している ForEach メソッドは別クラスに拡張メソッドとして用意した。
using System;
using System.Collections.Generic;
// ForEach 用
public static class EnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
{
foreach (var item in @this)
action(item);
}
}
文字列を生成する静的な例のテスト
それでは、これらのメソッドを呼び出してみよう。
using System;
using System.Reflection;
static class Program
{
static void Main()
{
文字列への変換のテスト();
XMLへの変換のテスト ();
HTMLへの変換のテスト ();
}
static void 文字列への変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToString());
}
static void XMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToXml());
}
static void HTMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToHtmlTable());
}
}
こちらも呼び出してみよう。先の Program クラスを少し書き換えてリフレクション版のメソッドを呼ぶようにしてみる。
using System;
using System.Reflection;
static class Program
{
static void Main()
{
文字列への変換のテスト();
XMLへの変換のテスト ();
HTMLへの変換のテスト ();
}
static void 文字列への変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByReflection());
}
static void XMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToXmlByReflection());
}
static void HTMLへの変換のテスト()
{
Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示
var books = new[] {
new Book { Title = "Metaprogramming C#", Price = 3200 },
new Book { Title = "Metaprogramming VB", Price = 2100 },
new Book { Title = "Metaprogramming F#", Price = 4300 }
};
Console.WriteLine(books.ToHtmlTableByReflection());
}
}
// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByEmitExtensions初期型
{
// ToString に代わる拡張メソッド (Reflection.Emit 版)
public static string ToStringByEmit初期型<T>(this T @this)
{
// 動的にメソッドを生成し、それを実行
return ToStringGeneratorByEmit.Generate<T>()(@this);
}
}
メソッドのキャッシュ無し版の動作テスト
次のような簡単なプログラムで動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByEmit初期型());
}
}
using System;
using System.Collections.Generic;
// 生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
// メソッド格納用
readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();
// メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
{
return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
}
// メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
{
var targetType = typeof(T);
Delegate method;
if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
method = generator(); // 動的にメソッドを生成して
methods.Add(key: targetType, value: method); // キャッシュに格納
}
return (Func<T, TResult>)method;
}
}
Reflection.Emit によるオブジェクトの文字列への変換 (キャッシュ有り)
では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。
上のメソッド キャッシュ クラス MethodCache を利用して、次のようにする。
// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByEmitExtensions改
{
// 生成したメソッドのキャッシュ
static readonly MethodCache<string> toStringCache = new MethodCache<string>();
// ToString に代わる拡張メソッド (Reflection.Emit 版)
public static string ToStringByEmit改<T>(this T @this)
{
// キャッシュを利用してメソッドを呼ぶ
return toStringCache.Call(item: @this, generator: ToStringGeneratorByEmit.Generate<T>);
}
}
メソッドのキャッシュ有り版の動作テスト
こちらも動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByEmit改());
}
}
// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByExpressionExtensions初期型
{
// ToString に代わる拡張メソッド (式木版)
public static string ToStringByExpression初期型<T>(this T @this)
{
// 動的にメソッドを生成し、それを実行
return ToStringGeneratorByExpression.Generate<T>()(@this);
}
}
メソッドのキャッシュ無し版の動作テスト
今回も、次のような簡単なプログラムで動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByExpression初期型());
}
}
using System;
using System.Collections.Generic;
// 生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
// メソッド格納用
readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();
// メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
{
return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
}
// メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
{
var targetType = typeof(T);
Delegate method;
if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
method = generator(); // 動的にメソッドを生成して
methods.Add(key: targetType, value: method); // キャッシュに格納
}
return (Func<T, TResult>)method;
}
}
式木によるオブジェクトの文字列への変換 (キャッシュ有り)
では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。
上のメソッドキャッシュ クラス MethodCache を利用して、次のようにする。
// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByExpressionExtensions改
{
// 生成したメソッドのキャッシュ
static readonly MethodCache<string> toStringCache = new MethodCache<string>();
// ToString に代わる拡張メソッド (式木版)
public static string ToStringByExpression改<T>(this T @this)
{
// キャッシュを利用してメソッドを呼ぶ
return toStringCache.Call(item: @this, generator: ToStringGeneratorByExpression.Generate<T>);
}
}
メソッドのキャッシュ有り版の動作テスト
こちらも動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByExpression改());
}
}
using System;
static class Program
{
static void Main()
{
var code = ToStringGeneratorByRoslyn.CreateCodeOfToStringByRoslyn<Book>();
Console.WriteLine(code);
}
}
実行してみよう。
(Func<Book, string>)(item => new StringBuilder().Append("Title: ").Append(item.Title).Append(", ").Append("Price: ").Append(item.
Price).ToString())
// 改良前 (メソッドのキャッシュ無し)
public static class ToStringByRoslynExtensions初期型
{
// ToString に代わる拡張メソッド (Roslyn 版)
public static string ToStringByRoslyn初期型<T>(this T @this)
{
return ToStringGeneratorByRoslyn.Generate<T>()(@this);
}
}
メソッドのキャッシュ無し版の動作テスト
次のような簡単なプログラムで動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByRoslyn初期型());
}
}
実行結果は次のようになり、正しく動作する。
Title: Metaprogramming C#, Price: 3200
生成したメソッドのキャッシュ
では、キャッシュを利用してみよう。
今回もメソッド キャッシュ クラスを使う。
using System;
using System.Collections.Generic;
// 生成したメソッド用のキャッシュ
public class MethodCache<TResult>
{
// メソッド格納用
readonly Dictionary<Type, Delegate> methods = new Dictionary<Type, Delegate>();
// メソッドの呼び出し (メソッド生成用のメソッドを引数 generator として受け取る)
public TResult Call<T>(T item, Func<Func<T, TResult>> generator)
{
return Get<T>(generator)(item); // キャッシュにあるメソッドを呼び出す
}
// メソッドをキャッシュを介して取得 (メソッド生成用のメソッドを引数 generator として受け取る)
Func<T, TResult> Get<T>(Func<Func<T, TResult>> generator)
{
var targetType = typeof(T);
Delegate method;
if (!methods.TryGetValue(key: targetType, value: out method)) { // キャッシュに無い場合は
method = generator(); // 動的にメソッドを生成して
methods.Add(key: targetType, value: method); // キャッシュに格納
}
return (Func<T, TResult>)method;
}
}
Roslyn によるオブジェクトの文字列への変換 (キャッシュ有り)
では、キャッシュを行う「オブジェクトの文字列への変換」を作成しよう。
上のメソッドキャッシュ クラス MethodCache を利用して、次のようにする。
// 改良後 (メソッドのキャッシュ有り)
public static class ToStringByRoslynExtensions改
{
// 生成したメソッドのキャッシュ
static readonly MethodCache<string> toStringCache = new MethodCache<string>();
// ToString に代わる拡張メソッド (Roslyn 版)
public static string ToStringByRoslyn改<T>(this T @this)
{
// キャッシュを利用してメソッドを呼ぶ
return toStringCache.Call(item: @this, generator: ToStringGeneratorByRoslyn.Generate<T>);
}
}
メソッドのキャッシュ有り版の動作テスト
こちらも動作させてみよう。
using System;
static class Program
{
static void Main()
{
var book = new Book { Title = "Metaprogramming C#", Price = 3200 };
Console.WriteLine(book.ToStringByRoslyn改());
}
}
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Text;
class Program
{
// Hello world プログラムの CodeDOM
static CodeNamespace HelloWorldCodeDom()
{
…… 同じなので省略 ……
}
// 名前空間からソースコードを生成
static string GenerateCode(CodeNamespace codeNamespace)
{
// コンパイル オプション
var compilerOptions = new CodeGeneratorOptions { IndentString = " ", BracingStyle = "C" };
var codeText = new StringBuilder();
using (var codeWriter = new StringWriter(codeText)) {
// 名前空間からソースコードを生成
CodeDomProvider.CreateProvider("C#").GenerateCodeFromNamespace(codeNamespace, codeWriter, compilerOptions);
}
return codeText.ToString(); // 生成されたソースコード
}
static void Main()
{
// Hello world プログラムの名前空間を生成
var helloWorldCodeDom = HelloWorldCodeDom();
// Hello world プログラムのソースコードを生成
var code = GenerateCode(helloWorldCodeDom);
Console.WriteLine(code); // 表示
}
}
実行してみると、次のように C# のソースコードが表示される。
namespace CodeDomHelloWorldDemo
{
using System;
public class Program
{
static void Main()
{
Console.WriteLine("Hello world!");
Console.ReadKey();
}
}
}
Imports System
Namespace CodeDomHelloWorldDemo
Public Class Program
Shared Sub Main()
Console.WriteLine("Hello world!")
Console.ReadKey
End Sub
End Class
End Namespace
"C#" を "JScript" に置き換えた場合は、次のようになる。
//@cc_on
//@set @debug(off)
import System;
package CodeDomHelloWorldDemo
{
public class Program
{
private static function Main()
{
Console.WriteLine("Hello world!");
Console.ReadKey();
}
}
}
CodeDOM によるアセンブリの生成
では次に、CodeDOM で生成した名前空間をコンパイルしてアセンブリを生成してみよう。
先程までのプログラムに、更に書き足して、次のようにする。
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.IO;
using System.Text;
class Program
{
// Hello world プログラムの CodeDOM
static CodeNamespace HelloWorldCodeDom()
{
…… 同じなので省略 ……
}
// 名前空間からソースコードを生成
static string GenerateCode(CodeNamespace codeNamespace)
{
…… 同じなので省略 ……
}
// 名前空間を実行可能アセンブリへコンパイル
static void CompileExecutableAssembly(CodeNamespace codeNamespace, string outputAssemblyName)
{
var codeCompileUnit = new CodeCompileUnit(); // コンパイル単位
codeCompileUnit.Namespaces.Add(codeNamespace); // コンパイル単位に名前空間を追加
// 実行可能アセンブリへコンパイル
CodeDomProvider.CreateProvider("C#").CompileAssemblyFromDom(
options : new CompilerParameters { // コンパイル オプション
OutputAssembly = outputAssemblyName, // 出力アセンブリのファイル名
GenerateExecutable = true // 実行可能なアセンブリを生成
},
compilationUnits: codeCompileUnit // コンパイル単位
);
}
static void Main()
{
// Hello world プログラムの名前空間を生成
var helloWorldCodeDom = HelloWorldCodeDom();
// Hello world プログラムのソースコードを生成
var code = GenerateCode(helloWorldCodeDom);
Console.WriteLine(code); // 表示
// Hello world プログラムを、実行可能アセンブリとしてコンパイル
const string outputAssemblyName = "CodeDomHelloWorldDemo.exe";
CompileExecutableAssembly(codeNamespace: HelloWorldCodeDom(), outputAssemblyName: outputAssemblyName);
Process.Start(fileName: outputAssemblyName); // 生成された実行可能アセンブリを実行
}
}
namespace CodeDomClassDemo
{
public class Item
{
private int price;
public int Price
{
get
{
return this.price;
}
set
{
this.price = value;
}
}
public override string ToString()
{
return (this.Price + "円");
}
}
}
次のような手順だ。
CodeDomClassDemo 名前空間の生成
― CodeNamespace を new
Item クラスの生成
― CodeTypeDeclaration を new
Item クラスを CodeDomClassDemo 名前空間へ追加
price フィールドを生成
― CodeMemberField を new
price フィールドを Item クラスに追加
Price プロパティを生成
― CodeMemberProperty を new
Price プロパティの Get を追加
― price フィールドへの参照を return する文を生成して追加
Price プロパティの Set を追加
― price フィールドへの参照に value を代入する文を生成して追加
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Text;
class Program
{
// Item クラスの Code DOM
static CodeNamespace ItemClassCodeDom()
{
…… 同じなので省略 ……
}
// 名前空間からソースコードを生成
static string GenerateCode(CodeNamespace codeNamespace)
{
// コンパイル オプション
var compilerOptions = new CodeGeneratorOptions { IndentString = " ", BracingStyle = "C" };
var codeText = new StringBuilder();
using (var codeWriter = new StringWriter(codeText)) {
// 名前空間からソースコードを生成
CodeDomProvider.CreateProvider("C#").GenerateCodeFromNamespace(codeNamespace, codeWriter, compilerOptions);
}
return codeText.ToString(); // 生成されたソースコード
}
static void Main()
{
// Item クラスを含む名前空間を CodeDOM で生成
var itemClassCodeDom = ItemClassCodeDom();
// Item クラスのソースコードを生成
var code = GenerateCode(itemClassCodeDom);
Console.WriteLine(code);
}
}
実行してみると、次のように C# のソースコードが表示される。
namespace CodeDomClassDemo
{
public class Item
{
private int price;
public int Price
{
get
{
return this.price;
}
set
{
this.price = value;
}
}
public override string ToString()
{
return (this.Price + "円");
}
}
}
Namespace CodeDomClassDemo
Public Class Item
Private price As Integer
Public Property Price() As Integer
Get
Return Me.price
End Get
Set
Me.price = value
End Set
End Property
Public Overrides Function ToString() As String
Return (Me.Price + "円")
End Function
End Class
End Namespace
using Roslyn.Compilers.CSharp;
using System;
class Walker : SyntaxWalker // Visitor パターンでソースコードを解析
{
public override void Visit(SyntaxNode node) // 各ノードを Visit
{
if (node != null)
Console.WriteLine("[Node - Type: {0}, Kind: {1}]\n{2}\n", node.GetType().Name, node.Kind, node);
base.Visit(node);
}
}
この Walker クラスで、先程の単純な C# のソースコードを解析してみる。
using Roslyn.Compilers.CSharp;
class Program
{
static void Main()
{
// 解析する C# のソースコード
var sourceCode = @"
using System;
class Program
{
static void Main()
{}
}
";
var syntaxTree = SyntaxTree.ParseText(sourceCode); // ソースコードをパースしてシンタックス ツリーに
var rootNode = syntaxTree.GetRoot(); // ルートのノードを取得
new Walker().Visit(rootNode); // 解析
}
}
using Roslyn.Compilers.CSharp;
using System;
class Program
{
static void Main()
{
// 改変する C# のソースコード
var sourceCode = @"
public class MyViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged メンバー
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion // INotifyPropertyChanged メンバー
}
";
var syntaxTree = SyntaxTree.ParseText(sourceCode); // ソースコードをパースしてシンタックス ツリーに
var rootNode = syntaxTree.GetRoot(); // ルートのノードを取得
var newNode = new RemoveRegionRewriter().Visit(node: rootNode); // #region と #endregion の除去
Console.WriteLine(newNode.NormalizeWhitespace()); // 整形して表示
}
}
実行してみよう。
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
using System;
// 名前空間
namespace Graphics
{
// 二次元ベクトルのクラス
public class Vector {
public double X { get; set; }
public double Y { get; set; }
// コンストラクター
public Vector(double x = 0.0, double y = 0.0)
{
X = x;
Y = y;
}
// プロパティ
public double Length
{
get { return Math.Sqrt(X * X + Y * Y); }
}
// メソッド
public Vector Add(Vector vector)
{
return new Vector(X + vector.X, Y + vector.Y);
}
public Vector Multiply(Vector vector)
{
return new Vector(X * vector.X, this.Y * vector.Y);
}
}
}
var canvas = <HTMLCanvasElement>document.querySelector("canvas");
var context = canvas.getContext("2d");
var imageData = context.createImageData(canvas.width, canvas.height);
また、ImageData オブジェクトに任意の色のピクセルを置くには、次のように ImageData オブジェクトの data に赤、緑、青、アルファ値の順で1バイトずつ書き込めば良い。
// ImageData の指定した座標の 1 ピクセルを指定した色にする
private static setPixel(imageData: ImageData, x: number, y: number, red: number, green: number, blue: number, alpha: number = 0xff) {
// 指定した座標のピクセルが ImageData の data のどの位置にあるかを計算
var index = (x + y * imageData.width) * 4;
// その位置から、赤、緑、青、アルファ値の順で1バイトずつ書き込むことで、ピクセルがその色になる
imageData.data[index + 0] = red ;
imageData.data[index + 1] = green;
imageData.data[index + 2] = blue ;
imageData.data[index + 3] = alpha;
}
using MandelbrotSample.WindowsStore.Graph;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace MandelbrotSample.WindowsStore
{
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
}
async void Page_Loaded(object sender, RoutedEventArgs e)
{
await Run();
}
async Task Run()
{
var canvasPosition = new IntRectangle { Position = new IntVector(),
Size = new IntSize { Width = (int)canvas.ActualWidth ,
Height = (int)canvas.ActualHeight } };
progress.IsActive = true ;
await MainProgram.RunAsync(canvasPosition, image);
progress.IsActive = false;
}
}
}
Graph.cs
graph.ts にあたる部分は次のような感じ。
こちらでは、Palette はクラスにした。
また、TypeScript では number という型だったものは、int と double に分けた。
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.UI;
using Windows.UI.Xaml.Media.Imaging;
namespace MandelbrotSample.WindowsStore.Graph
{
public class Palette
{
readonly Color[] colors;
public int Size
{
get { return colors.Length; }
}
public Palette(int size)
{ colors = new Color[size]; }
public Color this[int index]
{
get
{
return index >= 0 && index < Size ? colors[index] : new Color();
}
set
{
if (index >= 0 && index < Size)
colors[index] = value;
}
}
}
public class Vector
{
public double X { get; set; }
public double Y { get; set; }
public static Vector operator +(Vector vector1, Vector vector2)
{
return new Vector { X = vector1.X + vector2.X, Y = vector1.Y + vector2.Y };
}
public static Vector operator -(Vector vector1, Vector vector2)
{
return new Vector { X = vector1.X - vector2.X, Y = vector1.Y - vector2.Y };
}
public static Vector operator *(Vector vector1, Vector vector2)
{
return new Vector { X = vector1.X * vector2.X, Y = vector1.Y * vector2.Y };
}
public static Vector operator *(Vector vector, double value)
{
return new Vector { X = vector.X * value, Y = vector.Y * value };
}
public static Vector operator /(Vector vector, double value)
{
return new Vector { X = vector.X / value, Y = vector.Y / value };
}
}
public class IntVector
{
public int X { get; set; }
public int Y { get; set; }
public static IntVector operator -(IntVector vector, IntSize size)
{
return new IntVector { X = vector.X - size.Width, Y = vector.Y - size.Height };
}
}
public class IntSize
{
public int Width { get; set; }
public int Height { get; set; }
public static IntSize operator +(IntSize size1, IntSize size2)
{
return new IntSize { Width = size1.Width + size2.Width, Height = size1.Height + size2.Height };
}
public static IntSize operator *(IntSize size, IntVector vector)
{
return new IntSize { Width = size.Width * vector.X, Height = size.Height * vector.Y };
}
}
public class IntRectangle
{
public IntVector Position { get; set; }
public IntSize Size { get; set; }
}
public static class WriteableBitmapExtension
{
public static void Clear(this WriteableBitmap bitmap, Color color)
{
var arraySize = bitmap.PixelBuffer.Capacity;
var array = new byte[arraySize];
for (var index = 0; index < arraySize; index += 4) {
array[index ] = color.B;
array[index + 1] = color.G;
array[index + 2] = color.R;
array[index + 3] = color.A;
}
using (var pixelStream = bitmap.PixelBuffer.AsStream()) {
pixelStream.Seek(0, SeekOrigin.Begin);
pixelStream.Write(array, 0, array.Length);
}
}
public static void SetPixel(this WriteableBitmap bitmap, int x, int y, Color color)
{
var bitmapWidth = bitmap.PixelWidth;
var bitmapHeight = bitmap.PixelHeight;
if (!IsValid(bitmapWidth, bitmapHeight, x, y))
return;
var index = ToIndex(bitmapWidth, x, y);
Debug.Assert(index >= 0 && index < bitmap.PixelBuffer.Capacity);
using (var pixelStream = bitmap.PixelBuffer.AsStream()) {
var array = new byte[] { color.B, color.G, color.R, color.A };
pixelStream.Seek(index, SeekOrigin.Begin);
pixelStream.Write(array, 0, array.Length);
}
}
static int ToIndex(int bitmapWidth, int x, int y)
{
return (x + y * bitmapWidth) * 4;
}
static bool IsValid(int width, int height, int x, int y)
{
return x >= 0 && x < width &&
y >= 0 && y < height;
}
}
public class Sheet
{
public WriteableBitmap Bitmap { get; set; }
public Sheet(IntSize size, Color backgroundColor = new Color())
{
Bitmap = new WriteableBitmap(size.Width, size.Height);
Bitmap.Clear(backgroundColor);
}
public void SetPixel(IntVector position, Color color)
{ Bitmap.SetPixel(position.X, position.Y, color); }
}
}
Mandelbrot.cs
mandelbrot.ts にあたる部分は次のような感じ。
TypeScript の run メソッドは、非同期の RunAsync とした。
using MandelbrotSample.WindowsStore.Graph;
using System;
using System.Threading.Tasks;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
namespace MandelbrotSample.WindowsStore
{
public class Mandelbrot
{
public class Parameter {
public Vector Position { get; private set; }
public int Maximum { get; private set; }
public IntSize Size { get; private set; }
public double Ratio { get; private set; }
public Parameter(IntSize size) : this(position: new Vector { X = -2.8, Y = 1.5 }, scale: 5.0, maximum: 32, size: size)
{}
public Parameter(Vector position, double scale, int maximum, IntSize size)
{
Position = position;
Maximum = maximum;
Size = size;
Ratio = scale / size.Width;
}
}
const double squareBorder = 25.0;
readonly Sheet sheet;
readonly Parameter parameter;
public WriteableBitmap Bitmap
{
get { return sheet.Bitmap; }
}
public Mandelbrot(IntRectangle position, Color backgroundColor = new Color())
{
sheet = new Sheet(position.Size, backgroundColor);
this.parameter = new Parameter(size: position.Size);
}
public Mandelbrot(IntRectangle position, Parameter parameter, Color backgroundColor = new Color())
{
sheet = new Sheet(position.Size, backgroundColor);
this.parameter = parameter;
}
public void Draw(Palette palette)
{
var point = new Graph.IntVector();
for (point.Y = 0; point.Y < parameter.Size.Height; point.Y++) {
for (point.X = 0; point.X < this.parameter.Size.Width; point.X++) {
var a = parameter.Position + new Vector { X = point.X, Y = -point.Y } * parameter.Ratio;
SetPixel(point, GetCount(a), palette);
}
}
}
int GetCount(Vector a)
{
var square = new Vector();
var point = new Vector();
var count = 0;
do {
point = new Vector { X = square.X - square.Y + a.X, Y = 2.0 * point.X * point.Y + a.Y };
square = point * point;
count++;
} while (square.X + square.Y < squareBorder && count <= parameter.Maximum);
return count < parameter.Maximum ? count : 0;
}
void SetPixel(IntVector point, int count, Palette palette)
{
sheet.SetPixel(point, palette[ToColorIndex(count, palette.Size)]);
}
static int ToColorIndex(int colorNumber, int paletteSize)
{
var colorIndexNumber = paletteSize * 2 - 2;
var colorIndex = colorNumber % colorIndexNumber;
if (colorIndex >= paletteSize)
colorIndex = colorIndexNumber - colorIndex;
return colorIndex;
}
}
class MainProgram
{
public static async Task RunAsync(IntRectangle canvasPosition, Image image)
{
await Window.Current.Dispatcher.RunIdleAsync(e => image.Source = DrawMandelbrot(canvasPosition));
}
static Palette GetPalette()
{
var palette = new Graph.Palette(16);
palette[ 0] = new Color { R = 0x02, G = 0x08, B = 0x80 };
palette[ 1] = new Color { R = 0x10, G = 0x10, B = 0x70 };
palette[ 2] = new Color { R = 0x20, G = 0x18, B = 0x60 };
palette[ 3] = new Color { R = 0x30, G = 0x20, B = 0x50 };
palette[ 4] = new Color { R = 0x40, G = 0x28, B = 0x40 };
palette[ 5] = new Color { R = 0x50, G = 0x30, B = 0x30 };
palette[ 6] = new Color { R = 0x60, G = 0x38, B = 0x20 };
palette[ 7] = new Color { R = 0x70, G = 0x40, B = 0x10 };
palette[ 8] = new Color { R = 0x80, G = 0x48, B = 0x0e };
palette[ 9] = new Color { R = 0x90, G = 0x50, B = 0x0c };
palette[10] = new Color { R = 0xa0, G = 0x58, B = 0x0a };
palette[11] = new Color { R = 0xb0, G = 0x60, B = 0x08 };
palette[12] = new Color { R = 0xc0, G = 0x68, B = 0x06 };
palette[13] = new Color { R = 0xd0, G = 0x70, B = 0x04 };
palette[14] = new Color { R = 0xe8, G = 0x78, B = 0x02 };
palette[15] = new Color { R = 0xff, G = 0x80, B = 0x01 };
return palette;
}
static WriteableBitmap DrawMandelbrot(IntRectangle canvasPosition)
{
var mandelbrot = new Mandelbrot(position: canvasPosition, backgroundColor: Colors.MidnightBlue);
mandelbrot.Draw(GetPalette());
return mandelbrot.Bitmap;
}
}
}
class Program
{
static void Main()
{
System.Console.WriteLine("Hello world!");
}
}
コマンドラインで、これをコンパイルしてみる。
C:\Source\Roslyn\Test>"C:\Source\Roslyn\Binaries\Debug\rcsc.exe" hello.cs
Microsoft (R) Visual C# Compiler version 0.7.0.0
Copyright (C) Microsoft Corporation. All rights reserved.
C:\Source\Roslyn\Test>"C:\Source\Roslyn\Binaries\Debug\rcsc.exe" hello.cs
Microsoft (R) Visual C# Compiler version 0.7.0.0
Copyright (C) Microsoft Corporation. All rights reserved.
以下では、あえて if を使って定数名を文字列に変換し、switch や Dictinary などを使った場合と処理時間を比較してみたい。
Sample クラス
先ず、225 個の const int を持つ Sample クラス。あえて enum は使っていない。
※ ぞっとするようなこのクラスや後述する else if の繰り返し, case の繰り返しのソースコードは、Enumerable.Range(1,225).ToList().ForEach(number => Console.WriteLine(適当なフォーマット文字列, number)) で作った。
public static class Sample
{
public const int CONST_001 = 1;
public const int CONST_002 = 2;
public const int CONST_003 = 3;
public const int CONST_004 = 4;
public const int CONST_005 = 5;
public const int CONST_006 = 6;
public const int CONST_007 = 7;
public const int CONST_008 = 8;
public const int CONST_009 = 9;
public const int CONST_010 = 10;
public const int CONST_011 = 11;
public const int CONST_012 = 12;
public const int CONST_013 = 13;
public const int CONST_014 = 14;
public const int CONST_015 = 15;
public const int CONST_016 = 16;
public const int CONST_017 = 17;
public const int CONST_018 = 18;
public const int CONST_019 = 19;
public const int CONST_020 = 20;
public const int CONST_021 = 21;
public const int CONST_022 = 22;
public const int CONST_023 = 23;
public const int CONST_024 = 24;
public const int CONST_025 = 25;
public const int CONST_026 = 26;
public const int CONST_027 = 27;
public const int CONST_028 = 28;
public const int CONST_029 = 29;
public const int CONST_030 = 30;
public const int CONST_031 = 31;
public const int CONST_032 = 32;
public const int CONST_033 = 33;
public const int CONST_034 = 34;
public const int CONST_035 = 35;
public const int CONST_036 = 36;
public const int CONST_037 = 37;
public const int CONST_038 = 38;
public const int CONST_039 = 39;
public const int CONST_040 = 40;
public const int CONST_041 = 41;
public const int CONST_042 = 42;
public const int CONST_043 = 43;
public const int CONST_044 = 44;
public const int CONST_045 = 45;
public const int CONST_046 = 46;
public const int CONST_047 = 47;
public const int CONST_048 = 48;
public const int CONST_049 = 49;
public const int CONST_050 = 50;
public const int CONST_051 = 51;
public const int CONST_052 = 52;
public const int CONST_053 = 53;
public const int CONST_054 = 54;
public const int CONST_055 = 55;
public const int CONST_056 = 56;
public const int CONST_057 = 57;
public const int CONST_058 = 58;
public const int CONST_059 = 59;
public const int CONST_060 = 60;
public const int CONST_061 = 61;
public const int CONST_062 = 62;
public const int CONST_063 = 63;
public const int CONST_064 = 64;
public const int CONST_065 = 65;
public const int CONST_066 = 66;
public const int CONST_067 = 67;
public const int CONST_068 = 68;
public const int CONST_069 = 69;
public const int CONST_070 = 70;
public const int CONST_071 = 71;
public const int CONST_072 = 72;
public const int CONST_073 = 73;
public const int CONST_074 = 74;
public const int CONST_075 = 75;
public const int CONST_076 = 76;
public const int CONST_077 = 77;
public const int CONST_078 = 78;
public const int CONST_079 = 79;
public const int CONST_080 = 80;
public const int CONST_081 = 81;
public const int CONST_082 = 82;
public const int CONST_083 = 83;
public const int CONST_084 = 84;
public const int CONST_085 = 85;
public const int CONST_086 = 86;
public const int CONST_087 = 87;
public const int CONST_088 = 88;
public const int CONST_089 = 89;
public const int CONST_090 = 90;
public const int CONST_091 = 91;
public const int CONST_092 = 92;
public const int CONST_093 = 93;
public const int CONST_094 = 94;
public const int CONST_095 = 95;
public const int CONST_096 = 96;
public const int CONST_097 = 97;
public const int CONST_098 = 98;
public const int CONST_099 = 99;
public const int CONST_100 = 100;
public const int CONST_101 = 101;
public const int CONST_102 = 102;
public const int CONST_103 = 103;
public const int CONST_104 = 104;
public const int CONST_105 = 105;
public const int CONST_106 = 106;
public const int CONST_107 = 107;
public const int CONST_108 = 108;
public const int CONST_109 = 109;
public const int CONST_110 = 110;
public const int CONST_111 = 111;
public const int CONST_112 = 112;
public const int CONST_113 = 113;
public const int CONST_114 = 114;
public const int CONST_115 = 115;
public const int CONST_116 = 116;
public const int CONST_117 = 117;
public const int CONST_118 = 118;
public const int CONST_119 = 119;
public const int CONST_120 = 120;
public const int CONST_121 = 121;
public const int CONST_122 = 122;
public const int CONST_123 = 123;
public const int CONST_124 = 124;
public const int CONST_125 = 125;
public const int CONST_126 = 126;
public const int CONST_127 = 127;
public const int CONST_128 = 128;
public const int CONST_129 = 129;
public const int CONST_130 = 130;
public const int CONST_131 = 131;
public const int CONST_132 = 132;
public const int CONST_133 = 133;
public const int CONST_134 = 134;
public const int CONST_135 = 135;
public const int CONST_136 = 136;
public const int CONST_137 = 137;
public const int CONST_138 = 138;
public const int CONST_139 = 139;
public const int CONST_140 = 140;
public const int CONST_141 = 141;
public const int CONST_142 = 142;
public const int CONST_143 = 143;
public const int CONST_144 = 144;
public const int CONST_145 = 145;
public const int CONST_146 = 146;
public const int CONST_147 = 147;
public const int CONST_148 = 148;
public const int CONST_149 = 149;
public const int CONST_150 = 150;
public const int CONST_151 = 151;
public const int CONST_152 = 152;
public const int CONST_153 = 153;
public const int CONST_154 = 154;
public const int CONST_155 = 155;
public const int CONST_156 = 156;
public const int CONST_157 = 157;
public const int CONST_158 = 158;
public const int CONST_159 = 159;
public const int CONST_160 = 160;
public const int CONST_161 = 161;
public const int CONST_162 = 162;
public const int CONST_163 = 163;
public const int CONST_164 = 164;
public const int CONST_165 = 165;
public const int CONST_166 = 166;
public const int CONST_167 = 167;
public const int CONST_168 = 168;
public const int CONST_169 = 169;
public const int CONST_170 = 170;
public const int CONST_171 = 171;
public const int CONST_172 = 172;
public const int CONST_173 = 173;
public const int CONST_174 = 174;
public const int CONST_175 = 175;
public const int CONST_176 = 176;
public const int CONST_177 = 177;
public const int CONST_178 = 178;
public const int CONST_179 = 179;
public const int CONST_180 = 180;
public const int CONST_181 = 181;
public const int CONST_182 = 182;
public const int CONST_183 = 183;
public const int CONST_184 = 184;
public const int CONST_185 = 185;
public const int CONST_186 = 186;
public const int CONST_187 = 187;
public const int CONST_188 = 188;
public const int CONST_189 = 189;
public const int CONST_190 = 190;
public const int CONST_191 = 191;
public const int CONST_192 = 192;
public const int CONST_193 = 193;
public const int CONST_194 = 194;
public const int CONST_195 = 195;
public const int CONST_196 = 196;
public const int CONST_197 = 197;
public const int CONST_198 = 198;
public const int CONST_199 = 199;
public const int CONST_200 = 200;
public const int CONST_201 = 201;
public const int CONST_202 = 202;
public const int CONST_203 = 203;
public const int CONST_204 = 204;
public const int CONST_205 = 205;
public const int CONST_206 = 206;
public const int CONST_207 = 207;
public const int CONST_208 = 208;
public const int CONST_209 = 209;
public const int CONST_210 = 210;
public const int CONST_211 = 211;
public const int CONST_212 = 212;
public const int CONST_213 = 213;
public const int CONST_214 = 214;
public const int CONST_215 = 215;
public const int CONST_216 = 216;
public const int CONST_217 = 217;
public const int CONST_218 = 218;
public const int CONST_219 = 219;
public const int CONST_220 = 220;
public const int CONST_221 = 221;
public const int CONST_222 = 222;
public const int CONST_223 = 223;
public const int CONST_224 = 224;
public const int CONST_225 = 225;
}
PerformanceTester クラス
次に、実行時間測定用に PerformanceTester というクラスを用意した。
using System;
using System.Diagnostics;
public static class PerformanceTester
{
static Stopwatch stopwatch = new Stopwatch();
public static double Test(Action action, long times)
{
stopwatch.Restart();
for (var counter = 0L; counter < times; counter++)
action();
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds / 1000.0;
}
}
Test メソッドは、「渡されたメソッドを使って1~255の値を定数名に変換する」処理を 100,000回実行する時間を測り、それを表示する。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
class IfPerformanceTestProgram
{
static string IfSample(int value)
{
string result;
if (value == Sample.CONST_001) result = "CONST_001";
else if (value == Sample.CONST_002) result = "CONST_002";
else if (value == Sample.CONST_003) result = "CONST_003";
else if (value == Sample.CONST_004) result = "CONST_004";
else if (value == Sample.CONST_005) result = "CONST_005";
else if (value == Sample.CONST_006) result = "CONST_006";
else if (value == Sample.CONST_007) result = "CONST_007";
else if (value == Sample.CONST_008) result = "CONST_008";
else if (value == Sample.CONST_009) result = "CONST_009";
else if (value == Sample.CONST_010) result = "CONST_010";
else if (value == Sample.CONST_011) result = "CONST_011";
else if (value == Sample.CONST_012) result = "CONST_012";
else if (value == Sample.CONST_013) result = "CONST_013";
else if (value == Sample.CONST_014) result = "CONST_014";
else if (value == Sample.CONST_015) result = "CONST_015";
else if (value == Sample.CONST_016) result = "CONST_016";
else if (value == Sample.CONST_017) result = "CONST_017";
else if (value == Sample.CONST_018) result = "CONST_018";
else if (value == Sample.CONST_019) result = "CONST_019";
else if (value == Sample.CONST_020) result = "CONST_020";
else if (value == Sample.CONST_021) result = "CONST_021";
else if (value == Sample.CONST_022) result = "CONST_022";
else if (value == Sample.CONST_023) result = "CONST_023";
else if (value == Sample.CONST_024) result = "CONST_024";
else if (value == Sample.CONST_025) result = "CONST_025";
else if (value == Sample.CONST_026) result = "CONST_026";
else if (value == Sample.CONST_027) result = "CONST_027";
else if (value == Sample.CONST_028) result = "CONST_028";
else if (value == Sample.CONST_029) result = "CONST_029";
else if (value == Sample.CONST_030) result = "CONST_030";
else if (value == Sample.CONST_031) result = "CONST_031";
else if (value == Sample.CONST_032) result = "CONST_032";
else if (value == Sample.CONST_033) result = "CONST_033";
else if (value == Sample.CONST_034) result = "CONST_034";
else if (value == Sample.CONST_035) result = "CONST_035";
else if (value == Sample.CONST_036) result = "CONST_036";
else if (value == Sample.CONST_037) result = "CONST_037";
else if (value == Sample.CONST_038) result = "CONST_038";
else if (value == Sample.CONST_039) result = "CONST_039";
else if (value == Sample.CONST_040) result = "CONST_040";
else if (value == Sample.CONST_041) result = "CONST_041";
else if (value == Sample.CONST_042) result = "CONST_042";
else if (value == Sample.CONST_043) result = "CONST_043";
else if (value == Sample.CONST_044) result = "CONST_044";
else if (value == Sample.CONST_045) result = "CONST_045";
else if (value == Sample.CONST_046) result = "CONST_046";
else if (value == Sample.CONST_047) result = "CONST_047";
else if (value == Sample.CONST_048) result = "CONST_048";
else if (value == Sample.CONST_049) result = "CONST_049";
else if (value == Sample.CONST_050) result = "CONST_050";
else if (value == Sample.CONST_051) result = "CONST_051";
else if (value == Sample.CONST_052) result = "CONST_052";
else if (value == Sample.CONST_053) result = "CONST_053";
else if (value == Sample.CONST_054) result = "CONST_054";
else if (value == Sample.CONST_055) result = "CONST_055";
else if (value == Sample.CONST_056) result = "CONST_056";
else if (value == Sample.CONST_057) result = "CONST_057";
else if (value == Sample.CONST_058) result = "CONST_058";
else if (value == Sample.CONST_059) result = "CONST_059";
else if (value == Sample.CONST_060) result = "CONST_060";
else if (value == Sample.CONST_061) result = "CONST_061";
else if (value == Sample.CONST_062) result = "CONST_062";
else if (value == Sample.CONST_063) result = "CONST_063";
else if (value == Sample.CONST_064) result = "CONST_064";
else if (value == Sample.CONST_065) result = "CONST_065";
else if (value == Sample.CONST_066) result = "CONST_066";
else if (value == Sample.CONST_067) result = "CONST_067";
else if (value == Sample.CONST_068) result = "CONST_068";
else if (value == Sample.CONST_069) result = "CONST_069";
else if (value == Sample.CONST_070) result = "CONST_070";
else if (value == Sample.CONST_071) result = "CONST_071";
else if (value == Sample.CONST_072) result = "CONST_072";
else if (value == Sample.CONST_073) result = "CONST_073";
else if (value == Sample.CONST_074) result = "CONST_074";
else if (value == Sample.CONST_075) result = "CONST_075";
else if (value == Sample.CONST_076) result = "CONST_076";
else if (value == Sample.CONST_077) result = "CONST_077";
else if (value == Sample.CONST_078) result = "CONST_078";
else if (value == Sample.CONST_079) result = "CONST_079";
else if (value == Sample.CONST_080) result = "CONST_080";
else if (value == Sample.CONST_081) result = "CONST_081";
else if (value == Sample.CONST_082) result = "CONST_082";
else if (value == Sample.CONST_083) result = "CONST_083";
else if (value == Sample.CONST_084) result = "CONST_084";
else if (value == Sample.CONST_085) result = "CONST_085";
else if (value == Sample.CONST_086) result = "CONST_086";
else if (value == Sample.CONST_087) result = "CONST_087";
else if (value == Sample.CONST_088) result = "CONST_088";
else if (value == Sample.CONST_089) result = "CONST_089";
else if (value == Sample.CONST_090) result = "CONST_090";
else if (value == Sample.CONST_091) result = "CONST_091";
else if (value == Sample.CONST_092) result = "CONST_092";
else if (value == Sample.CONST_093) result = "CONST_093";
else if (value == Sample.CONST_094) result = "CONST_094";
else if (value == Sample.CONST_095) result = "CONST_095";
else if (value == Sample.CONST_096) result = "CONST_096";
else if (value == Sample.CONST_097) result = "CONST_097";
else if (value == Sample.CONST_098) result = "CONST_098";
else if (value == Sample.CONST_099) result = "CONST_099";
else if (value == Sample.CONST_100) result = "CONST_100";
else if (value == Sample.CONST_101) result = "CONST_101";
else if (value == Sample.CONST_102) result = "CONST_102";
else if (value == Sample.CONST_103) result = "CONST_103";
else if (value == Sample.CONST_104) result = "CONST_104";
else if (value == Sample.CONST_105) result = "CONST_105";
else if (value == Sample.CONST_106) result = "CONST_106";
else if (value == Sample.CONST_107) result = "CONST_107";
else if (value == Sample.CONST_108) result = "CONST_108";
else if (value == Sample.CONST_109) result = "CONST_109";
else if (value == Sample.CONST_110) result = "CONST_110";
else if (value == Sample.CONST_111) result = "CONST_111";
else if (value == Sample.CONST_112) result = "CONST_112";
else if (value == Sample.CONST_113) result = "CONST_113";
else if (value == Sample.CONST_114) result = "CONST_114";
else if (value == Sample.CONST_115) result = "CONST_115";
else if (value == Sample.CONST_116) result = "CONST_116";
else if (value == Sample.CONST_117) result = "CONST_117";
else if (value == Sample.CONST_118) result = "CONST_118";
else if (value == Sample.CONST_119) result = "CONST_119";
else if (value == Sample.CONST_120) result = "CONST_120";
else if (value == Sample.CONST_121) result = "CONST_121";
else if (value == Sample.CONST_122) result = "CONST_122";
else if (value == Sample.CONST_123) result = "CONST_123";
else if (value == Sample.CONST_124) result = "CONST_124";
else if (value == Sample.CONST_125) result = "CONST_125";
else if (value == Sample.CONST_126) result = "CONST_126";
else if (value == Sample.CONST_127) result = "CONST_127";
else if (value == Sample.CONST_128) result = "CONST_128";
else if (value == Sample.CONST_129) result = "CONST_129";
else if (value == Sample.CONST_130) result = "CONST_130";
else if (value == Sample.CONST_131) result = "CONST_131";
else if (value == Sample.CONST_132) result = "CONST_132";
else if (value == Sample.CONST_133) result = "CONST_133";
else if (value == Sample.CONST_134) result = "CONST_134";
else if (value == Sample.CONST_135) result = "CONST_135";
else if (value == Sample.CONST_136) result = "CONST_136";
else if (value == Sample.CONST_137) result = "CONST_137";
else if (value == Sample.CONST_138) result = "CONST_138";
else if (value == Sample.CONST_139) result = "CONST_139";
else if (value == Sample.CONST_140) result = "CONST_140";
else if (value == Sample.CONST_141) result = "CONST_141";
else if (value == Sample.CONST_142) result = "CONST_142";
else if (value == Sample.CONST_143) result = "CONST_143";
else if (value == Sample.CONST_144) result = "CONST_144";
else if (value == Sample.CONST_145) result = "CONST_145";
else if (value == Sample.CONST_146) result = "CONST_146";
else if (value == Sample.CONST_147) result = "CONST_147";
else if (value == Sample.CONST_148) result = "CONST_148";
else if (value == Sample.CONST_149) result = "CONST_149";
else if (value == Sample.CONST_150) result = "CONST_150";
else if (value == Sample.CONST_151) result = "CONST_151";
else if (value == Sample.CONST_152) result = "CONST_152";
else if (value == Sample.CONST_153) result = "CONST_153";
else if (value == Sample.CONST_154) result = "CONST_154";
else if (value == Sample.CONST_155) result = "CONST_155";
else if (value == Sample.CONST_156) result = "CONST_156";
else if (value == Sample.CONST_157) result = "CONST_157";
else if (value == Sample.CONST_158) result = "CONST_158";
else if (value == Sample.CONST_159) result = "CONST_159";
else if (value == Sample.CONST_160) result = "CONST_160";
else if (value == Sample.CONST_161) result = "CONST_161";
else if (value == Sample.CONST_162) result = "CONST_162";
else if (value == Sample.CONST_163) result = "CONST_163";
else if (value == Sample.CONST_164) result = "CONST_164";
else if (value == Sample.CONST_165) result = "CONST_165";
else if (value == Sample.CONST_166) result = "CONST_166";
else if (value == Sample.CONST_167) result = "CONST_167";
else if (value == Sample.CONST_168) result = "CONST_168";
else if (value == Sample.CONST_169) result = "CONST_169";
else if (value == Sample.CONST_170) result = "CONST_170";
else if (value == Sample.CONST_171) result = "CONST_171";
else if (value == Sample.CONST_172) result = "CONST_172";
else if (value == Sample.CONST_173) result = "CONST_173";
else if (value == Sample.CONST_174) result = "CONST_174";
else if (value == Sample.CONST_175) result = "CONST_175";
else if (value == Sample.CONST_176) result = "CONST_176";
else if (value == Sample.CONST_177) result = "CONST_177";
else if (value == Sample.CONST_178) result = "CONST_178";
else if (value == Sample.CONST_179) result = "CONST_179";
else if (value == Sample.CONST_180) result = "CONST_180";
else if (value == Sample.CONST_181) result = "CONST_181";
else if (value == Sample.CONST_182) result = "CONST_182";
else if (value == Sample.CONST_183) result = "CONST_183";
else if (value == Sample.CONST_184) result = "CONST_184";
else if (value == Sample.CONST_185) result = "CONST_185";
else if (value == Sample.CONST_186) result = "CONST_186";
else if (value == Sample.CONST_187) result = "CONST_187";
else if (value == Sample.CONST_188) result = "CONST_188";
else if (value == Sample.CONST_189) result = "CONST_189";
else if (value == Sample.CONST_190) result = "CONST_190";
else if (value == Sample.CONST_191) result = "CONST_191";
else if (value == Sample.CONST_192) result = "CONST_192";
else if (value == Sample.CONST_193) result = "CONST_193";
else if (value == Sample.CONST_194) result = "CONST_194";
else if (value == Sample.CONST_195) result = "CONST_195";
else if (value == Sample.CONST_196) result = "CONST_196";
else if (value == Sample.CONST_197) result = "CONST_197";
else if (value == Sample.CONST_198) result = "CONST_198";
else if (value == Sample.CONST_199) result = "CONST_199";
else if (value == Sample.CONST_200) result = "CONST_200";
else if (value == Sample.CONST_201) result = "CONST_201";
else if (value == Sample.CONST_202) result = "CONST_202";
else if (value == Sample.CONST_203) result = "CONST_203";
else if (value == Sample.CONST_204) result = "CONST_204";
else if (value == Sample.CONST_205) result = "CONST_205";
else if (value == Sample.CONST_206) result = "CONST_206";
else if (value == Sample.CONST_207) result = "CONST_207";
else if (value == Sample.CONST_208) result = "CONST_208";
else if (value == Sample.CONST_209) result = "CONST_209";
else if (value == Sample.CONST_210) result = "CONST_210";
else if (value == Sample.CONST_211) result = "CONST_211";
else if (value == Sample.CONST_212) result = "CONST_212";
else if (value == Sample.CONST_213) result = "CONST_213";
else if (value == Sample.CONST_214) result = "CONST_214";
else if (value == Sample.CONST_215) result = "CONST_215";
else if (value == Sample.CONST_216) result = "CONST_216";
else if (value == Sample.CONST_217) result = "CONST_217";
else if (value == Sample.CONST_218) result = "CONST_218";
else if (value == Sample.CONST_219) result = "CONST_219";
else if (value == Sample.CONST_220) result = "CONST_220";
else if (value == Sample.CONST_221) result = "CONST_221";
else if (value == Sample.CONST_222) result = "CONST_222";
else if (value == Sample.CONST_223) result = "CONST_223";
else if (value == Sample.CONST_224) result = "CONST_224";
else if (value == Sample.CONST_225) result = "CONST_225";
else result = "" ;
return result;
}
static string SwitchSample(int value)
{
string result;
switch (value) {
case Sample.CONST_001: result = "CONST_001"; break;
case Sample.CONST_002: result = "CONST_002"; break;
case Sample.CONST_003: result = "CONST_003"; break;
case Sample.CONST_004: result = "CONST_004"; break;
case Sample.CONST_005: result = "CONST_005"; break;
case Sample.CONST_006: result = "CONST_006"; break;
case Sample.CONST_007: result = "CONST_007"; break;
case Sample.CONST_008: result = "CONST_008"; break;
case Sample.CONST_009: result = "CONST_009"; break;
case Sample.CONST_010: result = "CONST_010"; break;
case Sample.CONST_011: result = "CONST_011"; break;
case Sample.CONST_012: result = "CONST_012"; break;
case Sample.CONST_013: result = "CONST_013"; break;
case Sample.CONST_014: result = "CONST_014"; break;
case Sample.CONST_015: result = "CONST_015"; break;
case Sample.CONST_016: result = "CONST_016"; break;
case Sample.CONST_017: result = "CONST_017"; break;
case Sample.CONST_018: result = "CONST_018"; break;
case Sample.CONST_019: result = "CONST_019"; break;
case Sample.CONST_020: result = "CONST_020"; break;
case Sample.CONST_021: result = "CONST_021"; break;
case Sample.CONST_022: result = "CONST_022"; break;
case Sample.CONST_023: result = "CONST_023"; break;
case Sample.CONST_024: result = "CONST_024"; break;
case Sample.CONST_025: result = "CONST_025"; break;
case Sample.CONST_026: result = "CONST_026"; break;
case Sample.CONST_027: result = "CONST_027"; break;
case Sample.CONST_028: result = "CONST_028"; break;
case Sample.CONST_029: result = "CONST_029"; break;
case Sample.CONST_030: result = "CONST_030"; break;
case Sample.CONST_031: result = "CONST_031"; break;
case Sample.CONST_032: result = "CONST_032"; break;
case Sample.CONST_033: result = "CONST_033"; break;
case Sample.CONST_034: result = "CONST_034"; break;
case Sample.CONST_035: result = "CONST_035"; break;
case Sample.CONST_036: result = "CONST_036"; break;
case Sample.CONST_037: result = "CONST_037"; break;
case Sample.CONST_038: result = "CONST_038"; break;
case Sample.CONST_039: result = "CONST_039"; break;
case Sample.CONST_040: result = "CONST_040"; break;
case Sample.CONST_041: result = "CONST_041"; break;
case Sample.CONST_042: result = "CONST_042"; break;
case Sample.CONST_043: result = "CONST_043"; break;
case Sample.CONST_044: result = "CONST_044"; break;
case Sample.CONST_045: result = "CONST_045"; break;
case Sample.CONST_046: result = "CONST_046"; break;
case Sample.CONST_047: result = "CONST_047"; break;
case Sample.CONST_048: result = "CONST_048"; break;
case Sample.CONST_049: result = "CONST_049"; break;
case Sample.CONST_050: result = "CONST_050"; break;
case Sample.CONST_051: result = "CONST_051"; break;
case Sample.CONST_052: result = "CONST_052"; break;
case Sample.CONST_053: result = "CONST_053"; break;
case Sample.CONST_054: result = "CONST_054"; break;
case Sample.CONST_055: result = "CONST_055"; break;
case Sample.CONST_056: result = "CONST_056"; break;
case Sample.CONST_057: result = "CONST_057"; break;
case Sample.CONST_058: result = "CONST_058"; break;
case Sample.CONST_059: result = "CONST_059"; break;
case Sample.CONST_060: result = "CONST_060"; break;
case Sample.CONST_061: result = "CONST_061"; break;
case Sample.CONST_062: result = "CONST_062"; break;
case Sample.CONST_063: result = "CONST_063"; break;
case Sample.CONST_064: result = "CONST_064"; break;
case Sample.CONST_065: result = "CONST_065"; break;
case Sample.CONST_066: result = "CONST_066"; break;
case Sample.CONST_067: result = "CONST_067"; break;
case Sample.CONST_068: result = "CONST_068"; break;
case Sample.CONST_069: result = "CONST_069"; break;
case Sample.CONST_070: result = "CONST_070"; break;
case Sample.CONST_071: result = "CONST_071"; break;
case Sample.CONST_072: result = "CONST_072"; break;
case Sample.CONST_073: result = "CONST_073"; break;
case Sample.CONST_074: result = "CONST_074"; break;
case Sample.CONST_075: result = "CONST_075"; break;
case Sample.CONST_076: result = "CONST_076"; break;
case Sample.CONST_077: result = "CONST_077"; break;
case Sample.CONST_078: result = "CONST_078"; break;
case Sample.CONST_079: result = "CONST_079"; break;
case Sample.CONST_080: result = "CONST_080"; break;
case Sample.CONST_081: result = "CONST_081"; break;
case Sample.CONST_082: result = "CONST_082"; break;
case Sample.CONST_083: result = "CONST_083"; break;
case Sample.CONST_084: result = "CONST_084"; break;
case Sample.CONST_085: result = "CONST_085"; break;
case Sample.CONST_086: result = "CONST_086"; break;
case Sample.CONST_087: result = "CONST_087"; break;
case Sample.CONST_088: result = "CONST_088"; break;
case Sample.CONST_089: result = "CONST_089"; break;
case Sample.CONST_090: result = "CONST_090"; break;
case Sample.CONST_091: result = "CONST_091"; break;
case Sample.CONST_092: result = "CONST_092"; break;
case Sample.CONST_093: result = "CONST_093"; break;
case Sample.CONST_094: result = "CONST_094"; break;
case Sample.CONST_095: result = "CONST_095"; break;
case Sample.CONST_096: result = "CONST_096"; break;
case Sample.CONST_097: result = "CONST_097"; break;
case Sample.CONST_098: result = "CONST_098"; break;
case Sample.CONST_099: result = "CONST_099"; break;
case Sample.CONST_100: result = "CONST_100"; break;
case Sample.CONST_101: result = "CONST_101"; break;
case Sample.CONST_102: result = "CONST_102"; break;
case Sample.CONST_103: result = "CONST_103"; break;
case Sample.CONST_104: result = "CONST_104"; break;
case Sample.CONST_105: result = "CONST_105"; break;
case Sample.CONST_106: result = "CONST_106"; break;
case Sample.CONST_107: result = "CONST_107"; break;
case Sample.CONST_108: result = "CONST_108"; break;
case Sample.CONST_109: result = "CONST_109"; break;
case Sample.CONST_110: result = "CONST_110"; break;
case Sample.CONST_111: result = "CONST_111"; break;
case Sample.CONST_112: result = "CONST_112"; break;
case Sample.CONST_113: result = "CONST_113"; break;
case Sample.CONST_114: result = "CONST_114"; break;
case Sample.CONST_115: result = "CONST_115"; break;
case Sample.CONST_116: result = "CONST_116"; break;
case Sample.CONST_117: result = "CONST_117"; break;
case Sample.CONST_118: result = "CONST_118"; break;
case Sample.CONST_119: result = "CONST_119"; break;
case Sample.CONST_120: result = "CONST_120"; break;
case Sample.CONST_121: result = "CONST_121"; break;
case Sample.CONST_122: result = "CONST_122"; break;
case Sample.CONST_123: result = "CONST_123"; break;
case Sample.CONST_124: result = "CONST_124"; break;
case Sample.CONST_125: result = "CONST_125"; break;
case Sample.CONST_126: result = "CONST_126"; break;
case Sample.CONST_127: result = "CONST_127"; break;
case Sample.CONST_128: result = "CONST_128"; break;
case Sample.CONST_129: result = "CONST_129"; break;
case Sample.CONST_130: result = "CONST_130"; break;
case Sample.CONST_131: result = "CONST_131"; break;
case Sample.CONST_132: result = "CONST_132"; break;
case Sample.CONST_133: result = "CONST_133"; break;
case Sample.CONST_134: result = "CONST_134"; break;
case Sample.CONST_135: result = "CONST_135"; break;
case Sample.CONST_136: result = "CONST_136"; break;
case Sample.CONST_137: result = "CONST_137"; break;
case Sample.CONST_138: result = "CONST_138"; break;
case Sample.CONST_139: result = "CONST_139"; break;
case Sample.CONST_140: result = "CONST_140"; break;
case Sample.CONST_141: result = "CONST_141"; break;
case Sample.CONST_142: result = "CONST_142"; break;
case Sample.CONST_143: result = "CONST_143"; break;
case Sample.CONST_144: result = "CONST_144"; break;
case Sample.CONST_145: result = "CONST_145"; break;
case Sample.CONST_146: result = "CONST_146"; break;
case Sample.CONST_147: result = "CONST_147"; break;
case Sample.CONST_148: result = "CONST_148"; break;
case Sample.CONST_149: result = "CONST_149"; break;
case Sample.CONST_150: result = "CONST_150"; break;
case Sample.CONST_151: result = "CONST_151"; break;
case Sample.CONST_152: result = "CONST_152"; break;
case Sample.CONST_153: result = "CONST_153"; break;
case Sample.CONST_154: result = "CONST_154"; break;
case Sample.CONST_155: result = "CONST_155"; break;
case Sample.CONST_156: result = "CONST_156"; break;
case Sample.CONST_157: result = "CONST_157"; break;
case Sample.CONST_158: result = "CONST_158"; break;
case Sample.CONST_159: result = "CONST_159"; break;
case Sample.CONST_160: result = "CONST_160"; break;
case Sample.CONST_161: result = "CONST_161"; break;
case Sample.CONST_162: result = "CONST_162"; break;
case Sample.CONST_163: result = "CONST_163"; break;
case Sample.CONST_164: result = "CONST_164"; break;
case Sample.CONST_165: result = "CONST_165"; break;
case Sample.CONST_166: result = "CONST_166"; break;
case Sample.CONST_167: result = "CONST_167"; break;
case Sample.CONST_168: result = "CONST_168"; break;
case Sample.CONST_169: result = "CONST_169"; break;
case Sample.CONST_170: result = "CONST_170"; break;
case Sample.CONST_171: result = "CONST_171"; break;
case Sample.CONST_172: result = "CONST_172"; break;
case Sample.CONST_173: result = "CONST_173"; break;
case Sample.CONST_174: result = "CONST_174"; break;
case Sample.CONST_175: result = "CONST_175"; break;
case Sample.CONST_176: result = "CONST_176"; break;
case Sample.CONST_177: result = "CONST_177"; break;
case Sample.CONST_178: result = "CONST_178"; break;
case Sample.CONST_179: result = "CONST_179"; break;
case Sample.CONST_180: result = "CONST_180"; break;
case Sample.CONST_181: result = "CONST_181"; break;
case Sample.CONST_182: result = "CONST_182"; break;
case Sample.CONST_183: result = "CONST_183"; break;
case Sample.CONST_184: result = "CONST_184"; break;
case Sample.CONST_185: result = "CONST_185"; break;
case Sample.CONST_186: result = "CONST_186"; break;
case Sample.CONST_187: result = "CONST_187"; break;
case Sample.CONST_188: result = "CONST_188"; break;
case Sample.CONST_189: result = "CONST_189"; break;
case Sample.CONST_190: result = "CONST_190"; break;
case Sample.CONST_191: result = "CONST_191"; break;
case Sample.CONST_192: result = "CONST_192"; break;
case Sample.CONST_193: result = "CONST_193"; break;
case Sample.CONST_194: result = "CONST_194"; break;
case Sample.CONST_195: result = "CONST_195"; break;
case Sample.CONST_196: result = "CONST_196"; break;
case Sample.CONST_197: result = "CONST_197"; break;
case Sample.CONST_198: result = "CONST_198"; break;
case Sample.CONST_199: result = "CONST_199"; break;
case Sample.CONST_200: result = "CONST_200"; break;
case Sample.CONST_201: result = "CONST_201"; break;
case Sample.CONST_202: result = "CONST_202"; break;
case Sample.CONST_203: result = "CONST_203"; break;
case Sample.CONST_204: result = "CONST_204"; break;
case Sample.CONST_205: result = "CONST_205"; break;
case Sample.CONST_206: result = "CONST_206"; break;
case Sample.CONST_207: result = "CONST_207"; break;
case Sample.CONST_208: result = "CONST_208"; break;
case Sample.CONST_209: result = "CONST_209"; break;
case Sample.CONST_210: result = "CONST_210"; break;
case Sample.CONST_211: result = "CONST_211"; break;
case Sample.CONST_212: result = "CONST_212"; break;
case Sample.CONST_213: result = "CONST_213"; break;
case Sample.CONST_214: result = "CONST_214"; break;
case Sample.CONST_215: result = "CONST_215"; break;
case Sample.CONST_216: result = "CONST_216"; break;
case Sample.CONST_217: result = "CONST_217"; break;
case Sample.CONST_218: result = "CONST_218"; break;
case Sample.CONST_219: result = "CONST_219"; break;
case Sample.CONST_220: result = "CONST_220"; break;
case Sample.CONST_221: result = "CONST_221"; break;
case Sample.CONST_222: result = "CONST_222"; break;
case Sample.CONST_223: result = "CONST_223"; break;
case Sample.CONST_224: result = "CONST_224"; break;
case Sample.CONST_225: result = "CONST_225"; break;
default : result = "" ; break;
}
return result;
}
static class ConstantsTable
{
static Dictionary<int, string> constantsDictionary = new Dictionary<int, string>();
static ConstantsTable()
{
var fields = typeof(Sample)
.GetFields(BindingFlags.Public | BindingFlags.Static);
fields.Where(field => field.IsLiteral && field.FieldType.Equals(typeof(int)))
.ToList()
.ForEach(field => constantsDictionary.Add((int)field.GetValue(null), field.Name));
}
public static string GetConstantName(int value)
{
string name;
return constantsDictionary.TryGetValue(value, out name) ? name : "";
}
}
static string ReflectionAndDictionarySample(int value)
{
return ConstantsTable.GetConstantName(value);
}
static void Test(Func<int, string> sampleFunction, string sampleName)
{
const long times = 100000L;
string result;
var time = PerformanceTester.Test(
() => Enumerable
.Range(1, 225)
.ToList()
.ForEach(number => result = sampleFunction(number)),
times
);
Console.WriteLine("{0}の場合\t: {1:F3}/{2}sec.", sampleName, time, times);
}
static void Main()
{
Test(IfSample , "if" );
Test(SwitchSample , "switch" );
Test(ReflectionAndDictionarySample, "ReflectionとDictionary");
}
}
// QueryableTweets.cs
using System.Linq;
// IOrderedQueryable<string> な QueryableTweets
// まだ IOrderedQueryable インタフェイスを実装してないのでコンパイル エラー
public class QueryableTweets<TElement> : IOrderedQueryable<TElement>
{}
ここから、Visual Studio でインタフェイスの実装を行うと次のようになる。
// QueryableTweets.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
// IOrderedQueryable<string> な QueryableTweets
// Visual Studio でインタフェイスを実装した直後
public class QueryableTweets<TElement> : IOrderedQueryable<TElement>
{
public Type ElementType
{
get { throw new NotImplementedException(); }
}
public Expression Expression
{
get { throw new NotImplementedException(); }
}
public IQueryProvider Provider
{
get { throw new NotImplementedException(); }
}
public IEnumerator<TElement> GetEnumerator()
{ throw new NotImplementedException(); }
IEnumerator IEnumerable.GetEnumerator()
{ throw new NotImplementedException(); }
}
// QueryableTweets.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
// IOrderedQueryable<string> な QueryableTweets
public class QueryableTweets<TElement> : IOrderedQueryable<TElement>
{
public Type ElementType
{
get { return typeof(TElement); }
}
public Expression Expression { get; set; }
public IQueryProvider Provider { get; set; }
public QueryableTweets()
{
Provider = new TwitterQueryProvider(); // IQueryProvider インタフェイスを実装したクラス。後述。
Expression = Expression.Constant(this);
}
public IEnumerator<TElement> GetEnumerator()
{ return ((IEnumerable<TElement>)Provider.Execute(Expression)).GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
}
// TwitterQueryProvider.cs
using System.Linq;
// LINQ プロバイダーの実験用
// まだ IQueryProvider インタフェイスを実装してないのでコンパイル エラー
public class TwitterQueryProvider : IQueryProvider
{}
ここから、Visual Studio でインタフェイスの実装を行うと次のようになる。
// TwitterQueryProvider.cs
using System;
using System.Linq;
using System.Linq.Expressions;
// LINQ プロバイダーの実験用
// Visual Studio でインタフェイスを実装した直後
public class TwitterQueryProvider : IQueryProvider
{
public IQueryable CreateQuery(Expression expression)
{ throw new NotImplementedException(); }
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{ throw new NotImplementedException(); }
public object Execute(Expression expression)
{ throw new NotImplementedException(); }
public TResult Execute<TResult>(Expression expression)
{ throw new NotImplementedException(); }
}
少し実装を進めてみる。
// TwitterQueryProvider.cs
using System;
using System.Linq;
using System.Linq.Expressions;
// LINQ プロバイダーの実験用
public class TwitterQueryProvider : IQueryProvider
{
IQueryable IQueryProvider.CreateQuery(Expression expression)
{ return null; }
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{ return new QueryableTweets<TElement> { Provider = this, Expression = expression }; }
public TResult Execute<TResult>(Expression expression)
{ return default(TResult); }
public object Execute(Expression expression)
{
// ここで式木を解釈して、コレクションを作って返す
return null; // とりあえずは仮に null を返すだけにしておく
}
}
Microsoft Virtual Academy (MVA) に登録。
Microsoft Virtual Academy (MVA) の会員登録は無料です。 登録すると Microsoft Virtual Academy
(MVA) に掲載されているすべての授業動画や資料を閲覧できます。
さらに学習に便利な学習履歴の閲覧や、受講証の発行、学習ランキングを使用することができます。
マイクロソフトアカウントがある方はマイクロソフトアカウントでサインイン、ない方は「Sign up now」 からアカウントを作成してください。
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Shos.Collections.Test
{
[TestClass]
public class Stackのテスト
{
[TestMethod]
public void 作ったら空()
{
var stack = new Stack<int>();
Assert.IsTrue(stack.IsEmpty);
}
}
}
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);
}
}
この記事では、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 メソッドを使っているが、この時点ではどこにも実装がない。
// 何か 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();
}
@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
『PredictStockPrice-AI-decode』 | GitHub
PredictStockPrice Sample for Microsoft de:code 2018 AI sessions
Microsoft Azure Machine Learning Studio による株価予想プログラム
吉島良平(室長) 氏 @navisionaxapta
Pacific Business Consulting,Inc. 取締役/戦略事業推進室、Microsoft MVP for Business Solutions 2015-2017、Microsoft MVP for Business Applications 2018
杉本 和也 氏 @sugimomoto
CData Software Japan Lead Engineer、Microsoft MVP for Business Solutions 2017-2019
The csv format file is sometimes necessary because it can be displayed / edited with Excel and is simple.
There are other libraries that read and write csv already, but I tried to make it simpler.
With this library, you can read and write plain object collections (IEnumerable<Something>) in csv format.
How to use
Overview of how to use
First, prepare something IEnumerable<TElement>:
An IEnumerable<TElement> something
IEnumerable<ToDo> toDoes = new ToDoList();
For example this class:
class ToDoList : IEnumerable<ToDo> // sample collection
{
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();
}
Public properties with both "get" and "set" of each element in the collection are read and written to csv files.
When writing:
When writing, it is converted to a string with the "ToString()" method regardless of the type.
When reading:
When reading, the string type is left as is, and enum (enumeration type) is read as the value of the string.
For other types, it tries to change the string to a value using "TryParse" or "Parse".
Types that cannot do either of these will not be read.
Properties that have both "get" and "set" and are of one of the following types are read:
String type
enum
A type that has a default constructor and can be "TryParse" or "Parse"
(Basic numeric types such as int, DateTime, user-defined types with "TryParse" or "Parse")
Other rules
Properties with the [CsvIgnore ()] attribute are not read or written.
Properties with the [ColumnName("Column name")] attribute will be changed to the one with the specified column name in the csv file.
For example, the above ToDo class is like this:
// Sample element type
// Properties marked with ✔ will be read / written
// Properties marked with ✖ won't be read / written
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; // ✔ user-defined enum
[ColumnName("Details")]
public string Detail { get; set; } = ""; // ✔ [ColumnName ("Details")] changes the column name in the csv file to "Details"
public DaySpan DaySpan { get; set; } // ✔ User-defined type with "Parse" (without "TryParse")
[CsvIgnore()]
public string Option { get; set; } = ""; // ✖ Ignored because [CsvIgnore()] is attached
public string Version => "1.0"; // ✖ Ignored because it is only a get property
}
The user-defined types used in the above ToDo class are as follows:
// User-defined enum example
enum Priority { High, Middle, Low }
// Example of a user-defined type with "Parse" (without "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();
}
With or without header
csv can be read and written even if it has no header part (eg the first line of the csv file above).
However, if there is a header part, it can be read by collating the header part even if the column is switched, but if there is no header part, it cannot be read if the column is switched.