Voidインスタンスの作り方、或いはシリアライザとコンストラクタについて

voidといったら、特別扱いされる構造体です。default(void)なんてない。インスタンスは絶対作れない。作れない。本当に?

var v = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));

Console.WriteLine(v); // System.Void

作れました。というわけで、GetUninitializedObjectはその名前のとおり、コンストラクタをスルーしてオブジェクトを生成します。そのため、voidですら生成できてしまうわけです、恐ろしい。こないだ.NETの標準シリアライザ(XML/JSON)の使い分けまとめという記事でシリアライザ特集をして少し触れましたが、DataContractSerializerで激しく使われています。よって、シリアライズ対象のクラスがコンストラクタ内で激しく色々なところで作用しているようならば、それが呼び出されることはないので注意が必要です。

ただし、DataContractSerializerを使ったからって、必ずしも呼ばれるわけではないです。DataContract属性がついていなければ普通にコンストラクタを呼ぶ。DataContract属性がついていれば、引数のないコンストラクタがあったとしても、コンストラクタを無視する。という挙動になっているようです。ちょっと紛らわしいので、以下のコードは(参照設定があれば)そのままペーストして動くので、是非試してみてください。

using System;
using System.IO;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml.Serialization;

public class EmptyClass
{
    public EmptyClass()
    {
        Console.WriteLine("BANG!");
    }
}

[DataContract]
public class ContractEmptyClass
{
    public ContractEmptyClass()
    {
        Console.WriteLine("BANG!BANG!");
    }
}

[DataContract]
public class NoEmptyConstructorClass
{
    public NoEmptyConstructorClass(int dummy)
    {
        Console.WriteLine("BANG!BANG!BANG!");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // 普通にnewするとBANG!
        Console.WriteLine("New:");
        var e1 = new EmptyClass();

        // Activator.CreateInstanceでnewするのもBANG!
        Console.WriteLine("Activator.CreateInstance:");
        var e2 = Activator.CreateInstance<EmptyClass>();

        // ExpressionTreeでCompileしてもBANG!
        Console.WriteLine("Expression.New");
        var e3 = Expression.Lambda<Func<EmptyClass>>(Expression.New(typeof(EmptyClass))).Compile().Invoke();

        // 何も起こらない(コンストラクタを無視するのでね)
        Console.WriteLine("GetUninitializedObject:");
        var e4 = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(EmptyClass));

        // XmlSerializerでのデシリアライズはBANG!
        Console.WriteLine("XmlSerializer:");
        var e5 = new XmlSerializer(typeof(EmptyClass)).Deserialize(new MemoryStream(Encoding.UTF8.GetBytes("<EmptyClass />")));

        // DataContractSerializerでもBANGって起こるよ!
        Console.WriteLine("DataContractSerializer:");
        var e6 = new DataContractSerializer(typeof(EmptyClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("<EmptyClass xmlns=\"http://schemas.datacontract.org/2004/07/\" />")));

        // DataContractJsonSerializerでも起こるんだ!
        Console.WriteLine("DataContractJsonSerializer:");
        var e7 = new DataContractJsonSerializer(typeof(EmptyClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("{}")));

        // DataContract属性をつけたクラスだと何も起こらない
        Console.WriteLine("DataContract + DataContractSerializer:");
        var e8 = new DataContractSerializer(typeof(ContractEmptyClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("<ContractEmptyClass xmlns=\"http://schemas.datacontract.org/2004/07/\" />")));

        // DataContract属性をつけたクラスだとJsonSerializerのほうも当然何も起こらない
        Console.WriteLine("DataContract + DataContractJsonSerializer:");
        var e9 = new DataContractJsonSerializer(typeof(ContractEmptyClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("{}")));

        // 空コンストラクタのないもの+DataContractSerializerだと何も起こらない
        Console.WriteLine("NoEmptyConstructor + DataContractSerializer:");
        var e10 = new DataContractSerializer(typeof(NoEmptyConstructorClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("<NoEmptyConstructorClass xmlns=\"http://schemas.datacontract.org/2004/07/\" />")));

        // 空コンストラクタのないもの+DataContractJsonSerializerでも何も起こらない
        Console.WriteLine("NoEmptyConstructor + DataContractJsonSerializer:");
        var e11 = new DataContractJsonSerializer(typeof(NoEmptyConstructorClass)).ReadObject(new MemoryStream(Encoding.UTF8.GetBytes("{}")));
    }
}

.NET 4でもSilverlightでも共通です。この挙動は妥当だと思います。DataContract属性を付けた時点で、そのクラスはシリアライズに関して特別な意識を持つ必要がある。コンストラクタ内でシリアライズで復元できない副作用のある処理をすべきではない。逆に、何も付いていない場合は特に意識しなくても大丈夫。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive