C#と諸々

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

2008/12/13 23:57
シリアル化の際に、異なるキーで保存されているセッション値の間で、オブジェクトグラフの共有がされないんだけど…。
InProc の場合はそもそもシリアル化が行われていないので大丈夫。


[再現方法]

aspx に次の二つのコントロールを配置。

<asp:Label runat ="server" ID="Label1" />
<asp:Button runat="server" ID="Button1" Text="Button1" onclick="Button1_Click" />


コードビハインドに次のコードを記述。

protected void Page_Load(object sender, EventArgs e)
{
    if (!this.IsPostBack)
    {
        object o = new object();
        HttpContext.Current.Session["o1"] = o;
        HttpContext.Current.Session["o2"] = o;
    }
}

protected void Button1_Click(object sender, EventArgs e)
{
    object o1 = HttpContext.Current.Session["o1"];
    object o2 = HttpContext.Current.Session["o2"];
    this.Label1.Text = object.ReferenceEquals(o1, o2).ToString();
}


Button1 をクリックすると、セッション状態のモードが InProc なら True と表示されるけど、StateServer だと False と表示される。
もちろん、InProc のように動作して欲しい。
タグ: .NET C# ASP.NET


StateServer はプロセス内ではなく、外部のステートサービスにそれぞれ格納するので、同じインスタンスの参照にならないんじゃないでしょうか。

2008.12.16 17:16 URL | しん #nb.NMF5M [ 編集 ]


> StateServer は 外部のステートサービスにそれぞれ格納するので、

サービスがどのように動作しているのかはよくわからないのですが
まさにその "それぞれ" が納得いかないのです ^^;

例えば、次のようなクラスがあったとします。

public class Hoge
{
 public Fuga F;
}

public class Fuga
{
 public int Value;
}

そして、次のようにセッション状態に格納しときます。
Fuga f = new Fuga();
f.Value = 10;
Hoge h1 = new Hoge();
Hoge h2 = new Hoge();
h1.F = f;
h2.F = f;

HttpContext.Current.Session["h1"] = h1;
HttpContext.Current.Session["h2"] = h2;


これがシリアル化されると、h1.F と h2.F は別のオブジェクトになってしまいます。
つまり、h1.F.Value を 20 に変更しても h2.F.Value は 10 のままになってしまいます。

これだと困るので、仕方なくセッションにディクショナリを一個格納しておいて、
そのディクショナリに各項目を格納するようにしてます。
これならば、オブジェクトグラフは適切に保たれます。

一つのセッション状態で、オブジェクトグラフを共有させないようにしても混乱するだけで、あまりメリットがないような。。。

2008.12.16 17:40 URL | よこけん #Ay6tTHf6 [ 編集 ]


実際にASP.NETアプリが動作しているWebサーバとは別のサーバ上のステートサービスを利用することも可能な仕様になっているみたいなので、独立したステートサービスとしては、セッションIDのやりとりだけで同じインスタンスかどうか認識して管理するのに限界があるんじゃないかと推測したんです。
推測でしかないので、私のほうでも調べてみます。

2008.12.16 20:43 URL | しん #nb.NMF5M [ 編集 ]


というか、わたしは
object.Equals(o1, o2).ToString();
がTrueなだけで十分な気がするのですが、
object.ReferenceEquals(o1, o2).ToString();
がTrueになってほしいのって、何かメリットがあるのでしょうか?

2008.12.16 22:34 URL | しん #nb.NMF5M [ 編集 ]


この問題の本質は、オブジェクトグラフが複製されてしまうことです。この記事で ReferenceEquals を使っているのは、それを簡単に検証する目的でしかないです。

先ほどの僕のコメントに書きましたように、元々は一つのオブジェクト (Fuga オブジェクト) であったものがシリアル化の際に別モノになってしまうと、期待しない結果 (Fuga.Value の変更が他に反映されない) を招きます。

2008.12.16 22:53 URL | よこけん #Ay6tTHf6 [ 編集 ]


> 別のサーバ上のステートサービスを利用することも可能な仕様になっているみたい

なるほど、一つのセッション状態を一箇所に格納するとは限らないという仕様だと、難しいのかもしれませんね…。
# 実際そういう仕様なのかはわからないですが

2008.12.16 23:40 URL | よこけん #Ay6tTHf6 [ 編集 ]


そもそもセッションはオブジェクトグラフを再現する事を目的にはしてないです。
どちらかと言えば値のセマンティクスを元にしています。

セッションではシリアル化されたサイズがパフォーマンスに影響を与えるため、
出来るだけサイズが小さくなるようにシリアル化の方法を最適化しています。

単なるシリアル化可能なオブジェクトは最適化出来ないため、
仕方なく単純にシリアル化して保存しているという感じに近いです。

もちろんこれは直感に反する動作にはなりますが、セッションは本来オブジェクトグラフをそのまま再生する事が目的ではないため、使い方の誤りです。

例えば同一インスタンスを保存する必要があるなら、セッションには共有している最終的なインスタンスを一つ保存するのが本来の使い方です。

セッションを意識せずに活用するような汎用的な仕組みを作るには確かに不便ではありますが。

2008.12.19 09:41 URL | なちゃ #- [ 編集 ]


なるほど、そもそも思想が違うんですね。
まぁ、ディクショナリを一個かませるという回避方法で特に問題ないので、我慢しときます。

2008.12.20 00:03 URL | よこけん #Ay6tTHf6 [ 編集 ]












トラックバックURL↓
http://csharper.blog57.fc2.com/tb.php/242-94f1bcfb