この記事を学習する過程で、独自の属性を実装し、それらを適用する方法を学び、実際に型拡張の方法とリフレクションの適用に精通し、 MSILの基本 、特にOpCodeの 基本、およびAMFでオブジェクトをシリアル化する方法を学びますスレッドを使用します。
問題の声明
まず、この記事で解決する必要があるタスクのおおよそのリストを作成しましょう。 次のようにします。
- 任意のタイプのサーバーデータをC#コードで記述し、それらを便利に操作する機能。
- 任意のタイプのシリアル化/逆シリアル化の可能性(私たちが説明したものとそうでないものの両方);
- サーバー自体にデータを送信するメカニズム。
この記事のフレームワークでは、何らかの既製の抽象サーバーを使用します(ユーザーの場合、これは非常に実際に機能するサーバーであり、ユーザーの参加なしに実装されます)。 サーバー側がFlexで記述されていることがわかっていると仮定すると、AMFオブジェクトを送信する必要がある直接リンクも既に受信しています。 AMF自体を直接操作するために、サーバー側とクライアント側の両方でFlexサポートを整理するために必要なすべてのツールを提供する素晴らしいFluorineFXフレームワークがあります。 しかし、問題は、オブジェクトをサーバーに送信する非常に待たれた瞬間になると、FluorineFXがマッピングのための便利な手段を提供せず、サーバーに送信されるすべてのオブジェクトが元のタイプメタデータで飛行することです。 実際、クライアント側とサーバー側の作成者であれば、これはまったく問題ではありませんが、カスタム構造を持つデータパケットを送信する必要がある場合はどうでしょうか。 1 in 1名前空間と型名を繰り返すことは必ずしも便利ではありません(時には不可能な場合もあります)。 少しグーグルで、近くで完全に機能するソリューションを見つけます。 その主な長所と短所をすぐに検討してください。
長所:
- スピード。 FluorineFXのソースコードを直接変更し、収集してお楽しみください。
短所:
- サポート。 チームで作業する場合、チーム開発のすべての参加者がソースFluorineFXを常に利用できるようにし、常に関連性を維持する必要があります。 これには、より多くのプロジェクトコードが必要であるため、サポートに時間がかかります。
- 更新。 FluorineFXの新しいバージョンがリリースされた場合、最大限の互換性を維持しながら、ソースコード全体を再更新して変更する必要があります。これは非常に不便です。 ここで、おそらく、唯一のプラスのポイントは、更新から常に新しい機能が必要になる可能性があり、フレームワーク自体がめったに更新されないことです。 しかし、最適なワークフロー(および別の問題を解決するときに同様の状況が発生する可能性がある場合)の場合、この点は非常に重要です。
この決定の不利益に恥ずかしくない人のために、以下のすべての資料は必要ではないかもしれません。 私と一緒に回ってより柔軟なソリューションを見つけることにした人にとって、以下の資料は非常に興味深いものになります。
属性を操作する
一般に、 ソリューションで説明されているアプローチは正しい方向に進みます。 マッピング自体は、タイプメタデータのレベルでのある種のバインディングを意味します。これには属性が必要です。
まず、AMFでのシリアル化のために型を準備するときに、型がどのように見えるかについて大まかな説明を書きましょう。 次の機能を提供します。
- 属性が指定されている型は、属性プロパティで指定された名前でシリアル化されます。
- 属性が指定されていない型(またはプロパティがnullである属性)は、元のアセンブリで定義された型の元の名前でシリアル化されます。
- 属性が設定されているプロパティおよびタイプフィールドは、属性プロパティで指定された名前でシリアル化されます。
- 属性が設定されているが属性プロパティがnullであるプロパティと型フィールドは、元のアセンブリで定義された元の型名でシリアル化されます。
- 属性が指定されていないプロパティおよび型フィールドは、シリアル化プロセスに参加せず、最終型のメンバーの一部ではありません。
- 辞書は、AMF連想配列としてシリアル化する必要があります。
- 配列型はすべて、AMFオブジェクトの配列としてシリアル化する必要があります。
- 組み込み型は、AMFオブジェクトの組み込み型としてシリアル化する必要があります。
属性クラスを実装することから始めましょう。 オブジェクトタイプとフィールドタイプとオブジェクトプロパティに単一の属性を書き込むことができます( ソリューションと同様)が、タイプとタイプメンバーに個別の属性を実装することをお勧めします。これは、異なるインスタンスのタイプからメタデータを処理するロジックを分離する必要があるときに便利です。 まず、オブジェクトタイプの属性クラスを実装します。
/// <summary> /// AMF. /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class AmfObjectAttribute : Attribute { /// <summary> /// . /// </summary> public string Name { get; set; } /// <summary> /// <see cref="AmfObjectAttribute"/>. /// </summary> /// <param name="name"> .</param> public AmfObjectAttribute(string name) { Name = name; } /// <summary> /// <see cref="AmfObjectAttribute"/>. /// </summary> public AmfObjectAttribute() : this(null) { } }
属性の実装におけるいくつかの重要なポイント:
- すべての属性はSystem.Attributeクラスから継承する必要があります。
- AttributeUsageAttribute 属性は 、属性のスコープを決定するために必要です。 その助けにより、実装された属性が適用されるメタデータのタイプを設定および除外することができます。
- デフォルトのコンストラクターを指定しない場合、パラメーターを持つオーバーロードされたコンストラクターを介してのみ、初期化時に明示的に属性プロパティに値を割り当てることができません。
- 属性タイプを指定する場合、サフィックス... Attributeは省略できます。
AmfObjectAttribute属性は、オブジェクトタイプに適用されます。 この属性を使用すると、シリアル化されたオブジェクトの型名はAmfObjectAttributeプロパティで指定された値と一致します。 Name 、プロパティ値がnullの場合、シリアル化されたオブジェクトは元の型名を保持します。 この属性でマークされていない型のオブジェクトをシリアル化する可能性を実現するために、意図的に型の属性の存在を確認しませんでした。
次に、タイプのプロパティとフィールドの属性クラスを実装します。
/// <summary> /// AMF. /// </summary> [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class AmfMemberAttribute : Attribute { /// <summary> /// . /// </summary> public string Name { get; set; } /// <summary> /// <see cref="AmfMemberAttribute"/>. /// </summary> /// <param name="name"> .</param> public AmfMemberAttribute(string name) { Name = name; } /// <summary> /// <see cref="AmfMemberAttribute"/>. /// </summary> public AmfMemberAttribute() : this(null) { } }
属性の実装を理解したので、特定のデータモデルを記述する単純なクラスを作成しましょう。 将来的には、テストに使用します。
/// <summary> /// AMF- . "namespase.of.your.object". /// </summary> [AmfObject("namespase.of.your.object")] public class CustomAmfObject { /// <summary> /// <see cref="bool"/>. "bit_prop". /// </summary> [AmfMember("bit_prop")] public bool BooleanProperty { get; set; } = true; /// <summary> /// <see cref="sbyte"/>. UnsignedByteProperty. /// </summary> [AmfMember] public sbyte UnsignedByteProperty { get; set; } = 2; /// <summary> /// <see cref="string"/>. . /// </summary> public string StringProperty { get; set; } = "test"; /// <summary> /// <see cref="bool"/>. "bit_fld". /// </summary> [AmfMember("bit_fld")] public bool booleanField = false; /// <summary> /// <see cref="float"/>. singleField. /// </summary> [AmfMember] public float singleField = -5.00065f; /// <summary> /// <see cref="string"/>. . /// </summary> public string stringField = "test2"; /// <summary> /// <see cref="CustomAmfObject"/>. /// </summary> public CustomAmfObject() { } }
属性が書き込まれ、テストクラスが実装されます。 ここで、シリアル化自体の問題の解決を開始する必要があります。 .NET 2.0のサポートを計画している場合は、オブジェクトインスタンスで動作し、そのタイプのメタデータでさまざまな操作を実行するシリアライザークラスを実装する必要があります。 C#-engineerの2つの非常に重要な機能が登場したため、バージョン.NET 3.5のサポートを考慮してコードを記述します:LINQと型拡張メソッド。 問題を解決するためにそれらを適用します。
拡張メソッドとリフレクション
型拡張メソッドを実装するには、パブリック静的クラスを宣言し、this修飾子を最初のパラメーターに追加するだけで十分です。 これらの条件は必須です。それ以外の場合、コンパイラはメソッドが型の拡張であることを単に理解しません。 実装後、このメソッドは、同じタイプのオブジェクト、または拡張メソッドの最初のパラメーターのタイプから継承されたオブジェクトに適用できます。 拡張メソッドの独自のクラスを作成しましょう:
/// <summary> /// / AMF. /// </summary> public static class Extensions { }
まず、コードを再利用するためにいくつかのヘルパーメソッドが必要になります。 .NET 4.5では、 Attribute Type .GetCustomAttribute( Type )メソッドが導入されました。これにより、指定したタイプの属性をすぐに取得できます。 .NET 3.5では、これはまだではないので、属性を使用して便利に作業するための拡張メソッドをいくつか実装します。
/// <summary> /// . /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="sourceType"> .</param> /// <returns></returns> private static T GetAttribute<T>(this Type sourceType) where T : Attribute { object[] attributes = sourceType.GetCustomAttributes(typeof(T), true); // . if (attributes == null || attributes.Length == 0) return default(T); // - null. return attributes[0] as T; } /// <summary> /// . /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="sourceMember"> .</param> /// <returns></returns> private static T GetAttribute<T>(this MemberInfo sourceMember) where T : Attribute { object[] attributes = sourceMember.GetCustomAttributes(typeof(T), true); // . if (attributes == null || attributes.Length == 0) return default(T); // - null. return attributes[0] as T; } /// <summary> /// , . /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="sourceType"> .</param> /// <returns></returns> private static bool IsDefinedAttribute<T>(this Type sourceType) { object[] attributes = sourceType.GetCustomAttributes(typeof(T), true); // . return attributes != null && attributes.Length > 0; }
各シリアル化手順では、指定された属性によって型が生成されます。 シリアル化メソッドの呼び出しごとに多くの操作を繰り返さないために、作成後に動的アセンブリにメタデータを格納する可能性をすぐに考えてみましょう。
/// <summary> /// . /// </summary> private static ModuleBuilder moduleBuilder; /// <summary> /// <see cref="Extensions"/>. /// </summary> static Extensions() { AssemblyName assemblyName = new AssemblyName("AmfDynamicAssembly"); // . AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); // . moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll"); // . }
これで、すべてのタイプについて、独自の単一の動的アセンブリが作成され、メタデータが保存されます。 属性のデータに基づいて型生成アルゴリズムを実装するときが来ました。 最初に、アルゴリズムの各ステップをステップごとに実行してから、メソッドの完全なリストを提供します。
「リフレクション」(「リフレクション」でもあり、「リフレクション」でもある)の概念は、タイプのメタデータを使用した操作に基づいています。 実際、ここではすべてがシンプルです。 コンパイラによってコードに記述された型を作成するプロセスを繰り返す必要がありますが、独自のコードを使用します。 基礎として、既知のソースオブジェクトのタイプのメタデータと、もしあれば属性からのデータを取得します。 また、生成された型のオブジェクトのインスタンスへの参照を初期化するデフォルトのコンストラクタを実装する必要があります。 ModuleBuilder.TypeBuilderクラスを使用して、必要なすべての操作を実行できます。
TypeBuilderを使用してタイプを決定します 。
TypeBuilder typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public);
FieldBuilderを使用してフィールドを定義する:
FieldBuilder fieldBuilder = typeBuilder.DefineField($"m_{propertyName}", propertyType, FieldAttributes.Private);
PropertyBuilderを使用してプロパティを定義します。 ここでは、プライベートフィールドと、それにアクセスするためのアクセサーとミューテーターを定義する必要があります。
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); // . MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; // . MethodBuilder methodBuilderAccessor = typeBuilder.DefineMethod($"get_{propertyName}", getSetAttr, propertyType, Type.EmptyTypes); // . ILGenerator accessorIL = methodBuilderAccessor.GetILGenerator(); // MSIL- . accessorIL.Emit(OpCodes.Ldarg_0); // . accessorIL.Emit(OpCodes.Ldfld, fieldBuilder); // . accessorIL.Emit(OpCodes.Ret); // . MethodBuilder methodBuilderSetter = typeBuilder.DefineMethod($"set_{propertyName}", getSetAttr, null, new Type[] { propertyType }); // . ILGenerator setterIL = methodBuilderSetter.GetILGenerator(); // MSIL- . setterIL.Emit(OpCodes.Ldarg_0); // . setterIL.Emit(OpCodes.Ldarg_1); // . setterIL.Emit(OpCodes.Stfld, fieldBuilder); // . setterIL.Emit(OpCodes.Ret); // . propertyBuilder.SetGetMethod(methodBuilderAccessor); // . propertyBuilder.SetSetMethod(methodBuilderSetter); // .
ConstructorBuilderを使用してデフォルトコンストラクターを定義する:
ConstructorBuilder ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes); // . ILGenerator ctorIL = ctor.GetILGenerator(); // MSIL- . ctorIL.Emit(OpCodes.Ldarg_0); // . ctorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); // . ctorIL.Emit(OpCodes.Ret); // .
Activatorを使用して、生成されたばかりのタイプのオブジェクトの新しいインスタンスを初期化します。
object targetObject = Activator.CreateInstance(typeBuilder.CreateType());
すべてのメソッドメタデータコレクターには、 ILGeneratorインスタンスを返すGetILGenerator()メソッドがあります(これにより、目的のステートメントのシーケンスを実行のためにMSIL計算スタックに配置できます)。 OpCodesから渡すIL命令のセットは、説明したメソッドのロジックの動作を直接決定します。 この記事では、リフレクションのトピックについて表面的に触れていますが、これは既に別の記事の機会です。さらに、公式のMSDNドキュメントの指示の全リストをいつでも読むことができます。
型メタデータを動的に生成するためのロジックを記述するために必要なものはすべて揃っています。 生成された型のプロパティとフィールドもシリアル化属性を持つことができるという事実をすぐに考慮してください。再帰は実装に適用できます。 オブジェクトに一致するタイプ名の属性がない場合は、そのまま返します。 動的アセンブリに既にタイプのメタデータがある場合、それらに基づいてオブジェクトインスタンスを収集します。 これはすべて最適化に役立ちます。 必要なすべてのチェックと手順を含む、生成方法の完全なリストは次のとおりです。
/// <summary> /// <see cref="AmfObjectAttribute"/>, , <see cref="AmfMemberAttribute"/>. /// </summary> /// <param name="sourceObject"> .</param> /// <returns></returns> private static object GenerateType<T>(T sourceObject) { Type sourceType = sourceObject.GetType(); // . if (sourceType.IsDictionary()) return GenerateType(sourceObject as IEnumerable<KeyValuePair<string, object>>); if (!sourceType.IsDefinedAttribute<AmfObjectAttribute>()) return sourceObject; // - . string typeName = sourceType.GetAttribute<AmfObjectAttribute>().Name ?? sourceType.FullName; // . Type definedType = moduleBuilder.GetType(typeName); // . TypeBuilder typeBuilder = null; // . Dictionary<string, object> properties = new Dictionary<string, object>(); // . Dictionary<string, object> fields = new Dictionary<string, object>(); // . // ... if (definedType == null) { typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public); // . ConstructorBuilder ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes); // . ILGenerator ctorIL = ctor.GetILGenerator(); // MSIL- . ctorIL.Emit(OpCodes.Ldarg_0); // . ctorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); // . ctorIL.Emit(OpCodes.Ret); // . // . foreach (PropertyInfo propertyInfo in sourceType.GetProperties()) { AmfMemberAttribute attribute = propertyInfo.GetAttribute<AmfMemberAttribute>(); // AmfMemberAttribute. if (attribute == null) continue; // - . string propertyName = attribute.Name ?? propertyInfo.Name; // . object propertyValue = propertyInfo.GetValue(sourceObject, null); // . Type propertyType = propertyInfo.PropertyType; // . // ... if (propertyInfo.PropertyType.IsDefinedAttribute<AmfObjectAttribute>() || propertyType.IsDictionary()) { // , . propertyValue = propertyType.IsDictionary() ? GenerateType(propertyValue as IEnumerable<KeyValuePair<string, object>>) : GenerateType(propertyValue); propertyType = propertyValue.GetType(); // . } FieldBuilder fieldBuilder = typeBuilder.DefineField($"m_{propertyName}", propertyType, FieldAttributes.Private); // . PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); // . MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; // . MethodBuilder methodBuilderAccessor = typeBuilder.DefineMethod($"get_{propertyName}", getSetAttr, propertyType, Type.EmptyTypes); // . ILGenerator accessorIL = methodBuilderAccessor.GetILGenerator(); // MSIL- . accessorIL.Emit(OpCodes.Ldarg_0); // . accessorIL.Emit(OpCodes.Ldfld, fieldBuilder); // . accessorIL.Emit(OpCodes.Ret); // . MethodBuilder methodBuilderSetter = typeBuilder.DefineMethod($"set_{propertyName}", getSetAttr, null, new Type[] { propertyType }); // . ILGenerator setterIL = methodBuilderSetter.GetILGenerator(); // MSIL- . setterIL.Emit(OpCodes.Ldarg_0); // . setterIL.Emit(OpCodes.Ldarg_1); // . setterIL.Emit(OpCodes.Stfld, fieldBuilder); // . setterIL.Emit(OpCodes.Ret); // . propertyBuilder.SetGetMethod(methodBuilderAccessor); // . propertyBuilder.SetSetMethod(methodBuilderSetter); // . properties.Add(propertyName, propertyValue); // . } // . foreach (FieldInfo fieldInfo in sourceType.GetFields()) { AmfMemberAttribute attribute = fieldInfo.GetAttribute<AmfMemberAttribute>(); // AmfMemberAttribute. if (attribute == null) continue; // - . string fieldName = attribute.Name ?? fieldInfo.Name; // . object fieldValue = fieldInfo.GetValue(sourceObject); // . Type fieldType = fieldInfo.FieldType; // . // ... if (fieldInfo.FieldType.IsDefinedAttribute<AmfObjectAttribute>() || fieldType.IsDictionary()) { // , . fieldValue = fieldType.IsDictionary() ? GenerateType(fieldValue as IEnumerable<KeyValuePair<string, object>>) : GenerateType(fieldValue); fieldType = fieldValue.GetType(); // . } typeBuilder.DefineField(fieldName, fieldType, FieldAttributes.Public); // . fields.Add(fieldName, fieldValue); // . } } else { // . foreach (PropertyInfo propertyInfo in sourceType.GetProperties()) { AmfMemberAttribute attribute = propertyInfo.GetAttribute<AmfMemberAttribute>(); // AmfMemberAttribute. if (attribute == null) continue; // - . string propertyName = attribute.Name ?? propertyInfo.Name; // . object propertyValue = propertyInfo.GetValue(sourceObject, null); // . Type propertyType = propertyInfo.PropertyType; // . AmfObjectAttribute propertyAttribute = propertyInfo.PropertyType.GetAttribute<AmfObjectAttribute>(); // . // ... if (propertyAttribute != null || propertyType.IsDictionary()) { // , . propertyValue = propertyType.IsDictionary() ? GenerateType(propertyValue as IEnumerable<KeyValuePair<string, object>>) : GenerateType(propertyValue); propertyType = propertyValue.GetType(); // . } properties.Add(propertyName, propertyValue); // . } // . foreach (FieldInfo fieldInfo in sourceType.GetFields()) { AmfMemberAttribute attribute = fieldInfo.GetAttribute<AmfMemberAttribute>(); // AmfMemberAttribute. if (attribute == null) continue; // - . string fieldName = attribute.Name ?? fieldInfo.Name; // . object fieldValue = fieldInfo.GetValue(sourceObject); // . Type fieldType = fieldInfo.FieldType; // . AmfObjectAttribute fieldAttribute = fieldInfo.FieldType.GetAttribute<AmfObjectAttribute>(); // . // ... if (fieldAttribute != null || fieldType.IsDictionary()) { // , . fieldValue = fieldType.IsDictionary() ? GenerateType(fieldValue as IEnumerable<KeyValuePair<string, object>>) : GenerateType(fieldValue); fieldType = fieldValue.GetType(); // . } fields.Add(fieldName, fieldValue); // . } } object targetObject = Activator.CreateInstance(definedType ?? typeBuilder.CreateType()); // . // . foreach (KeyValuePair<string, object> property in properties) targetObject.GetType().GetProperty(property.Key).SetValue(targetObject, property.Value, null); // . foreach (KeyValuePair<string, object> field in fields) targetObject.GetType().GetField(field.Key).SetValue(targetObject, field.Value); return targetObject; }
次に、シリアル化中にオブジェクトの配列のメタデータが正しく保存されることを確認する必要があります。 これを行うには、型生成メソッドの基本的なオーバーロードを記述します。
/// <summary> /// <see cref="AmfObjectAttribute"/>, , <see cref="AmfMemberAttribute"/>. /// </summary> /// <param name="sourceObjects"> .</param> /// <returns></returns> private static object[] GenerateType(object[] sourceObjects) { for (int i = 0; i < sourceObjects.Length; i++) sourceObjects[i] = GenerateType(sourceObjects[i]); // . return sourceObjects; }
これで、サーバーにさらに送信するために、オブジェクトと配列をAMFにシリアル化できます。 ただし、C#にはない連想AMF配列を送信できる機能が必要です。 ここでの連想配列は、 IEnumerable <KeyValuePair <TKey、TValue>辞書のさまざまな実装であり、 Hashtableハッシュテーブルです。AMFは、各辞書キーを配列型のフィールドとして認識します。 この問題の解決策を実装するために、辞書のキーと値に基づいてAMF辞書を正しく生成できる別のオーバーロードを作成します。
/// <summary> /// , . , object, , AMF. /// </summary> /// <param name="sourceType"> .</param> /// <returns></returns> private static bool IsDictionary(this Type sourceType) { Type type0 = typeof(IEnumerable<KeyValuePair<string, object>>); Type type1 = typeof(IDictionary<string, object>); Type type2 = typeof(Dictionary<string, object>); return sourceType.FullName == type0.FullName || sourceType.IsSubclassOf(type0) || sourceType.FullName == type1.FullName || sourceType.IsSubclassOf(type1) || sourceType.FullName == type2.FullName || sourceType.IsSubclassOf(type2); } /// <summary> /// "-", , - . /// </summary> /// <param name="fields"> "-"</param> /// <returns></returns> private static object GenerateType(IEnumerable<KeyValuePair<string, object>> fields) { AssemblyName assemblyName = new AssemblyName("AmfDynamicAssemblyForDictionary"); // . AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); // . ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll"); // . TypeBuilder typeBuilder = moduleBuilder.DefineType(typeof(Array).FullName, TypeAttributes.Public); // . ConstructorBuilder ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes); // . ILGenerator ctorIL = ctor.GetILGenerator(); // MSIL- . ctorIL.Emit(OpCodes.Ldarg_0); // . ctorIL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); // . ctorIL.Emit(OpCodes.Ret); // . // . foreach (KeyValuePair<string, object> pair in fields) { object fieldValue = pair.Value; // . Type fieldType = fieldValue.GetType(); // . // ... if (fieldType.IsDefinedAttribute<AmfObjectAttribute>()) { fieldValue = GenerateType(fieldValue); // , . fieldType = fieldValue.GetType(); // . } typeBuilder.DefineField(pair.Key, fieldType, FieldAttributes.Public); // . } object targetObject = Activator.CreateInstance(typeBuilder.CreateType()); // . // . foreach (KeyValuePair<string, object> pair in fields) targetObject.GetType().GetField(pair.Key).SetValue(targetObject, pair.Value); return targetObject; }
ここで、メソッドを呼び出すたびに個別の動的アセンブリを作成し、そのメタデータをオーバーライドすることに注意してください。これは、ディクショナリのすべてのメタデータを新たに生成するために必要です。そうしないと、しばらくすると、ディクショナリのコピーに、タイプに含まれてはならず、メモリ空間を占有する未定義フィールドが大量に含まれます。
AMFシリアル化
これで、AMFでオブジェクトをシリアル化する準備がすべて整いました。シリアライザーの実装に進みます。
/// <summary> /// AMF. /// </summary> /// <param name="sourceObject"> .</param> /// <param name="version"> AMF.</param> /// <returns></returns> public static byte[] SerializeToAmf(this object sourceObject, ushort version) { using (MemoryStream memoryStream = new MemoryStream()) // . using (AMFSerializer amfSerializer = new AMFSerializer(memoryStream)) // AMF. { AMFMessage amfMessage = new AMFMessage(version); // AMF. AMFBody amfBody = new AMFBody(AMFBody.OnResult, null, GenerateType(sourceObject)); // AMF. amfMessage.AddBody(amfBody); // body AMF. amfSerializer.WriteMessage(amfMessage); // . return memoryStream.ToArray(); // . } } /// <summary> /// AMF3. /// </summary> /// <param name="sourceObject"> .</param> /// <returns></returns> public static byte[] SerializeToAmf(this object sourceObject) => sourceObject.SerializeToAmf(3); /// <summary> /// *.amf. /// </summary> /// <param name="sourceObject"> .</param> /// <param name="path"> .</param> /// <param name="version"> AMF.</param> public static void SerializeToAmf(this object sourceObject, string path, ushort version) => File.WriteAllBytes($"{path}.amf", sourceObject.SerializeToAmf(version)); /// <summary> /// *.amf. AMF 3. /// </summary> /// <param name="sourceObject"> .</param> /// <param name="path"> .</param> public static void SerializeToAmf(this object sourceObject, string path) => sourceObject.SerializeToAmf(path, 3);
実装はほとんど簡単です。唯一の注意点は、AMFのデフォルトバージョン番号が3であるオーバーロードを意図的に作成したことです。Flexを使用する場合、ほとんどの場合AMF3です。つまり、シリアル化メソッドの呼び出しで追加の引数を渡すことは意味がありません。
逆シリアル化を行うには、上記の手順をすべて逆の順序で行う必要があります。
/// <summary> /// AMF. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="sourceBuffer"> .</param> /// <returns></returns> public static T DeserializeFromAmf<T>(this byte[] sourceBuffer) where T : class { using (MemoryStream memoryStream = new MemoryStream(sourceBuffer)) // . using (AMFDeserializer amfDeserializer = new AMFDeserializer(memoryStream)) // AMF. { AMFMessage amfMessage = amfDeserializer.ReadAMFMessage(); // AMF. AMFBody amfBody = amfMessage.GetBodyAt(0); // body AMF. object amfObject = amfBody.Content; // body AMF. Type amfObjectType = amfObject.GetType(); // AMF. // . IEnumerable<Type> types = from type in Assembly.GetExecutingAssembly().GetTypes() where Attribute.IsDefined(type, typeof(AmfObjectAttribute)) select type; Type currentType = null; // . // . foreach (Type type in types) { AmfObjectAttribute attribute = type.GetAttribute<AmfObjectAttribute>(); // . if (attribute == null || attribute.Name != amfObjectType.FullName) continue; // - . currentType = type; // . break; } if (currentType == null) return default(T); // - null. object targetObject = Activator.CreateInstance(currentType); // . // . foreach (PropertyInfo propertyInfo in currentType.GetProperties()) { AmfMemberAttribute attribute = propertyInfo.GetAttribute<AmfMemberAttribute>(); // . if (attribute == null) continue; // - . propertyInfo.SetValue(targetObject, amfObjectType.GetProperty(attribute.Name).GetValue(amfObject, null), null); // . } // . foreach (FieldInfo fieldInfo in currentType.GetFields()) { AmfMemberAttribute attribute = fieldInfo.GetAttribute<AmfMemberAttribute>(); // . if (attribute == null) continue; // - . fieldInfo.SetValue(targetObject, amfObjectType.GetField(attribute.Name).GetValue(amfObject)); // . } return targetObject as T; // T . } } /// <summary> /// *.amf. /// </summary> /// <typeparam name="T"> .</typeparam> /// <param name="obj"> .</param> /// <param name="path"> .</param> /// <returns> AMF.</returns> public static T DeserializeFromAmf<T>(this object obj, string path) where T : class => File.ReadAllBytes($"{path}.amf").DeserializeFromAmf<T>();
次に、AMFでオブジェクトをシリアル化してサーバーに送信する最速の方法を以下に示します。
using (MemoryStream memoryStream = new MemoryStream()) // . using (AMFWriter amfWriter = new AMFWriter(memoryStream)) // AMF. using (WebClient client = new WebClient()) // HTTP- ( HttpWebRequest). { amfWriter.WriteBytes(new CustomAmfObject().SerializeToAmf()); // . client.Headers[HttpRequestHeader.ContentType] = "application/x-amf"; // ContentType . byte[] buffer = client.UploadData(Host, "POST", memoryStream.ToArray()); // . }
以下のスクリーンショットでは、サーバーに送信する準備ができているシリアル化されたオブジェクトの構造を確認できます。
データ送信後のサーバーがオブジェクトがIExternalizableインターフェイスを実装していないと誓う場合、カスタムクラスにIExternalizable実装を実装する必要があります:
[AmfObject("example.game.gameObject")] public class CustomAmfObject : IExternalizable { [AmfMember("x")] public float X { get; set; } [AmfMember("y")] public float Y { get; set; } [AmfMember("z")] public float Z { get; set; } public CustomAmfObject(float x, float y, float z) { X = x; Y = y; Z = z; } public CustomAmfObject() : this(0f, 0f, 0f) { } public void ReadExternal(IDataInput input) { X = input.ReadFloat(); Y = input.ReadFloat(); Z = input.ReadFloat(); } public void WriteExternal(IDataOutput output) { output.WriteFloat(X); output.WriteFloat(Y); output.WriteFloat(Z); } }
テストが実際の条件で示したように、このアプローチでデータをシリアル化するために、IDataOutputの一連のレコードとIDataInputからの読み取りはまったく必要なく、それらのないデータはすべて正しい形式でサーバーに送信されました。特に「呪い」サーバーの場合、このソリューションは非常に便利です。
あとがき
上記の問題を解決する方法は、元のフレームワークのソースコードへの介入を必要としない唯一のものではありません。たとえば、DynamicObject、ExpandoObject、およびその他の動的な
記事に記載されている知識ベースは、ほとんどすべてのタスクのシリアライザー、またはMSILとリフレクションに基づく単純なインタープリター/コンパイラー、およびAMFでのシリアル化の原理をより視覚的に理解するのに十分です。
記事のソースはこちらから入手できます。
NuGet FluorineFx拡張パックはこちらにあります。
AMFをシリアル化するだけで、すべてのFluorineFXをプロジェクトにドラッグする必要がある場合は、ここで、.NET DataContractシリアル化に基づいて、マッピングを使用して実装されたArtemAによって提案されたシリアライザーのバージョンを見つけることができます。
私が提示した資料があなたの役に立つことを願っています。
ご清聴ありがとうございました!