プロジェクトの説明
このプロジェクトの目標は、.NETで関数型リアクティブプログラミング (FRP)の原則を適用できるライブラリを作成することです。
FRFでは、計算は使用されるデータの変更に自動的に対応します。
物語
プロジェクトのアイデアは、XAMLを使用してインターフェイスを記述する大規模な管理アプリケーションで作業しているときに生まれました。 このアプリケーションには、データと表示の広範なドメインモデルがありました。
問題の1つは、ビューを更新するタイミングを判断するのがかなり難しいことでした。 各ユーザーアクションでディスプレイ全体を簡単に再カウントすることは、あまりにも時間がかかり、さらに、バックグラウンドプロセスによってデータを更新できるため、十分なアイデアではありません。
ソリューションとして、計算されたデータを更新する機能を備えたソースの変更を追跡するオブジェクトを作成するコンバーターのセットを作成しました。 この概念は非常に有用であることがわかりました。 イベントをサブスクライブおよびサブスクライブ解除する必要がなくなり、クリックイベントが発生したときにコントロールを更新する必要があることを覚えておく必要がなくなりました。 単純に依存関係を示し、結果の計算方法を示すだけで十分です。
WPFバインディングは、次の理由により、このタスクには適していません。
- バインディングのターゲットは、DependencyObjectを継承するクラスのプロパティである必要があります。
- observablecollectionsではバインディングはうまく機能しません。
- より複雑な変換にはバインディングを簡単に適用できません。 中間ステップとして、常にDependencyObjectが必要です。
コレクションの変換は本当に便利だと思いましたが、次の理由ですぐに失敗しました。
- それらを使用するには、コードに追加の単語が必要であり、この技術に不慣れな人々の認識を複雑にしました。 「register-event-handler-update」のようなコードは実際にはより複雑でしたが、同僚にとってはより読みやすくなっています。
- オブジェクトは完全に非同期で動作し、それぞれに独自のメッセージキューとバッファがあったため、非常に「重く」なりました。 それらの成功により、それらはしばしばプロジェクトで使用され、アプリケーションに大きな負荷をかけました。
- 従来のデバッガーでパイプライン変換をデバッグすることは非常に困難になります。 更新プロセスを段階的に追うのはそれほど簡単ではありません。
この経験から、次のバージョンの変換オブジェクトの追加要件を明確にすることができました。
- それらの使用は単純でなければなりません(変換および静的メソッドにはLINQを使用する必要があります)。
- それらは可能な限り軽くあるべきです。
- オブジェクトの使用は、コードの残りの部分に影響を与えることなく、既存のライブラリの作業に干渉することなく実行する必要があります(LINQを拡張しますが、置き換えません)。
- デバッグには、単体テストなどの他の方法を使用することをお勧めします。 アイデアは、変換プロセスをライブラリに委託することです。 このプロセスは、トレーサーによって正常に表示できないと結論付けました。
特徴
Obticsを使用すると、リアクティブで観察可能なオブジェクトを作成できるため、結果を更新する必要がなくなり、動的アプリケーションで結果を更新する必要があるタイミングを追跡できます。 これについて心配する機会が少ないので、リッチで信頼性の高いアプリケーションをすばやく作成できます。
抽象的ではない
以下のような単純なコードは、標準のオブジェクトLINQを使用して静的な結果を生成します。 XAMLからPeopleNamesプロパティにバインドを追加すると、_Peopleコレクションの変更方法に関係なく、「1回限り」の結果が得られます。 Obticsを使用すると、PeopleNamesの結果は完全に反応的で観察可能になります。 意味:
- 個々のPeopleオブジェクトの_PeopleコレクションまたはLastNameプロパティを変更すると、PeopleNames値が自動的に更新されます(反応性)。
- バインドされたXAMLアプリケーションは、PeopleNamesへの変更を自動的に表示します(可視性)。
public class Test
{
ObservableCollection<Person> _People;
public IEnumerable < string > PeoplesNames_Static
{
get
{
return
from p in _People
orderby p.LastName
select p.LastName ;
}
}
public IEnumerable < string > PeoplesNames
{
get
{
return
ExpressionObserver.Execute(
this ,
t =>
from p in t._People
orderby p.LastName
select p.LastName
).Cascade();
}
}
}
* This source code was highlighted with Source Code Highlighter .
Obticsを使用すると、以下のコードを記述し、FullNameプロパティのValue値にバインドできます。 LastNameまたはFirstNameプロパティへの変更は、アプリケーションに自動的に即座に表示されます。
public class Test
{
Person _Person;
public IValueProvider< string > FullName
{ get { return ExpressionObserver.Execute( this , t => t._Person.LastName + ", " + t._Person.FirstName); } }
}
* This source code was highlighted with Source Code Highlighter .
(これらの例のPersonクラスは監視可能なクラスです。つまり、このクラスのインスタンスは、プロパティ値が変更されるたびに変更通知を送信します。詳細はこちら 。)
変換タイプ
暗黙的な値の変換。
使用されるメソッドは、Obtics.Values.ExpressionObserverクラスを形成します。 目的の結果を返すラムダ関数を記述するだけで、ExpressionObserverはそれを分析し、すべての依存関係を抽出し、この式が変化に依存する観測値のいずれかが完全に反応する式を作成します。 これにより、標準のオブジェクトLINQが自動的にリアクティブで表示可能な形式に書き換えられます。これは、バインド可能なオブジェクトLINQクエリを作成する非常に便利な方法です。
using Obtics.Values;
class Test
{
Person _Person = new Person( "Glenn" , "Miller" );
Person Person
{ get { return _Person; } }
public IValueProvider< int > PersonFullNameLength
{
get
{
return
ExpressionObserver.Execute( this , t => t.Person.FirstName.Length + t.Person.LastName.Length + 1);
//the below line is even simpler but because the lambda expression depends on the external 'this' variable the
//expression is re-compiled for every Test class instance. Better use lambda's without external variables.
//return ExpressionObserver.Execute(() => Person.FirstName.Length + Person.LastName.Length + 1);
}
}
}
* This source code was highlighted with Source Code Highlighter .
明示的な値の変換。
Obtics.Values.ValueProviderクラスのメソッドを使用します。 メソッドを自分で指定することにより、独自の変換パイプラインを構築できます。これにより、変換プロセスの制御が容易になります。 計算に対応する可変値を正確に指定できるため、不変または観測不能な依存関係のリソース消費を防ぐことができます。 このアプローチは、大量のデータを扱う場合に役立ちます。
using Obtics.Values;
class Test
{
Person _Person = new Person( "Glenn" , "Miller" );
Person Person
{ get { return _Person; } }
public IValueProvider< int > PersonFirstNameLength
{
get
{
return
//select the never changing Person property make a (static) IValueProvider for it.
ValueProvider.Static(Person)
//select the FirstName property of Person. This is a classic
//property and observably mutable
.Property<Person, string >(Person.FirstNamePropertyName)
//Calculate the result
.Select(
//fn is a string and Length is an
//immutable property of string
fn => fn.Length
);
}
}
}
* This source code was highlighted with Source Code Highlighter .
コレクション変換(LINQ)。
Obtics.Collections.ObservableEnumerableのメソッドが使用されます。 明示的な変換と同様に、このアプローチでは、コレクションがどのように応答するかを指定できます。 これによりライブラリの使用が複雑になりますが、変換を手動で記述し、変更を監視する必要がある依存関係を通知できます。 これにより、変更されない依存関係にリソースが費やされるのを防ぐことができます。 このスタイルは、不必要なリソースコストを防ぐために大規模なコレクションを使用する場合に役立ちます。 この方法を最大限に活用するには、コード内で「using System.Linq;」を「usingObtics.Collections;」に置き換える必要があります。そうしないと、多くのエラーが発生します。
using SL = System.Linq;
using Obtics.Values;
using Obtics.Collections;
class Test
{
ObservableCollection<Person> _People = new ObservableCollection<Person>();
public ReadOnlyObservableCollection<Person> People
{ get { return new ReadOnlyObservableCollection<Person>(_People); } }
public IEnumerable < string > LastNames
{
get
{
return
from p in People
select ValueProvider.Static(p).Property<Person, string >( "LastName" ) into ln
orderby ln
select ln;
}
}
}
* This source code was highlighted with Source Code Highlighter .
ExpressionObserver(暗黙的な変換)は拡張可能です。 ライブラリ( ObticsToXml )が作成されました。これはその拡張機能に基づいて構築されており、完全に動的なLinq toXML式を作成できます。
Obticsは、すべてのObjectLinqおよびほとんどのLinq to XMLを完全に監視可能なサポートを提供します 。
より多くの例は、これらのリンクで見つけることができます: 変換の例 、 ObticsExamlおよびObticsRaytracer 。
ObticsWpfHelperプロジェクトには、ObticsとWPFの部分的な組み合わせに基づくソリューションが含まれています。
Obticsで使用される関数、ラムダ式、値、およびオブジェクトがそれらから期待どおりに動作することは非常に重要です。
今後の機会
バージョン2.0が準備中です。 将来のバージョンの計画:
- 双方向操作(双方向操作)。 すべての変換は、ソースからクライアント、およびその逆の両方の変更をサポートします。 開発者は、クライアントからソースへの自動変更を適用できるようにするリターンパスを指定できます。
- Web(Silverlight)およびモバイルデバイス用のライトバージョン。 コードの束は、ソースからクライアントへの変更の伝播に焦点を当てています。 コレクションの変更通知を使用しないバージョンを作成することはできますが、プロパティの変更については通知できます。 小規模なコレクションを扱う場合によく見えるもの。
- ObticsはF#で簡単にサポートされるべきです。
プロジェクトのウェブサイト: http : //obtics.codeplex.com/