通常、オブジェクトに結びついている型を変更することはできません。例えば String オブジェクトを Object クラスにキャストしても、オブジェクト自体は String オブジェクトのままです。
オブジェクトはメモリ上のデータです。このデータには、オブジェクトヘッダーと各フィールドの値が含まれます。オブジェクトヘッダーには、同期ブロックインデックスと型ハンドルが含まれます。
この型ハンドルが、オブジェクトと結びついている型を示します。型ハンドルは、ローダーヒープ (アプリケーションドメイン内にロードされた型情報を格納している領域) 内にある型情報 (Type クラスのインスタンスとは別物) へのポインタです。Object.GetType メソッドは、この型ハンドルを元に Type クラスのインスタンスを取得して返却しています。
つまり、オブジェクトヘッダー内の型ハンドルを書き換えると、オブジェクトに結びついている型を変換することができるわけです。
オブジェクトヘッダーへの直接のアクセスはサポートされていません。ただし、アンマネージドコードを用いれば、アンマネージポインタからメモリを直接参照することができますので、これでアクセスすることが可能です。ちなみに、オブジェクトの参照 (マネージポインタ) は、オブジェクトヘッダー内の型ハンドルのアドレスを示すようになっています。
下記のメソッドは、オブジェクトに結びついている型を変換します。(ビルドするにはアンセーフコードの許可が必要です。)
/// <summary>
/// 指定したオブジェクトを強制的に別の型に変換します。
/// このメソッドはオブジェクトの型を破壊的に変換するため、予期しない動作を引き起こす可能性があります。
/// </summary>
/// <param name="target">対象オブジェクト。</param>
/// <param name="newType">新しい型。</param>
static unsafe void ConvertType(object target, Type newType)
{
// 対象オブジェクトに対する GC ハンドルを生成
GCHandle targetGCHandle = GCHandle.Alloc(target, GCHandleType.Normal);
try
{
// GC ハンドルの実際のエントリへのポインタを取得
void* entryPointer = (void*)GCHandle.ToIntPtr(targetGCHandle);
// エントリにはオブジェクトへの参照が格納されている
void* targetPointer = *((void**)entryPointer);
// オブジェクトへのポインタは、オブジェクトヘッダーの型ハンドル部分を参照している
IntPtr* typeHandlePointer = (IntPtr*)targetPointer;
// 型ハンドルを書き換えることで型を強制的に変換する
*typeHandlePointer = newType.TypeHandle.Value;
}
finally
{
// GC ハンドルの解放
targetGCHandle.Free();
}
}
以下は使用例です。
static void Main(string[] args)
{
object obj = new object();
Console.WriteLine("obj is a {0}", obj.GetType());
ConvertType(obj, typeof(IDisposable));
Console.WriteLine("obj is a {0}", obj.GetType());
Console.ReadLine();
}
以下は実行結果です。
obj is a System.Object
obj is a System.IDisposable
このメソッドを使えば、オブジェクトに結びついている型をどんな型にでも変換できます。抽象クラスやインターフェイスに変換することもできます。ただし、その結果予期せぬ動作を引き起こす可能性があります。例えば、変換前の型より変換後の型の方がサイズが大きいと、他のオブジェクトを破壊してしまう場合があります。
また、このメソッドには一つ欠点があります。オブジェクトへのポインタを取得してから型ハンドルの書き換えを行うまでの間に、ガベージコレクションによってオブジェクトが移動されてしまうと、型変換が正常に行われず、全く無関係なデータを型ハンドルで上書きしてしまう可能性があります。
Blittable 型と呼ばれる型に関しては、GC 時にオブジェクトが移動してしまわないよう固定化することができますが、非 Blittable 型では固定化ができません。
この問題を解決する方法は不明 こちら です。
言うまでもないと思いますが、これはとても危険でトリッキーなメソッドです。普通はまず使うことのないメソッドです。
このメソッドで何か面白いことができたら、是非教えてください。
トラックバックURL↓
http://csharper.blog57.fc2.com/tb.php/212-06f41ecc