イントレ。
Tag for "C#"
Googleリーダー用の新着通知アプリ。こちらのソースコード リビジョン10をベースに使っています。
■ r3 - 認証API仕様変更対応。
↓過去のもの。
■ 初期リリース版。日本サーバーだと認証コケるので改変。
■ r2 - 認証API仕様変更対応。
・・・というのを思いついたので実装してみた。
パブリックプロパティと「プロパティ名Changed」という名前のイベントもどきを用意。
こいつにINotifyPropertyChangedを実装して、プロパティへの書き込み時に呼び出してます。
リフレクション経由のプロパティ操作が重いのが難点。
パブリックプロパティと「プロパティ名Changed」という名前のイベントもどきを用意。
こいつにINotifyPropertyChangedを実装して、プロパティへの書き込み時に呼び出してます。
リフレクション経由のプロパティ操作が重いのが難点。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Reflection;
namespace NotifyWrapper_Test
{
public class NotifyWrapper : DynamicObject, INotifyPropertyChanged
{
object obj;
Dictionary<string, Tuple<PropertyInfo, bool>> properties; // <name, <info, has_setter>>
Dictionary<string, EventHandler> events; // <name, delegate>
public event PropertyChangedEventHandler PropertyChanged;
// Tのインスタンスをnew()で作成して使用
public static NotifyWrapper Create<T>() where T : new() { return Create<T>(new T()); }
// Tのインスタンスを指定して使用
public static NotifyWrapper Create<T>(T instance) { return new NotifyWrapper(instance, typeof(T)); }
NotifyWrapper(object instance, Type type)
{
obj = instance;
properties = type.GetProperties()
.ToDictionary(i => i.Name, i => new Tuple<PropertyInfo, bool>(i, i.GetSetMethod() != null));
// プロパティ名Changed という名前のイベントを自動生成
events = properties
.Where(i => i.Value.Item2)
.ToDictionary(i => i.Key + "Changed", i => new EventHandler(delegate { })); // チェック面倒なので空のデリゲートを...
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
Tuple<PropertyInfo, bool> pi;
EventHandler ev;
result =
properties.TryGetValue(binder.Name, out pi) ? pi.Item1.GetValue(obj, null) :
events.TryGetValue(binder.Name, out ev) ? ev :
null;
return (result != null);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
Tuple<PropertyInfo, bool> pi;
if (properties.TryGetValue(binder.Name, out pi))
{
if (pi.Item2 == false)
return false;
pi.Item1.SetValue(obj, value, null);
events[binder.Name + "Changed"](obj, EventArgs.Empty);
if (PropertyChanged != null)
PropertyChanged(obj, new PropertyChangedEventArgs(binder.Name));
return true;
}
var ev = value as EventHandler;
if (ev != null && events.ContainsKey(binder.Name))
{
events[binder.Name] = ev;
return true;
}
return false;
}
// 必須じゃないけどとりあえず
public override IEnumerable<string> GetDynamicMemberNames()
{
return properties.Select(i => i.Key)
.Concat(events.Select(i => i.Key))
.OrderBy(i => i);
}
}
class Foo
{
public string Name { get; set; }
public Foo()
{
Name = "Hoge";
}
}
class Program
{
static void Main(string[] args)
{
dynamic item = NotifyWrapper.Create<Foo>();
// メンバー名を取得してみる
Console.WriteLine("DynamicMembers:");
((NotifyWrapper)item).GetDynamicMemberNames().Run(Console.WriteLine);
// イベント設定
((INotifyPropertyChanged)item).PropertyChanged += (_,e) => Console.WriteLine("PropertyChanged: {0}", e.PropertyName);
var name_changed = new EventHandler((_, __) => Console.WriteLine("Name Changed"));
item.NameChanged += name_changed;
// 読み書きテスト
Console.WriteLine(item.Name); // Hogeのはず
item.Name = "Foo"; // イベントが発動するはず
Console.WriteLine(item.Name); // Fooのはず
// イベント解除してテスト
item.NameChanged -= name_changed;
item.Name = "ほえー";
}
}
}
こんな感じのコードを書いてました。
var items = GetItems();
ProcessItems(items);
...
private void ProcessItems(IEnumerable
var items = GetItems();
ProcessItems(items);
...
private void ProcessItems(IEnumerable
以前チャレンジ失敗して放置してたんだけど、ズバリな回答案があった。
Highlight whole TreeViewItem line in WPF
Borderの中にGridを配置してGrid.Marginを調整することで子要素を段落下げしている。
TreeViewItemの親を辿ることで階層の深さを取得、Grid.Marginにコンバータ経由で渡している。
こんなシンプルな解決案があったなんて。・・・無念。
<追記>
・・・って思ったんだけど、DataTemplateと組み合わせると問題発生。
コンバータ経由で階層の深さを取得する際のタイミングの問題で、
TreeViewItem.Itemsに追加→スタイル設定となって欲しいんだけど逆になるっぽい。
TreeViewItem.Parentが変化したら再計算して適用すればいいのですが、
それを検知する方法がない・・・。
困った。
</追記>
そして、日記を更新する癖が完全に抜けてる件。
Highlight whole TreeViewItem line in WPF
Borderの中にGridを配置してGrid.Marginを調整することで子要素を段落下げしている。
TreeViewItemの親を辿ることで階層の深さを取得、Grid.Marginにコンバータ経由で渡している。
こんなシンプルな解決案があったなんて。・・・無念。
<追記>
・・・って思ったんだけど、DataTemplateと組み合わせると問題発生。
コンバータ経由で階層の深さを取得する際のタイミングの問題で、
TreeViewItem.Itemsに追加→スタイル設定となって欲しいんだけど逆になるっぽい。
TreeViewItem.Parentが変化したら再計算して適用すればいいのですが、
それを検知する方法がない・・・。
困った。
</追記>
そして、日記を更新する癖が完全に抜けてる件。
どっちが早いのか気になったので、ちょっとテストしてみた。
フィールドとプロパティの通常・リフレクション経由の読み書きを100万回行って時間計測。
read-field(normal): 27.3012ms
read-property(normal): 58.4602ms
read-field(reflection): 395.7991ms
write-property(normal): 397.8156ms
write-field(normal): 398.9727ms
write-field(reflection): 1005.3316ms
read-property(reflection): 2019.6969ms
write-property(reflection): 3130.8138ms
やっぱりフィールドへの読み書きは早いですね。
そして、プロパティへのリフレクション経由読み書きは絶望的なほど遅いですね・・・。
ついでにLambdaExpressionのメンバー名取得も計測。
INotifyPropertyChangedのいけてる実装とかでつかってるやつ。
GetName(() => PROPERTY); // == "PROPERTY"
みたいな感じで、ラムダ式で指定したプロパティ名を取得するってコードになります。
VSのリファクタ機能とか使えるので名前変更とか使ってる場所特定とか、文字リテラルを使うよりメリットは多いです。
が。10万回のテストで2019msとハンパなく遅いです。(文字リテラル指定だと2ms程度)
リフレクション絡む部分はコストが高いってことで、使い方をちゃんと考えなければいけないなぁ。
フィールドとプロパティの通常・リフレクション経由の読み書きを100万回行って時間計測。
read-field(normal): 27.3012ms
read-property(normal): 58.4602ms
read-field(reflection): 395.7991ms
write-property(normal): 397.8156ms
write-field(normal): 398.9727ms
write-field(reflection): 1005.3316ms
read-property(reflection): 2019.6969ms
write-property(reflection): 3130.8138ms
やっぱりフィールドへの読み書きは早いですね。
そして、プロパティへのリフレクション経由読み書きは絶望的なほど遅いですね・・・。
ついでにLambdaExpressionのメンバー名取得も計測。
INotifyPropertyChangedのいけてる実装とかでつかってるやつ。
GetName(() => PROPERTY); // == "PROPERTY"
みたいな感じで、ラムダ式で指定したプロパティ名を取得するってコードになります。
VSのリファクタ機能とか使えるので名前変更とか使ってる場所特定とか、文字リテラルを使うよりメリットは多いです。
が。10万回のテストで2019msとハンパなく遅いです。(文字リテラル指定だと2ms程度)
リフレクション絡む部分はコストが高いってことで、使い方をちゃんと考えなければいけないなぁ。
Utilities
- タグ
- カレンダー
- 最近の更新
- Adsense