web-dev-qa-db-ja.com

C#で匿名デリゲートをシリアル化する

次のシリアル化サロゲートを使用して無名関数/デリゲート/ラムダのシリアル化を有効にすることで、どのような問題が発生する可能性があるかを判断しようとしています。

// see http://msdn.Microsoft.com/msdnmag/issues/02/09/net/#S3
class NonSerializableSurrogate : ISerializationSurrogate
{
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        foreach (FieldInfo f in obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            info.AddValue(f.Name, f.GetValue(obj));
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context,
                                ISurrogateSelector selector)
    {
        foreach (FieldInfo f in obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            f.SetValue(obj, info.GetValue(f.Name, f.FieldType));
        return obj;
    }
}  

リスト1から適応カウントデモ

私が考えることができる主な問題は、匿名クラスが内部コンパイラの詳細であり、その構造が.NETFrameworkのリビジョン間で一定であることが保証されていないことです。これは、イテレータに関する同様の問題に関する私の調査に基づいたケースであると確信しています。

バックグラウンド

私は無名関数のシリアル化を調査しています。これが機能しないことを期待していましたが、場合によっては機能することがわかりました。ラムダがコンパイラに匿名クラスを生成させない限り、すべてが正常に機能します。

コンパイラが無名関数を実装するために生成されたクラスを必要とする場合、SerializationExceptionがスローされます。これは、コンパイラによって生成されたクラスがシリアル化可能としてマークされていないためです。

namespace Example
{
    [Serializable]
    class Other
    {
        public int Value;
    }

    [Serializable]
    class Program
    {
        static void Main(string[] args)
        {
            MemoryStream m = new MemoryStream();
            BinaryFormatter f = new BinaryFormatter();

            // Example 1
            Func<int> succeeds = () => 5;
            f.Serialize(m, succeeds);

            // Example 2
            Other o = new Other();
            Func<int> fails = () => o.Value;
            f.Serialize(m, fails); // throws SerializationException - Type 'Example.Program+<>c__DisplayClass3' in Assembly 'Example, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
        }
    }

リスト2

これは、イテレータをシリアル化しようとする問題に似ており、以前の検索で次のコードを見つけました( countingdemo を参照)-からのコードを使用リスト1およびISurrogateSelector2番目の失敗した例を正常にシリアル化および逆シリアル化することができました。

目的

Webサービスを介して公開されるシステムがあります。システムの状態は複雑ですが小さいです(オブジェクトが多く、オブジェクトごとのプロパティは多くありません)。状態はASP.NETキャッシュに保存されますが、キャッシュの有効期限が切れた場合はSQLのBLOBにもシリアル化されます。一部のオブジェクトは、何らかの条件に達したときに任意の「イベント」を実行する必要があります。したがって、それらにはAction/Funcオブジェクトを受け入れるプロパティがあります。考案された例:

    class Command
    {
        public Command(Action action, Func<bool> condition);
    }

どこか別の場所

    void DoSomethingWithThing(Thing thing)
    {
        state = Store.GetCurrentState();

        Command cmd = new Command(() => thing.Foo(), () => thing.IsReady())
        state.Add(cmd);

        Store.Save(state);
    }
37
Joseph Kingry

CountingDemoのフォローアップとして書いたこの投稿を見ましたか: http://dotnet.agilekiwi.com/blog/2007/12/update-on-persistent-iterators.html ?残念ながら、Microsoftは、問題を引き起こす可能性のある方法で、コンパイラの詳細を(1日で)変更する可能性があることを確認しています。 (たとえば、f /新しいコンパイラに更新すると、古い(現在の)コンパイラで保存したものを逆シリアル化することはできません。)

5
John Rusk

一部のオブジェクトは、何らかの条件に達する任意の「イベント」を実行する必要があります。

これらのイベントはどれほど恣意的ですか?それらをカウントし、IDを割り当て、参照にマッピングできますか?

public class Command<T> where T : ISerializable
{
  T _target;
  int _actionId;
  int _conditionId;

  public Command<T>(T Target, int ActionId, int ConditionId)
  {
    _target = Target;
    _actionId = ActionId;
    _conditionId = ConditionId;
  }

  public bool FireRule()
  {
    Func<T, bool> theCondition = conditionMap.LookupCondition<T>(_conditionId)
    Action<T> theAction = actionMap.LookupAction<T>(_actionId);

    if (theCondition(_target))
    {
      theAction(_target);
      return true;
    }
    return false;
  }  
}
5
Amy B

デリゲートをシリアル化するという考え全体は非常に危険です。さて、は理にかなっているかもしれませんが、それでも表現するのは難しいです-動的LINQサンプルは、テキストベースの式の形式を許可するために何らかの方法で使用されます。

シリアル化されたデリゲートでdoしたいのは正確には何ですか?これは良い考えではないと思います...

4
Marc Gravell

この状態はローカルであるため、マッピングを設定しようとすると問題が発生します。

ローカル状態は、シリアル化に関してまったく同じ問題を提示しませんか?

コンパイラとフレームワークがこれを機能させたと仮定します。

Other o = FromSomeWhere();
Thing t = OtherPlace();
target.OnWhatever = () => t.DoFoo() + o.DoBar();
target.Save();

Tとoもシリアル化する必要があったと思います。メソッドには状態がありませんが、インスタンスにはあります。

後で、ターゲットを逆シリアル化します。 tとoの新しいコピーを入手しませんか?これらのコピーは、元のtおよびoへの変更と同期していませんか?

また:あなたの手動の例はこのように呼ぶことができませんでしたか?

Other o = FromSomeWhere();
Thing t = OtherPlace();
target.OnWhatever = new DoFooBar() {Other = o, Thing = t} .Run;
target.Save();
1
Amy B

私はこれに100%取り組んでいるわけではありませんが、デリゲートまたはかなり動的なコードをデータベースに「保存」したい場合は、式を作成してから式をコンパイルできると思います。 Func <...>に。

式ツリーの基本

式ツリーを使用したレイトバウンド呼び出し

0
viggity

関数マップを使用すると、アクション/条件でローカル状態を使用できなくなります。これを回避する唯一の方法は、追加の状態を必要とする関数ごとにクラスを作成することです。

これは、C#コンパイラが無名関数を使用して自動的に実行していることです。私の問題は、これらのコンパイラクラスのシリアル化です。

        Other o = FromSomeWhere();
        Thing t = OtherPlace();
        target.OnWhatever = () => t.DoFoo() + o.DoBar();
        target.Save();c

それをシリアル化しようとすると失敗します。この状態はローカルであるため、マッピングを設定しようとすると問題が発生します。代わりに、次のように宣言する必要があります。

[Serializable]
abstract class Command<T>
{
    public abstract T Run();
}

class DoFooBar : Command<int>
{
    public Other Other { get; set; }
    public Thing Thing { get; set; }

    public override int Run()
    {
        return Thing.DoFoo() + Other.DoBar(); 
    }
}

そして、次のように使用します。

        DoFooBar cmd = new DoFooBar();
        cmd.Other = FromSomewhere();
        cmd.Thing = OtherPlace();

        target.OnWhatever = cmd.Run;

        target.Save();

基本的に、これが意味するのは、C#コンパイラが自動的に実行していることを手動で実行することです。

0
Joseph Kingry