スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: スポンサー広告

あるクラスの某プロパティが欲しい時

あるクラス(X)の某プロパティ(P)が欲しい時、特に意識する事なくgetアクセサを使う。

var instanceOfX = new X();
var val = instanceOfX.P;
まあ、これはC#のコード上で取得したいプロパティがわかっているからできる事なんだけど。じゃあ、外部から与えられたプロパティ名(文字列)の値を取得したい時とかどうするだろう?
→これはリフレクションを使うことになる。メソッド化してみた。
using System;

namespace TawamureDays {

public static class Utils {

/// <summary>
/// 指定のオブジェクトから、指定されたプロパティ(文字列)の値を取得します。
/// </summary>
/// <param name="obj">オブジェクト</param>
/// <param name="propName">プロパティ名</param>
/// <returns>プロパティが持つ値</returns>
public static object GetProperty1(object obj, string propName) {
if (obj == null) {
throw new ArgumentNullException("obj");
}

if (string.IsNullOrWhiteSpace(propName)) {
throw new ArgumentNullException("propName");
}

var propInfo = obj.GetType().GetProperty(propName,
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance);

if (propInfo != null) {
return propInfo.GetValue(obj, null);
} else {
return null;
}
}
}
}
なお、このメソッドの場合、「プロパティは存在するが、値がnull」と「プロパティは存在しないので、null」との区別がつかない。付けたい場合は、以下のようにするのも1つの手。
using System;

namespace TawamureDays {

public static class Utils {

/// <summary>
/// 指定のオブジェクトから、指定されたプロパティ(文字列)の値を取得します。
/// </summary>
/// <param name="obj">オブジェクト</param>
/// <param name="propName">プロパティ名</param>
/// <returns>プロパティが持つ値</returns>
public static Tuple<bool, object> GetProperty1(object obj, string propName) {
if (obj == null) {
throw new ArgumentNullException("obj");
}

if (string.IsNullOrWhiteSpace(propName)) {
throw new ArgumentNullException("propName");
}

var propInfo = obj.GetType().GetProperty(propName,
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance);

if (propInfo != null) {
return Tuple.Create(true, propInfo.GetValue(obj, null));
} else {
return Tuple.Create(false, null as object);
}
}
}
}
取得した値をそのまま使えなくなるというデメリットは発生するけど、戻り値のItem1で、プロパティの存在をチェックできる。何を今更な内容をだしておいて、リフレクションを使いはするけど、リフレクション以外でプロパティを取得する方法をメモしておく。
方法としては、以前にも書いた、インスタンスを生成する時に使った式木を使う。式木は使いようによって(キャッシュすることで)、リフレクションよりも高い性能を見せてくれる。


namespace TawamureDays {

public static class Utils {

/// <summary>
/// 指定のオブジェクトから、指定されたプロパティ(文字列)の値を取得します。
/// </summary>
/// <param name="obj">オブジェクト</param>
/// <param name="propName">プロパティ名</param>
/// <returns>プロパティが持つ値</returns>
public static Tuple<bool, object> GetProperty2(object obj, string propName) {
if (obj == null) {
throw new ArgumentNullException("obj");
}

if (string.IsNullOrWhiteSpace(propName)) {
throw new ArgumentNullException("propName");
}

var propInfo = obj.GetType().GetProperty(propName,
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance);

if (propInfo != null) {
var target = System.Linq.Expressions.Expression.Parameter(typeof(object), "obj");

//Convert(obj).${propName}
var propExpression =
System.Linq.Expressions.Expression.Property(
System.Linq.Expressions.Expression.Convert(
target, obj.GetType()),
propName);
//obj => Convert(Convert(obj).${propName})
//Convert(obj) でobject型から具体的な型にConvert
//更にConvertを行うのは、そうしないと、値型をobjectに変換できない為
var expr = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(
System.Linq.Expressions.Expression.Convert(
propExpression,
typeof(object)),
target);
//exprをコンパイルしてFunc<object, object>を作り出す。
var getter = expr.Compile();

return Tuple.Create(true, getter(obj));

} else {
return Tuple.Create(false, null as object);
}
}
}
}
これでもGetPropertyメソッドと同じ働きをする。上述したけど、これを毎回呼び出すのは、リフレクションよりも重い。なぜか。コンパイルしてるから。ただし、上記メソッド内でコンパイルしてできた物(getter)をstatic領域にでもキャッシュして、再利用すれば、リフレクションよりも早くなる。仕事では、TypeのFullname+プロパティ名をキーにしてディクショナリにキャッシュしている。
↓はキャッシュバージョン

static Dictionary<string, Func<object, object>> cache_ =
new Dictionary<string, Func<object,object>>();

/// <summary>
/// 指定のオブジェクトから、指定されたプロパティ(文字列)の値を取得します。
/// </summary>
/// <param name="obj">オブジェクト</param>
/// <param name="propName">プロパティ名</param>
/// <returns>プロパティが持つ値</returns>
public static Tuple<bool, object> GetProperty2(object obj, string propName) {
if (obj == null) {
throw new ArgumentNullException("obj");
}

if (string.IsNullOrWhiteSpace(propName)) {
throw new ArgumentNullException("propName");
}

var propInfo = obj.GetType().GetProperty(propName,
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance);

if (propInfo != null) {

var key = obj.GetType().FullName + "|" + propName;
Func<object, object> getter;

if (cache_.ContainsKey(key)) {
//キャッシュされているなら、それを使う
getter = cache_[key];
return Tuple.Create(true, getter(obj));
}

var target = System.Linq.Expressions.Expression.Parameter(typeof(object), "obj");

//Convert(obj).${propName}
var propExpression =
System.Linq.Expressions.Expression.Property(
System.Linq.Expressions.Expression.Convert(
target, obj.GetType()),
propName);
//obj => Convert(Convert(obj).${propName})
//Convert(obj) でobject型から具体的な型にConvert
//更にConvertを行うのは、そうしないと、値型をobjectに変換できない為
var expr = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(
System.Linq.Expressions.Expression.Convert(
propExpression,
typeof(object)),
target);
//exprをコンパイルしてFunc<object, object>を作り出す。
getter = expr.Compile();
//キャッシュするよ
cache_[key] = getter;

return Tuple.Create(true, getter(obj));

} else {
return Tuple.Create(false, null as object);
}
}

●速度測定

/// <summary>
/// テストクラス1
/// </summary>
public class TestClass1 {

/// <summary>
/// データを取得|設定します。
/// </summary>
public int Data {
get; set;
}
}

void Main() {
TestClass1 class1 = new TestClass1 {Data = 100};

var watch = new System.Diagnostics.Stopwatch();
watch.Start();

for (var i = 0; i < 3000000; i ++) {
//切り替える
Utils.GetProperty1(class1, "Data");
//Utils.GetProperty2(class1, "Data");
}

watch.Stop();
var cost = watch.Elapsed;
return;
}

●測定結果
繰り返し回数:300万回
リフレクション使用バージョン:
5秒00
4秒81
5秒08
式木使用バージョン
1秒82
1秒82
2秒14

繰り返し回数:1000万回
リフレクション使用バージョン:
16秒67
16秒19
16秒62
式木使用バージョン
7秒40
6秒00
6秒05
結果から言えば、2倍強~5倍の性能という事になった。static領域を使う分メモリを食うけど、この速度は魅力なんだよな。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: C# | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。