37 votes

Sérialisation des délégués anonymes en C#

J'essaie de déterminer quels problèmes pourraient être causés par l'utilisation du substitut de sérialisation suivant pour permettre la sérialisation des fonctions/délégations/lambdas anonymes.

// 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;
    }
}

Liste 1 adapté de Démonstration de comptage

Le principal problème auquel je peux penser est que la classe anonyme est un détail interne du compilateur et que sa structure n'est pas garantie de rester constante entre les révisions du .NET Framework. Je suis presque certain que c'est le cas, d'après mes recherches sur le problème similaire des itérateurs.

Contexte

J'étudie la sérialisation des fonctions anonymes. Je m'attendais à ce que cela ne fonctionne pas, mais j'ai découvert que cela fonctionnait dans certains cas. Tant que le lambda ne force pas le compilateur à générer une classe anonyme, tout fonctionne bien.

Une SerializationException est levée si le compilateur exige une classe générée pour mettre en œuvre la fonction anonyme. Cela est dû au fait que la classe générée par le compilateur n'est pas marquée comme sérialisable.

Exemple

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.
        }
    }

Liste 2

C'est similaire au problème d'essayer de sérialiser itérateurs et j'avais trouvé le code suivant lors d'une recherche précédente (voir [countingdemo]) En utilisant le code de Liste 1 et un ISurrogateSelector, j'ai pu sérialiser et désérialiser avec succès le deuxième exemple défaillant.

Objectif

J'ai un système qui est exposé via un service web. Le système a un état complexe mais petit (beaucoup d'objets, pas beaucoup de propriétés par objet). L'état est sauvegardé dans le cache ASP.NET, mais il est également sérialisé en BLOB dans SQL en cas d'expiration du cache. Certains objets doivent exécuter des "événements" arbitraires lorsqu'ils atteignent une certaine condition. Ils ont donc des propriétés acceptant les objets Action/Func. Exemple détourné :

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

Un autre endroit

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

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

        Store.Save(state);
    }

5voto

Avez-vous vu ce billet que j'ai écrit pour faire suite à la démonstration de comptage : http://dotnet.agilekiwi.com/blog/2007/12/update-on-persistent-iterators.html ? Malheureusement, Microsoft a confirmé qu'ils changeront probablement les détails du compilateur (un jour), d'une manière qui risque de causer des problèmes. (par exemple, si vous effectuez une mise à jour vers le nouveau compilateur, vous ne serez pas en mesure de désérialiser les éléments que vous avez enregistrés sous l'ancien (actuel) compilateur).

5voto

David B Points 53123

Certains objets doivent exécuter des "événements" arbitraires atteignant une certaine condition.

Jusqu'à quel point ces événements sont-ils arbitraires ? Peut-on les compter, leur attribuer des identifiants et leur attribuer des références ?

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;
  }  
}

4voto

Marc Gravell Points 482669

L'idée même de sérialiser un délégué est très risquée. Maintenant, un expression pourrait avoir un sens, mais même cela est difficile à exprimer - bien que les échantillons LINQ dynamiques permettent une certaine forme d'expression textuelle.

Qu'est-ce que vous voulez exactement faire avec un délégué sérialisé ? Je ne pense vraiment pas que ce soit une bonne idée...

1voto

David B Points 53123

Mais comme cet état est local, cela pose des problèmes lorsqu'on essaie d'établir une cartographie.

L'état local ne présenterait-il pas exactement les mêmes problèmes pour la sérialisation ?

Supposons que le compilateur et le framework permettent à cela de fonctionner :

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

Je suppose que T et O ont dû être sérialisés aussi. Les méthodes n'ont pas l'état, les instances l'ont.

Plus tard, vous désérialisez la cible. N'obtenez-vous pas de nouvelles copies de t et o ? Ces copies ne seront-elles pas désynchronisées par rapport aux modifications apportées aux t et o originaux ?

Par ailleurs, votre exemple de manuel ne pourrait-il pas être appelé de cette façon ?

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

0voto

Joseph Kingry Points 4594

Une carte de fonctions m'empêcherait d'utiliser l'état local dans les actions/conditions. La seule façon de contourner ce problème serait de créer une classe par fonction nécessitant un état supplémentaire.

C'est ce que le compilateur C# fait automatiquement pour moi avec les fonctions anonymes. Mon problème est la sérialisation de ces classes de compilateur.

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

Essayer de sérialiser ça échouerait. Mais comme cet état est local, cela pose des problèmes lorsqu'on essaie de configurer un mappage. Au lieu de cela, je devrais déclarer quelque chose comme ceci :

[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(); 
    }
}

et ensuite l'utiliser comme ceci :

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

        target.OnWhatever = cmd.Run;

        target.Save();

En fait, il s'agit de faire manuellement ce que le compilateur C# fait automatiquement pour moi.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X