DDDProjector - Home
フレームワークと言っても、クラスライブラリとしてではなく、C# ソリューション (を作成するツール) として提供します。なので、フレームワークのソースコードを直接カスタマイズすることが可能です。
サンプルアプリではデータストアに SQL CE 4.0、プレゼンテーション技術に Windows フォームを使用していましたが、このフレームワークでは特定のデータストア・プレゼンテーション技術には依存していません。
ツールを起動すると、ソリューション名や各プロジェクト名・名前空間の入力フォームが表示されます。
各フィールドを入力後、作成ボタンを押すと保存先選択ダイアログが出てきます。保存先を決定するとそこにソリューションが作成されます。
なお、入力値の妥当性検証は一切行っていないので注意してください。
これから少しずつ解説記事を公開していこうと思います。
Sandcastle で作った、アプリケーションアーキテクチャに関する説明ドキュメントも同梱してあります。
# ユニットテストは用意していません。
DDD サンプル - ToDoList - Home
- 列挙型のプロパティをマッピングできない
- クエリー式で、列挙値が扱えない (int として扱わなければいけない)
- クエリー式で、エンティティを直接比較できない (主キー等のプロパティを比較しなければいけない)
- コレクションの順序を保存できない (順序を自分で管理する必要がある)
- 関連オブジェクトの読み込みが非透過的
- 関連オブジェクトの遅延読み込みが使いづらい (コレクションだけ遅延読み込みにするとかできない。そのためプロキシ作成を容認しなければいけない)
- NHibernate の cascade=all-delete-orphan に相当する機能がない (集約内の子エンティティの削除はパーシステンスレイヤへ明示的に指示せずにできるべき)
Entity Framework 4.1 は試してないけど、この辺はそのままっぽい感じ
2番目のはロジックで弾きだした値を列挙型で扱おうとしてダメだった
最後のだけは何とかならんものか…
ファクトリだと、集約ルートの生成とそれ以外のエンティティの生成はアトミックに扱えないけどビルダならアトミックに扱えますし、構築中は不正な状態も扱えます。
また、構築に関するロジックがエンティティに一切なくなります。
ビルダは特に、UI への入力直後に入力チェックする場合や、ウィザード形式で入力を進めていく場合なんかに本領発揮します。
Person.cs
public sealed partial class Person
{
private Person(string name, IList<Address> addresses)
{
_name = name;
_addresses = addresses;
}
private string _name;
private readonly IList<Address> _addresses;
public string Name
{
get { return _name; }
}
public IEnumerable<Address> Addresses
{
get { return _addresses; }
}
}
Address.cs
public sealed class partial Address
{
...
}
Person.Builder.cs
partial class Person
{
public sealed class Builder
{
[Flags]
public enum States
{
Valid = 0,
NameIsEmpty = 1,
NameIsTooLong = 2,
AddressListIsEmpty = 4,
AddressListHasError = 8,
}
public static readonly int MaxNameLength = 20;
public Builder()
{
Name = null;
AddressBuilders = new List<Address.Builder>();
}
public Builder(Person target)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
Name = target.Name;
AddressBuilders = target._addresses.Select(address => new Address.Builder(address)).ToList();
}
private readonly Person _target;
public string Name;
public readonly List<Address.Builder> AddressBuilders;
public Address.Builder AddAddress()
{
var addressBuilder = new Address.Builder();
AddressBuilders.Add(addressBuilder);
return addressBuilder;
}
public void RemoveAddress(int index)
{
AddressBuilders.RemoveAt(index);
}
public States Validate()
{
return (ValidateName() | ValidateAddresses());
}
public States ValidateName()
{
if (string.IsNullOrWhitespace(Name))
{
return States.NameIsEmpty;
}
if (MaxNameLength < Name.Length)
{
return States.NameIsTooLong;
}
return States.Valid;
}
public States ValidateAddresses()
{
States result = States.Valid;
if (AddressBuilders.Length == 0)
{
result |= States.AddressListIsEmpty;
}
foreach (var addressBuilder in AddressBuilders)
{
if (addressBuilder.Validate() != Address.Builder.States.Valid)
{
result |= States.AddressListHasError;
break;
}
}
return result;
}
public Person Build()
{
if (Validate() != States.Valid)
{
throw new InvalidOperationException();
}
var addresses = AddressBuilders.Select(builder => builder.Build()).ToList();
if (_target == null)
{
return new Person(Name, addresses);
}
else
{
_target._name = Name;
_target._addresses.Clear();
foreach (var address in addresses)
{
_target._addresses.Add(address);
}
return _target;
}
}
}
}
Address.Builder.cs
partial class Address
{
public sealed class Builder
{
[Flags]
public enum States
{
...
}
internal AddressBuilder() { ... }
public States Validate() { ... }
internal Address Build() { ... }
}
}
まだ色々思案中でこれが本当に良い案なのかどうか確信は持ててないのですが。。。
[関連書籍]
ついにエリック・エヴァンスの "Domain-Driven Design" の日本語版が出版されますよ
C#と諸々 ローカル変数に参照渡し
ローカル変数に参照渡しはできないけれど、参照を扱うことはできるということに気付きました。
実は C# には隠しキーワードがいくつかあって、その内の __makeref と __refvalue を使うことで参照を扱うことができます。
using System;
class Program
{
static void Main()
{
int v0 = 0;
TypedReference r0 = __makeref(v0);
__refvalue(r0, int) = 5;
Console.WriteLine(v0); //=> 5
}
}
SpecialNameAttribute クラス (System.Runtime.CompilerServices)
最近見っけた。
これ使うとメソッドやフィールドに IL レベルで specialname 修飾子を付けれる。
using System;
using System.Runtime.CompilerServices;
class Hoge
{
public Hoge(string value)
{
_value = value;
}
private string _value;
[SpecialName]
public static string op_Explicit(Hoge obj)
{
return obj._value;
}
}
こうすれば普通のメソッド定義と同じ形式で演算子のオーバーロードができる。
けど、同一プロジェクト内からは演算子として呼び出すことはできなくて、静的メソッドとして呼び出す必要あり。
これを使って何か面白いことできないかなーとか考えてたんだけど・・・無い!
.ctor なんて名前のメソッドは定義できないからコンストラクタは作れないし、get_XXX とか set_XXX とか add_XXX とか remove_XXX なんてメソッド作ったところでプロパティやイベントになるわけでもなし。まぁできたところで・・・何も面白くない。
interface との変換演算子とかジェネリックな変換演算子なんて作ってみたけどコンパイラが認識しないから実行されず。
強いて言えば C++/CLI で使える非静的な演算子を用意したりできるけど・・・無意味すぎ。
struct Hoge
{
public void Clear()
{
this = new Hoge();
}
}
ちょっと驚きましたが、よくよく考えると出来て当然ですね。
「C#できます」と言い放ったあいつがJavaプログラマであることを見分ける10の質問 - 猫とC#について書くmatarilloの日記
仕事で C# 使わなくなって久しい僕なので、あまり自信ないけど…。
* ==演算子のオーバーロードを実装してEqualsメソッドと同じ処理を実行するようにしてもよい場合はどのような時か?
同値であるオブジェクトを同一であるとして扱う場合。
* ループ内でなければ、たとえ100個の文字列型変数であってもまとめて+演算子で連結してよい理由を説明せよ。
+演算子が99回繰り返されるのではなく100個の文字列を一つにまとめるという処理に最適化されるため。
* List<int>のように値型を格納するジェネリックコレクションを使ってもボックス化/ボックス化解除が発生しない理由を説明せよ。
実行時に、要素の型が内部的にも object 型ではなく実際のジェネリック引数の型で置換されて動作するため。
* Full GC(Gen2 GC)が動作したときに断片化していてもコンパクションされないヒープ領域はどのような領域か?
ラージオブジェクトヒープと呼ばれる、一定サイズ以上のサイズを持ったオブジェクトが配置される領域。
* throw; とthrow ex; の違いをスタックトレースの観点で説明せよ。
前者は catch するまでのスタックトレースを維持するが、後者はその時点からのスタックトレースで上書きされる。
* フィールドのアクセス修飾子をprivateにしプロパティのgetter/setterではそのフィールドを読み書きするだけというコードが馬鹿馬鹿しい理由を説明せよ。
プロパティで単一フィールドの読み書き以外を行わない場合、自動プロパティが使用できるため。
* nullを参照している参照型変数のメソッドを呼び出そうとした場合でもNullReferenceExceptionが発生しないのは主にどういう状況か?
第一引数が null であっても NullReferenceException が発生しない拡張メソッドである場合。
* クラスと構造体の違いは何か?(「スタックとヒープ」以外で)
全部「スタックとヒープ」に起因するけど、次のような違いがある。(スタックとヒープに起因しないものあるっけ?)
・継承できない
・ユーザー定義のデフォルトコンストラクタとファイナライザを用意できない
・== 演算子、!= 演算子がデフォルトでは用意されていない
・object や interface にキャストするとボックス化が生じる
* デストラクタとは何か?
オブジェクトが破棄される際に呼び出される特殊なメソッド。C# ではファイナライザと呼ぶ。
* インターフェースの明示的実装を利用する目的を1つ説明せよ。
対象のインターフェイスを意識していないコードから無闇に呼び出せないように隠蔽する。
こんな感じ。
class Hoge
{
}
class Fuga : Hoge
{
}
class Piyo : Hoge
{
}
class Program
{
static void Main(string[] args)
{
var hoge1 = new Hoge();
var hoge2 = new Hoge();
var fuga1 = new Fuga();
var fuga2 = new Fuga();
var piyo = new Piyo();
bool b1 = hoge1.Equals(hoge2); // T = Hoge
bool b2 = hoge1.Equals(fuga1); // T = Hoge
bool b3 = fuga1.Equals(fuga2); // T = Fuga
bool b4 = fuga1.Equals(hoge1); // T = Hoge (!!)
//bool b5 = fuga1.Equals(piyo); // Build Error !!
bool b6 = fuga1.Equals((Hoge)piyo); // T = Hoge
bool b7 = fuga1.Equals<Hoge>(piyo); // T = Hoge
}
}
[検証プログラム]
インテリセンスに Object.Equals(object) が出てこないこと、Equals の引数の型が自分自身になることを確認できます。
ビルドが出来るだけで実行は出来ません。
ダウンロードページ
public class Object
{
protected internal virtual bool Equals(object obj)
{
...
}
}
public static class ObjectExtension
{
public static bool Equals<T>(this T source, T obj)
{
if (source == null)
{
// return (obj == null); にするのも面白い
throw new NullReferenceException();
}
return source.Equals((object)obj);
}
}
あまり深く考えずに言っているので、なんらかの不都合が生じるかもしれないけど。。。
つか、拡張メソッドのサポートを全ての言語に強いることになるからダメかな。
つかつか、やっぱし自分自身の型を示すキーワードが欲しくなる。