C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2009/01/23 01:15
インスタンスが属するクラスをあとから変更する操作をいろいろな言語で - sumim’s smalltalking-tos

直交座標系とか極座標系とかはよくわかってないけど、とりあえず C# でもできます。
まぁ、2つのインスタンスのメモリ上のサイズが等しくない場合はマズいことになりかねませんけどね…。
Cartesian と Polar はサイズが等しいので大丈夫です。

// 追記1 (2009/01/28)
対象オブジェクトのフィールドを辿るとマネージヒープ上のオブジェクトへの参照が含まれている、という場合も、場合によってはマズいことになります。

// 追記2 (2009/01/28)
NyaRuRu さんより、「何が起きても不思議ではない」とご指摘頂きました。
今回のコードは動作しましたが、インスタンスサイズを揃えて、フィールドにマネージヒープ上のオブジェクトへの参照を含めないようにしたとしても、確実に大丈夫だと断言することはできません。また、今回のコードが如何なる時でも確実に動作すると断言することもできません。

using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;

class Cartesian
{
    public Cartesian(double x, double y)
    {
        this.X = x;
        this.Y = y;
    }

    public double X { get; set; }

    public double Y { get; set; }

    public Polar ToPolar()
    {
        return new Polar(Math.Sqrt(X * X + Y * Y), Math.Atan2(Y, X));
    }
}

class Polar
{
    public Polar(double r, double theta)
    {
        this.R = r;
        this.Theta = theta;
    }

    public double R { get; set; }

    public double Theta { get; set; }

    public Cartesian ToCartesian()
    {
        return new Cartesian(R * Math.Cos(Theta), R * Math.Sin(Theta));
    }
}

class Program
{
    static void Transmogrify(object a, object b)
    {
        Type aType = a.GetType();
        Type bType = b.GetType();

        BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
        FieldInfo[] aFieldInfos = aType.GetFields(flags);
        FieldInfo[] bFieldInfos = bType.GetFields(flags);

        var aFields = aFieldInfos.Select(item => new { Key = item, Value = item.GetValue(a) }).ToList();
        var bFields = bFieldInfos.Select(item => new { Key = item, Value = item.GetValue(b) }).ToList();

        ConvertType(a, bType);
        ConvertType(b, aType);

        aFields.ForEach(item => item.Key.SetValue(b, item.Value));
        bFields.ForEach(item => item.Key.SetValue(a, item.Value));
    }

    static unsafe void ConvertType(object target, Type newType)
    {
        IntPtr newTypeHandle = newType.TypeHandle.Value;
        GCHandle targetGCHandle = GCHandle.Alloc(target, GCHandleType.Normal);
        try
        {
            void* entryPointer = (void*)GCHandle.ToIntPtr(targetGCHandle);
            void* targetPointer = *((void**)entryPointer);
            IntPtr* typeHandlePointer = (IntPtr*)targetPointer;
            *typeHandlePointer = newTypeHandle;
        }
        finally
        {
            targetGCHandle.Free();
        }
    }

    static void Main(string[] args)
    {
        Polar pos1 = new Polar(Math.Sqrt(2), Math.PI / 4);
        Polar pos2 = pos1;
        Console.WriteLine(pos1.GetType().Name);             //=> Polar

        Transmogrify(pos1, pos1.ToCartesian());
        Console.WriteLine(pos1.GetType().Name);             //=> Cartesian
        Cartesian pos1AsCart = pos1 as object as Cartesian;
        Console.WriteLine(pos1AsCart.X);                    //=> 1
        Console.WriteLine(pos1AsCart.Y);                    //=> 1
        Console.WriteLine(pos2.GetType().Name);             //=> Cartesian

        Transmogrify(pos1, pos1AsCart.ToPolar());
        Console.WriteLine(pos1.GetType().Name);             //=> Polar
        Console.WriteLine(pos1.R);                          //=> 1.4142135623731
        Console.WriteLine(pos1.Theta / Math.PI);            //=> 0.25

        Console.ReadLine();
    }
}


[関連]
C#と諸々 オブジェクトの型を破壊的に変換
C#と諸々 ガベージコレクションを開始するには


さすがにこれを「できる」と呼ぶのは無理があるような.

>2つのインスタンスのメモリ上のサイズが等しくない場合はマズいことになりかねませんけどね…

サイズが等しくてもまずいことになるケースはあるような.
結局どんなときに正常に動くかの予言ってほんとにできますか?

2009.01.27 08:18 URL | NyaRuRu #1MshU/Gw [ 編集 ]


そっか、フィールドにヒープ上のオブジェクトへの参照が含まれていると、型変換してからフィールド交換するまでの間に GC が発動してしまった場合にとんでもないことになりかねないですね。(GC が参照を辿っていきますからね。)
つまり、メモリ上のサイズが等しく、且つ対象オブジェクトのフィールドを辿っても構造体のフィールドしか含まれていない場合でないとダメ、ですね。

他にも何かダメなケースありそうですかね…?

2009.01.27 10:39 URL | よこけん #Ay6tTHf6 [ 編集 ]


>他にも何かダメなケースありそうですかね…?

そもそも動くと保障されているのを聞いたことがないので,何が起きても不思議ではないかと思いますが.

一応,似たケースでStructLayoutのオーバーラップ問題があります.

[StructLayout(LayoutKind.Explicit)]
struct UnsafeStruct
{
[FieldOffset(0)]
public Polar polor;
[FieldOffset(0)]
public Cartesian cartesian;
}

static void Main(string[] args)
{
var us = new UnsafeStruct();
us.polor = new Polar(Math.Sqrt(2), Math.PI / 4);
Console.WriteLine(us.polor.GetType().Name); //=> Polar
Console.WriteLine(us.cartesian.GetType().Name); //=> Polar
us.cartesian = us.polor.ToCartesian();
Console.WriteLine(us.polor.GetType().Name); //=> Cartesian
Console.WriteLine(us.cartesian.GetType().Name); //=> Cartesian

Console.ReadLine();
}
http://d.hatena.ne.jp/NyaRuRu/20080731/p1

『プログラミングMicrosoft .NET Framework 第2版』だと,参照型を同一オフセットに配置するのは (検証可能ではなくなるものの) 許されると書いてあったかと思いますが,JIS X 3016:2006 §2.21.16 を読むと ObjectRef フィールドの重複は一律エラーっぽく読めるんですよね.
"種類がObjectRef のフィールドは,どのような種類のフィールドに対しても一部又は全体として重なってはならない [ERROR]"。

なんにせよ,現状「できないとは限らない」程度のものを「できる」に拡張するのはちょっと無謀かと.

2009.01.27 11:42 URL | NyaRuRu #1MshU/Gw [ 編集 ]

C# でやるという発想に驚きました
まさか C# / CLI でこういうことをしようとは思わなかった&できそうに思わなかったので、元ネタのエントリに C# 版があるのを見てびっくりしました(笑
これをやろうと思ったのが流石だと思います。

ちなみに、びっくりついでに RealProxy と IRemotingTypeInfo を使ったバージョンを作ってみましたので、参考までにどうぞ: http://d.hatena.ne.jp/saiya_moebius/20090127/1233036932

2009.01.27 15:30 URL | saiya #5Fao79Ls [ 編集 ]


> NyaRuRu さん
なるほど、確かに「確実な保障」は全くないですね。
「提示したコード程度なら動作した」というレベルです。
"恐らく" GC の挙動に気を付ければ大丈夫だという予想はできますが、例えば「実は CLR の実装では、オブジェクトと型ハンドルの関連付けがオブジェクトヘッダー以外にも記録されている」なんてことがありえないと断言することはできませんね。


> saiya さん
なるほど、RealProxy + IRemotingTypeInfo という手もありましたね。
RealProxy 経由でのすげ替えで妥協しちゃえば、結構シンプルにできそうだったので僕もやってみました。

public class MyProxy : RealProxy, IRemotingTypeInfo
{
 public MyProxy()
   : base(typeof(MarshalByRefObject))
 {
 }

 public MarshalByRefObject TargetInstance
 {
   get
   {
     return this.GetUnwrappedServer();
   }
   set
   {
     if (this.TargetInstance != null)
     {
       this.DetachServer();
     }
     this.AttachServer(value);
   }
 }

 public bool CanCastTo(Type fromType, object o)
 {
   return fromType.IsAssignableFrom(o.GetType());
 }

 public string TypeName
 {
   get
   {
     return this.TargetInstance.GetType().FullName;
   }
   set
   {
     throw new NotSupportedException();
   }
 }

 public override IMessage Invoke(IMessage msg)
 {
   IMethodCallMessage methodCallMessage = (IMethodCallMessage)msg;
   MethodBase targetMethod = methodCallMessage.MethodBase;
   object[] args = methodCallMessage.Args;

   ReturnMessage returnMessage;
   try
   {
     object invokeResult = targetMethod.Invoke(this.TargetInstance, args);
     returnMessage = new ReturnMessage(invokeResult, args, args.Length, methodCallMessage.LogicalCallContext, methodCallMessage);
   }
   catch (TargetInvocationException ex)
   {
     returnMessage = new ReturnMessage(ex.InnerException, methodCallMessage);
   }
   return returnMessage;
 }
}

class Program
{
 static void Main(string[] args)
 {
   MyProxy proxy = new MyProxy();

   proxy.TargetInstance = new Polar(Math.Sqrt(2), Math.PI / 4);
   Polar pos1 = (Polar)proxy.GetTransparentProxy();
   Polar pos2 = pos1;
   Console.WriteLine(pos1.GetType().Name);  //=> Polar

   proxy.TargetInstance = pos1.ToCartesian();
   Console.WriteLine(pos1.GetType().Name);  //=> Cartesian
   Cartesian pos1AsCart = pos1 as object as Cartesian;
   Console.WriteLine(pos1AsCart.X);  //=> 1
   Console.WriteLine(pos1AsCart.Y);  //=> 1
   Console.WriteLine(pos2.GetType().Name);  //=> Cartesian

   proxy.TargetInstance = pos1AsCart.ToPolar();
   Console.WriteLine(pos1.GetType().Name);  //=> Polar
   Console.WriteLine(pos1.R);  //=> 1.4142135623731
   Console.WriteLine(pos1.Theta / Math.PI);  //=> 0.25

   Console.ReadLine();
 }
}


後、ContextBoundObject + ProxyAttribute を導入すれば Proxy の明示的な生成も除去できそうに思いましたが、RealProxy のコンストラクタで MarshalByRefObject を渡しちゃうと InitializeServerObject メソッドが上手く動作しないですね^^; かといって適切な型を渡しちゃうと、GetType メソッドが常にその型を返すようになってしまいました。
# まぁ、生成処理が除去できたところで、すげ替えを RealProxy 経由でやってるんじゃ有難さ半減かもですが

2009.01.28 22:28 URL | よこけん #Ay6tTHf6 [ 編集 ]












トラックバックURL↓
http://csharper.blog57.fc2.com/tb.php/250-4880fe41

[dotNet][Csharp] インスタンスが属するクラスをあとから変更する操作を C# で ~RealProxy バージョン~
元ネタ インスタンスが属するクラスをあとから変更する操作をいろいろな言語で - sumim’s smalltalking-tos C#と諸々 インスタンスが属するクラスをあとから変更する操作を C# で 概要 元ネタにある C# 版では、オブジェクトへの参照が持っている型ハンドルを強引に書き換

2009.01.27 15:23 | やこ~ん SuperNova2