Сериализация анонимных делегатов в C # - PullRequest
38 голосов
/ 26 ноября 2008

Я пытаюсь определить, какие проблемы могут быть вызваны с помощью следующего суррогата сериализации, чтобы включить сериализацию анонимных функций / делегата / лямбды.

// 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 адаптирован из Демо-счет

Основная проблема, о которой я могу подумать, может заключаться в том, что анонимный класс является внутренней деталью компилятора, и его структура не гарантируется неизменной между версиями .NET Framework. Я вполне уверен, что это случай, основанный на моем исследовании аналогичной проблемы с итераторами.

Фон

Я расследую сериализацию анонимных функций. Я ожидал, что это не сработает, но обнаружил, что это сработало в некоторых случаях. Пока лямбда не * заставляет компилятор генерировать анонимный класс, все работает нормально.

Выдается исключение 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 и ISurrogateSelector Мне удалось успешно сериализовать и десериализовать второй неудачный пример.

Цель

У меня есть система, которая открывается через веб-сервис. Система имеет сложное, но небольшое состояние (много объектов, не много свойств на объект). Состояние сохраняется в кэше ASP.NET, но также сериализуется в большой двоичный объект в SQL в случае истечения срока действия кэша. Некоторые объекты должны выполнять произвольные «события» при достижении некоторого условия. Следовательно, у них есть свойства, принимающие объекты 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);
    }

Ответы [ 6 ]

5 голосов
/ 23 декабря 2008

Вы видели этот пост, который я написал как продолжение к CountingDemo: http://dotnet.agilekiwi.com/blog/2007/12/update-on-persistent-iterators.html? К сожалению, Microsoft подтвердила, что, вероятно, изменит детали компилятора (однажды) таким образом, что это может вызвать проблемы. (например, f / при обновлении до нового компилятора вы не сможете десериализовать материал, сохраненный в старом (текущем) компиляторе.)

5 голосов
/ 02 декабря 2008

Некоторые объекты должны выполнять произвольные «события», достигающие некоторого условия.

Насколько произвольны эти события? Могут ли они быть подсчитаны, присвоены идентификаторы и сопоставлены с референциальной?

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;
  }  
}
3 голосов
/ 27 ноября 2008

Сама идея сериализации делегата очень рискованна. Теперь выражение может иметь смысл, но даже это трудно выразить - хотя примеры динамического LINQ каким-то образом позволяют использовать форму текстового выражения.

Что именно вы хотите сделать с сериализованным делегатом? Я действительно не думаю, что это хорошая идея ...

1 голос
/ 03 декабря 2008

Поскольку это локальное состояние, это приводит к проблемам при попытке установить сопоставление.

Разве местное государство не представляет те же проблемы для сериализации?

Предположим, что компилятор и фреймворк позволили этому работать:

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();
0 голосов
/ 09 июля 2009

Я не на 100% в этом, но я считаю, что если вы хотите «сохранить» делегат или некоторый код в базе данных, который может быть довольно динамичным, вам нужно создать выражение, тогда вы скомпилировать выражение в Func <...>.

Основы дерева выражений

Вызовы с поздним связыванием с деревьями выражений

0 голосов
/ 03 декабря 2008

Функциональная карта помешала бы мне использовать местное состояние в действии / условиях. Единственный способ обойти это - создать класс для каждой функции, который требует дополнительного состояния.

Это то, что компилятор 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 # делает для меня автоматически

...