Сериализация анонимных делегатов в С#

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

// 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, но также сериализуется в BLOB в 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);
    }

Ответ 1

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

Ответ 2

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

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

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

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

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

Ответ 4

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

Не было бы локальное состояние предъявлять те же самые проблемы для сериализации?

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

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

Ответ 5

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

Это то, что компилятор С# автоматически выполняет для меня с анонимными функциями. Моя проблема заключается в сериализации этих классов компилятора.

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

По сути, это означает, что вручную делает то, что компилятор С# делает для меня автоматически.

Ответ 6

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

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

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