2011-11-14 3 views
6

私は変更できないプロトコルを持つinterop用の構造体型用のカスタムシリアライザを作成しています。私はリフレクションを使用して構造体メンバの値を取り出し、BinaryWriterに書き込みます。基本的な型と配列をサポートするように設計されています。シリアライズのための未知の型の動的キャスト

if  (fi.FieldType.Name == "Int16") bw.Write((Int16)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "UInt16") bw.Write((UInt16)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Int32") bw.Write((Int32)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "UInt32") bw.Write((UInt32)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Int64") bw.Write((Int64)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "UInt64") bw.Write((UInt64)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Single") bw.Write((float)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Double") bw.Write((double)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Decimal") bw.Write((decimal)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Byte") bw.Write((byte)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "SByte") bw.Write((sbyte)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "String") bw.Write((string)fi.GetValue(obj)); 

明らかに、これは醜いですし、これらの型の配列でも同じことをしたい場合は、さらに醜いです。アレイ用のものの同様の種類の操作を行いその後

bw.Write((fi.FieldType) fi.GetValue(obj)); 

:私はこのような何かを行うことができれば本当にいいだろう何

です。

アイデア?

+0

+1この質問に対して、私はしばらくの間これを行うための堅実な方法を探してきました。 –

+0

fiの種類は何ですか? – drdwilcox

+0

醜いコードが唯一の選択肢になってしまった場合、私は一般的に、この種のシナリオではT4テンプレートを使用して、愚かな間違いを避け、Visual Studioに自動的にすべてのコードを生成させます。あなたは、繰り返し処理するための型のリストが必要です。 – mellamokb

答えて

4

あなたはWrite

public static void WriteField(BinaryWriter bw, object obj, FieldInfo fieldInfo) 
{ 
    typeof(BinaryWriter) 
     .GetMethod("Write", new Type[] { fieldInfo.FieldType }) 
     .Invoke(bw, new object[] { fieldInfo.GetValue(obj) }); 
} 
+0

これはうまくいくようです。私はそれを行ってあげるよ。 – Polynomial

+0

@Polynomialは、それはかなり遅くなることに注意してください...反射は悪名高く遅いです。あなたのシナリオにもよりますが、それでもまだ十分速いかもしれません**。 –

+0

作品は絶対完璧です! :D – Polynomial

2

このコードは実際には全く醜いものではありません...それは単なる繰り返しです。しかし、それは実際にはかなりきれいで、短く、非常に理解しやすいです。あなたが百万の異なるタイプのアカウントを持っていれば、それは一つのことですが、限られた数しかありません。

あなたがやりたいことをすることができれば、それに問題があるか、何か他のプログラマーがそれを理解できない場合、あるいはあなたがしたことを忘れてしまって、それを再学習しなければならないかもしれません。これはあなたが持っているでしょう。これにより

: は、追加の開発時間 -reduced可読性 -reducedスピード

は時々我々はあまりにもシンプルな問題を取り、それらをより難しくするのが好き -increasedメンテナンスに-added。しかし、しばしば良いビジネスコードは、退屈で退屈なコードです。

+0

"それはちょうど反復です"と言えば、 'T []'、 'List '、 'Dictionary '、そして逆シリアル化についても考慮する必要があります。 – Polynomial

+0

私は理解しています。私はJacobの答えはかなり簡単だと思う...私はvcsJonesの解決策以上にあなたがこの経路を取ることを選ぶだろう。 –

2

簡略化したい場合は、式を使用して適切な呼び出しを動的に行うことができます。

//Cache the generated method for re-use later, say as a static field of dictionary. It shouldn't grow too-big given the number of overloads of Write. 
private static Dictionary<Type, Action<BinaryWriter, object>> _lambdaCache = new Dictionary<Type, Action<BinaryWriter, object>>(); 

//... 

if (!_lambdaCache.ContainsKey(fi.FieldType)) 
{ 
    var binaryWriterParameter = Expression.Parameter(typeof(BinaryWriter)); 
    var valueParameter = Expression.Parameter(typeof(object)); 
    var call = Expression.Call(binaryWriterParameter, "Write", null, Expression.Convert(valueParameter, fi.FieldType)); 
    var lambda = Expression.Lambda<Action<BinaryWriter, object>>(call, binaryWriterParameter, valueParameter).Compile(); 
    _lambdaCache.Add(fi.FieldType, lambda); 
} 
var write = _lambdaCache[fi.FieldType]; 
write(bw, fi.GetValue(obj)); 

ここでは、バイナリライターに必要な呼び出しを行うためのコードを動的に生成しています。これはそれよりも複雑に聞こえますが、私たちがやっていることは、BinaryWriterの "Write"メソッドへの式を作成することです。 Expression.Convertを使用して動的にキャストするので、Writeという正しいオーバーロードが呼び出されます。 BinaryWriterの2つのパラメータと値を書き込みます。最後に、ラムダをコンパイルし、後で再利用できるようにそのタイプのためにキャッシュします。

お客様のニーズに応じて、BinaryWriter以上の反射を使用するよりもはるかに高速です。

+0

これがどうやって、なぜこれがなぜ機能するかについての詳細をいくつか追加すれば、これをアップホートします。 –

+0

これは 'Write'のオーバーロードを考慮に入れますか? 'GetValue'のメソッドシグネチャが' object GetValue(object) 'であるにもかかわらず、返される型の' fi.GetValue(obj) 'が' Int32'のときは 'Write(Int32)'を呼び出さなければなりません。 – Polynomial

+0

フィールド値を定数として渡す以外は良い解決策です。したがって、クラス/構造体のインスタンスを直列化するたびに新しいラムダを作成してコンパイルする必要があります。 Expression.Convertを追加して、フィールド値を正しい型に変換し、フィールド値をパラメータにすることができますか?その後、コンパイルを繰り返すことなく繰り返し呼び出すことができます。 –

2

私はprotobuf-netによく似たコードをいくつか行います。

switch(Type.GetTypeCode(fi.FieldType)) { 
    case TypeCode.Int16: bw.Write((Int16)fi.GetValue(obj)); break 
    case TypeCode.UInt32: bw.Write((UInt16)fi.GetValue(obj)); break; 
     ... etc lots and lots 
} 
まだ少し繰り返し

、しかし、あなたは一度だけTypeを見て - 残りはswitch次のとおりです。Type.GetTypeCode(...)switchを許可する、恩恵です。

あなたが4.0を使用している場合は、別のトリックがあるかもしれない:実行時に最も適切なオーバーロードを選択しようとします

dynamic value = fi.GetValue(obj); 
bw.Write(value); 

。しかし、私のビュー、これは理由が十分ではないdynamicをここで使用する。

最終的な思考は、次のようになります。実行時にコードを作成するために、(例えばILGeneratorなど)のメタプログラミングを使用する - より複雑であるが、より速く、そして準備するときだけ(実行時にこれらのチェックのいずれかを持っていませんモデル)。

1

の正しいバージョンを呼び出すためにリフレクションを使用することができ、私は3つのオプションを考えることができます:

1)BinaryFormatter - これはで、非常に簡単にあなたのタスクを達成することができるかもしれませんSerialize方法。
2)ご提案のとおり、リフレクションを使用してください。コードは次のようになります:

// sample source data 
object src = (uint)234; 

var bwType = typeof(BinaryWriter); 
var argTypes = new Type[] { src.GetType() }; 
var m = bwType.GetMethod("Write", argTypes); 
var args = new object[] { src }; 
m.Invoke(bw, args); 

3)コードをすばやく生成するには、T4テンプレートを使用します。コードはまだ醜いですが、少なくとも維持する作業が少なくて済みます。このパターンは、リフレクションによるパフォーマンスの低下はなく、動的に生成されたコードのすべてのメリットを兼ね備えているため、いくつかのプロジェクトでよく使用されます。

0

他に何もしない場合でも、switchは文字列で動作し、読んだほうが読みやすくなります。明示的なキャストが動作している考える

Type t = Type.GetType(String.Concat("System.", fi.FieldType.Name)); 

は、それが

m.Invoke(bw, new object[] { fi.GetValue(obj) }); 

nullでない場合はこれがスコープにあるの種類にFieldType.Name対応を想定している

MethodInfo m = typeof(BinaryWriter).GetMethod("Write", new type[] { t }); 

を使用しています。配列のためにそこに何があるのか​​言っていないが、それがInt16[]なら、ちょっとジークポケリーなので、サブクラス化されてBinaryWriterであるかもしれない。 多くのことをしているなら、ある種のキャッシュName,TypeMethodInfoが役に立つでしょう。

関連する問題