それらがViewModelとして最適に機能するためには、クラスがDependencyObjectから継承するか、穴に陥ったINotifyPropertyChanged(INPC)インターフェイスを実装する必要がありました。
DependencyPropertyがINPCの手動実装より遅いことは長い間秘密ではありませんでした。 私のテストでは、DependencyPropertyへの書き込みは手動実装よりも約13倍遅いことが示されています。 したがって、不可解なオプティマイザーとして、私は特にINPCに傾倒しています。 さらに、INPCサポートコードは、DependencyPropertiesの説明よりも論理的かつ有機的に見えます。
INPCの実装を促進する方法について多くの記事が書かれています。 これはStackTraceの研究の変種です。これはLambdaメソッドの変種です。これは個人のコードモンキーとしてのコードスニペットです。これは、リファクタリングエラーの万能薬としてのResharperです。 これらのオプションはすべて、多くの余分な身体の動きを必要とします。
たとえば、StackTraceを使用した実装は次のとおりです。
public sealed class StackTraceNPC : INotifyPropertyChanged { string _myProperty; public string MyProperty { get { return _myProperty; } set { if (_myProperty == value) return; _myProperty = value; RaisePropertyChanged(); } } /// setter- void RaisePropertyChanged() { var e = PropertyChanged; if (e != null) { var propName = new StackTrace().GetFrame(1).GetMethod().Name.Substring(4); e(this, new PropertyChangedEventArgs(propName)); } } public event PropertyChangedEventHandler PropertyChanged; }
また、コード内のコンパイラによって作成されるたびに、式ツリーを使用した例は次のとおりです。
public sealed class LambdaNPC : INotifyPropertyChanged { string _myProperty; public string MyProperty { get { return _myProperty; } set { if (_myProperty == value) return; _myProperty = value; RaisePropertyChanged(() => this.MyProperty); } } void RaisePropertyChanged<T>(Expression<Func<T>> raiser) { var e = PropertyChanged; if (e != null) { var propName = ((MemberExpression)raiser.Body).Member.Name; e(this, new PropertyChangedEventArgs(propName)); } } public event PropertyChangedEventHandler PropertyChanged; }
また、上記のINPC実装のパフォーマンス結果は次のとおりです。
不正なオプティマイザは、これらの数値を見て、StackTraceとLambdaの両方のバリアントをメモリから恐ろしく消去します。 セッターはあまり頻繁に呼び出されないので、パフォーマンスについて真剣に考えていることは明らかですが、DataGridのViewModelや、多数のフィールドについて話している場合、これらのブレーキが表面化する可能性があります。 さらに、RaisePropertyChangedの便利な呼び出しではなく、フィールドの変更やその他の型プロパティ名リテラルの落書きのチェックなど、それに関連付けられているすべてのhemoの最適化についてです。
価値のあるオプションの1つはPostSharpベースのAoPアプローチですが、コンパイル後に受け取ったILコードのReflectorを調べるだけで、私たちとPostSharpが近づいていないことを理解できます。
ここで、もうひねる時間です...しかし、Mono.Cecil を使用してサードパーティアセンブリにMSILコードを挿入することに関するMono.Cecilに関する記事に触発され、私はこの問題を完全に解決することにしました。
始めに、それがどのようになったかの例を示します:
public class MyViewModel: PropertyChangedBase { string _stringProperty; public string StringProperty { get { return _stringProperty; } set { if (_stringProperty == value) return; _stringProperty = value; RaisePropertyChanged("StringProperty"); } } object _objectProperty; public object ObjectProperty { get { return _objectProperty; } set { if (_objectProperty == value) return; _objectProperty = value; RaisePropertyChanged("ObjectProperty"); } } }
今どのようにBECAMEの例:
public class MyViewModel: PropertyChangedBase { public string StringProperty { get; set;} public object ObjectProperty { get; set;} }
INPCの実装はどこにありますか、あなたは尋ねます、そしてあなたは正しいでしょう。 魔法のような? つまり、 Kind of Magic MSBuildタスクです。 それが、このオープンソースのコードプレックスプロジェクトと呼ばれるものです。
秘密はすべて、基本クラスPropertyChangedBaseにあります。各クラスには独自のバージョンがあります:)
何がそんなに特別なのか見てみましょう:
[Magic] public abstract class PropertyChangedBase : INotifyPropertyChanged { protected virtual void RaisePropertyChanged(string propName) { var e = PropertyChanged; if (e != null) e(this, new PropertyChangedEventArgs(propName)); // Dispatcher, UI thread } public event PropertyChangedEventHandler PropertyChanged; }
Magic属性を除き、他のすべては多かれ少なかれ順番に見えます。 MyViewModelクラスと同じアセンブリで記述されているMagicAttributeを扱いましょう。
class MagicAttribute: Attribute {}
一行、お願い? そうです。 アセンブリでMagicAttributeという名前の属性を定義し、INPCを実装するベースまたはクラスに適用するだけで十分です。 この場合、これらのクラスとその子孫のすべてのパブリックプロパティはINPC互換になります。 INPCクラスのプロパティに直接適用すると、これらのプロパティのみがINPC互換になります。
そして、そのような属性を追加します:
class NoMagicAttribute: Attribute {}
クラスとプロパティは、INPCの魔法の実装から除外できます。
余分なキロバイトのコードを心配する必要はありません。アセンブリをコンパイルした後、Reflectorがどのように役立つかを知るためにこれらの属性の痕跡はありません。
次に、その仕組みについて少し説明します。
- すべてはコンパイル段階で発生します。 より正確には、コンパイル後、ただしアセンブリの署名前。 つまり ランタイムでは、最大のパフォーマンスが得られます(ほとんどの場合、手書きコードよりも高速です)。
- KindOfMagicは文字通りセッターに私たちが書くのが面倒なものを追加し、それによって指、Resharper、コードエディター、神経の負荷を軽減します。
- KindOfMagicはプロパティをINPC互換にしますが、それだけです。 また、ILの観点から最適かつ迅速かつ透過的に実行します。 コンパニオンPDBファイルも変換されるため、「強化された」プロパティのデバッグに問題はありません。
- KindOfMagicは、プロパティが実際に変更された場合にのみRaisePropertyChangedを呼び出します。 新旧の検証コードは、プロパティのタイプに応じて生成されます。 Nullable <T>を含む、すべてのタイプがサポートされています。
- KindOfMagicは、Silverlightプロジェクトと.NETプロジェクトの両方をサポートしています。
- KindOfMagicはMono.Cecilを使用してコードを挿入します。 ミゲルとKに感謝します。
さて、今、私たちは勝者に会います:
ここではKindOfMagicをダウンロードできます。 ここには、 疑わしい人向けのテスト予測があります。 結果は、Win7x64、Core2 Quad @ 2.4GHzで取得されました。
更新1
よく調べてみると、バグが見つかりました。バグは正常に修正されました。 KindOfMagicの結果は、予想どおり手書きコードに追いつきました。
本当の奇跡はありません。時にはそのようなこともあります:)