Сериализация анонимных делегатов в С#
Я пытаюсь определить, какие проблемы могут быть вызваны использованием следующего суррогата сериализации, чтобы разрешить сериализацию анонимных функций /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 <... > .
Основы дерева выражений
Поздние привязки с деревьями выражений