同値の判定とシリアル化

前の人から引き継いだクライアント・サーバアプリケーションで以下のようなコードを見た。

namespace MyService.IF
{
    [Serializable]
    public sealed class MyResult
    {
        public static readonly MyResult OK = new MyResult(1, "OK");
        public static readonly MyResult ERROR = new MyResult(2, "ERROR");

        private Int32 _code;
        private String _name;

        private MyResult(Int32 code, String name)
        {
            _code = code;
            _name = name;
        }

        public override String ToString()
        {
            return String.Format("{0}: {1}", _code, _name);
        }
    }
}

こういう擬似enumのようなものを定義して、

MyResult result = Hoge(); // 何かの処理
if (result == MyResult.OK)
{
  // 処理がOKなら……
}

と結果を判定するというもの。

MyResult のインスタンスは OK と ERROR しか存在しないんだから、== でも問題ないしこっちのほうが効率いいよねという発想なんだろうけど、

IMyService remote = (IMyService)Activator.GetObject(typeof(IMyService), "サービスURI");
MyResult result = remote.DoIt();
if (result == MyResult.OK)
{
  // 処理がOKなら……
}

たとえば上のように .Net Remoting の公開サービスを使った結果だとすると、リモートサービス側で MyResult.OK を返したとしても「処理がOKなら」の部分は実行されない。中身の値が同じだけで異なるインスタンスなんだから当然そうなる。

C# に限ったことではない当たり前の話なんだけど、既存のコードの中ではローカル環境限定で使われていたため問題なかったらしい。ただ、このやり方をコピペしてリモートサービスとのやりとりに使った途端問題が起きましたよ、という。

Equals や operator == をオーバーライドするなどの正攻法でなく、どうしてもこのままでやりたければ、デシリアライズする部分をカスタマイズする必要がある。シリアル化のカスタマイズ を参考にしてみると、こんな感じになるんだろうか?

using System.Runtime.Serialization;
using System.Security.Permissions;

namespace MyService.IF
{
    [Serializable]
    public sealed class MyResult : ISerializable
    {
        public static readonly MyResult OK = new MyResult(1, "OK");
        public static readonly MyResult ERROR = new MyResult(2, "ERROR");

        private Int32 _code;
        private String _name;

        private MyResult(Int32 code, String name)
        {
            _code = code;
            _name = name;
        }

        [SecurityPermissionAttribute(SecurityAction.LinkDemand,
        Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.SetType(typeof(MyResultSerializationHelper));
            info.AddValue("_code", _code);
        }

        internal static MyResult Parse(Int32 code)
        {
            // 手抜き
            if (code == OK._code) { return OK; }
            else if (code == ERROR._code) { return ERROR; }
            else { throw new ArgumentException("invalid code"); }
        }
    }

    [Serializable]
    [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
    [SecurityPermissionAttribute(SecurityAction.LinkDemand,
        Flags = SecurityPermissionFlag.SerializationFormatter)]
    internal sealed class MyResultSerializationHelper : IObjectReference
    {
        internal Int32 _code;

        public Object GetRealObject(StreamingContext context)
        {
            return MyResult.Parse(_code);            
        }
    }
}

セキュリティ関係の属性設定の部分の意味をいまいち理解しきれてない……。