26

ストリームをオブジェクトにBinaryFormatter逆シリアル化すると、コンストラクターを呼び出さずに新しいオブジェクトを作成するように見えます。

これはどうですか?なぜ?これを行う.NETの他の何かがありますか?

これがデモです:

[Serializable]
public class Car
{
    public static int constructionCount = 0;

    public Car()
    {
        constructionCount++;
    }
}

public class Test
{
    public static void Main(string[] args)
    {
        // Construct a car
        Car car1 = new Car();

        // Serialize and then deserialize to create a second, identical car
        MemoryStream stream = new MemoryStream();
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, car1);
        stream.Seek(0, SeekOrigin.Begin);
        Car car2 = (Car)formatter.Deserialize(stream);

        // Wait, what happened?
        Console.WriteLine("Cars constructed: " + Car.constructionCount);
        if (car2 != null && car2 != car1)
        {
            Console.WriteLine("But there are actually two.");
        }
    }
}

出力:

Cars constructed: 1
But there are actually two.

4

3 に答える 3

22

コンストラクターの呼び出しが行う (または少なくとも行うべき) ことが 2 つあります。

1 つは、オブジェクト用に一定量のメモリを確保し、オブジェクトが .NET ワールドの残りの部分に対してオブジェクトになるために必要なすべてのハウスキーピングを行うことです (この説明では、一定量のハンドウェーブに注意してください)。

もう 1 つは、おそらくパラメーターに基づいて、オブジェクトを有効な初期状態にすることです。これは、コンストラクター内の実際のコードが行うことです。

逆シリアル化は、を呼び出すことによって最初のステップとほぼ同じことをFormatterServices.GetUninitializedObject行い、次に、フィールドの値をシリアル化中に記録された値と同等になるように設定することによって、2 番目のステップとほぼ同じことを行います (これには、他のオブジェクトの逆シリアル化が必要になる場合があります)。値)。

現在、逆シリアル化によってオブジェクトが置かれている状態は、コンストラクターによって可能な状態に対応していない場合があります。せいぜい無駄で (コンストラクターによって設定されたすべての値が上書きされます)、最悪の場合は危険です (コンストラクターには何らかの副作用があります)。また、不可能な場合もあります (パラメーターを受け取るのはコンストラクターのみです。シリアライゼーションでは、使用する引数を知る方法がありません)。

デシリアライゼーションでのみ使用される特別な種類のコンストラクターと見なすことができます (オブジェクト指向の純粋主義者は、構築しないコンストラクターのアイデアに身震いするでしょう。newメモリに関する限りオーバーライドが機能する方法と、さらに優れたアナロジーがありますが、それでも単なるアナロジーです)。

さて、これは場合によっては問題になる可能性があります。コンストラクターによってのみ設定できるフィールドがあるか、発生させたいreadonly副作用があるかもしれません。

両方の解決策は、シリアル化の動作を でオーバーライドすることISerializableです。これは、への呼び出しに基づいてシリアライズし、デシリアライズするフィールドを指定してISerializable.GetObjectData特定のコンストラクターを呼び出します(このコンストラクターはプライベートにすることもできます。つまり、他のほとんどのコードはそれを認識しません)。したがって、フィールドをデシリアライズして、必要な副作用を持たせることができれば (何をどのようにシリアライズするかを制御するためにあらゆる方法を実行することもできます)。SerializationInfoStreamingContextreadonly

構築時に発生する副作用がデシリアライゼーションで確実に発生するようにするだけであれば、実装でき、デシリアライゼーションが完了したときに呼び出すIDeserializationCallbackことができます。IDeserializationCallback.OnDeserialization

これと同じことを行う他のものについては、.NET には他の形式のシリアル化がありますが、私が知っているのはそれだけです。自分自身を呼び出すことは可能FormatterServices.GetUninitializedObjectですが、後続のコードが生成されたオブジェクトを有効な状態にするという強い保証がある場合を除きます (つまり、同じ並べ替えをシリアル化することによって生成されたデータからオブジェクトを逆シリアル化するときの状況とまったく同じです)。オブジェクトの) そのようなことを行うのは困難であり、診断が非常に難しいバグを生成する良い方法です。

于 2010-11-30T12:40:10.207 に答える
3

問題は、 BinaryFormatter が実際に特定のオブジェクトを作成しているわけではないということです。オブジェクトグラフをメモリに戻しています。オブジェクト グラフは基本的に、メモリ内のオブジェクトの表現です。これは、オブジェクトがシリアル化されたときに作成されました。次に、デシリアライズ呼び出しは、基本的に、そのグラフをオブジェクトとしてメモリ内のオープン ポインターに貼り付け、コードによって実際の状態にキャストします。キャストが間違っていると、例外がスローされます。

あなたの特定の例に関して言えば、実際には 1 台の車しか製造していません。その車の正確な複製を作成しているだけです。ストリームにシリアル化すると、その正確なバイナリ コピーが保存されます。逆シリアル化する場合、何も構築する必要はありません。オブジェクトとしてポインタ値でグラフをメモリに固定し、それを使ってやりたいことを何でもできるようにします。

Car1 != car2 の比較は、 Car が参照型であるため、ポインターの位置が異なるため true です。

なんで?率直に言って、各プロパティなどを取得する必要はなく、バイナリ表現を取得する方が簡単です。

.NET の他の何かがこの同じ手順を使用しているかどうかはわかりません。最も可能性の高い候補は、シリアル化中に何らかの形式でオブジェクトのバイナリを使用するものです。

于 2010-08-17T20:03:37.997 に答える
1

コンストラクターが呼び出されない理由はわかりませんがIDeserializationCallback、回避策として使用します。

また見てください

OnSerializingAttribute

OnSerializedAttribute

OnDeserializingAttribute

OnDeserializedAttribute

于 2010-11-30T12:47:25.567 に答える