#c# #serialization #reflection #delegates
#c# #сериализация #отражение #делегаты
Вопрос:
Я пытаюсь сериализовать объекты класса Reference
в конце моей программы. Генерируется исключение сериализации, которое жалуется на то, что «Анализ данных.Ссылка <>c__DisplayClass4» не помечена как сериализуемая.
Изначально у меня было два делегата без Serializable
атрибута, поэтому я попробовал, но это ничего не изменило. Классы Cacheable
и Operation
уже помечены как Serializable
— и на самом деле сериализация обоих из них работала отлично, прежде чем я представил Reference
класс.
Я даже не знаю, что означает c__DisplayClass4. Поэтому я прошу прощения, но я не знаю, какие другие части моего исходного кода объемом 1 мегабайт опубликовать здесь, чтобы помочь вам решить проблему, потому что в конце я бы опубликовал все.
Как я уже сказал, до представления класса все работало нормально Reference
. Поэтому я надеюсь, что проблема каким-то образом локализована в нем.
using System;
using System.Reflection;
namespace DataAnalysis
{
/// <summary>
/// Description of Reference.
/// </summary>
[Serializable]
public class Reference
{
[Serializable]
public delegate void ReferenceSetter(Operation op, Cacheable c);
[Serializable]
public delegate Cacheable ReferenceGetter(Operation op);
readonly ReferenceGetter refGetter;
readonly ReferenceSetter refSetter;
public Reference(ReferenceGetter getter, ReferenceSetter setter)
{
refGetter = getter;
refSetter = setter;
}
public Reference(FieldInfo operationField)
{
refGetter = (op => (Cacheable)operationField.GetValue(op));
refSetter = ((op, value) => operationField.SetValue(op, value));
}
public Cacheable this[Operation op]
{
get {return refGetter(op);}
set {refSetter(op, value);}
}
}
}
Редактировать: я выбрал первое решение Таффера (избегайте использования FieldInfo внутри делегата):
public class Reference
{
public delegate void ReferenceSetter(Operation op, Cacheable c);
public delegate Cacheable ReferenceGetter(Operation op);
readonly FieldInfo opField;
readonly ReferenceGetter refGetter;
readonly ReferenceSetter refSetter;
public Reference(ReferenceGetter getter, ReferenceSetter setter)
{
refGetter = getter;
refSetter = setter;
}
public Reference(FieldInfo operationField)
{
opField = operationField;
}
public Cacheable this[Operation op]
{
get
{
if (opField != null) return (Cacheable)opField.GetValue(op);
else return refGetter(op);
}
set
{
if (opField != null) opField.SetValue(op, value);
else refSetter(op, value);
}
}
}
Еще не доработанный, я, вероятно, наконец-то буду использовать абстрактный Reference
класс с двумя реализациями. Но принцип становится понятным.
Комментарии:
1. Вы пытаетесь сериализовать делегатов, это ссылки на методы, которые не лучше всего подходят для сериализации.
Ответ №1:
Вы получаете ошибку из-за того, как вы инициализируете свои поля во втором конструкторе:
public Reference(FieldInfo operationField)
{
// operationField is captured in the lambda below, which causes to generate an inner class
// where operationField will be a field so can be accessed by the method of the lambda body
refGetter = (op => (Cacheable)operationField.GetValue(op));
refSetter = ((op, value) => operationField.SetValue(op, value));
}
Решение 1:
Не фиксируйте локальные значения и параметры заключающего метода в лямбда-выражении. Поле должно быть параметром делегата.
Решение2:
Реализовать ISerializable
и обеспечить пользовательскую сериализацию:
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("getter", refGetter);
info.AddValue("setter", refSetter);
}
// the special constructor needed for deserialization
private Reference(SerializationInfo info, StreamingContext context)
{
refGetter = (ReferenceGetter)info.GetValue("getter", typeof(ReferenceGetter));
refSetter = (ReferenceSetter)info.GetValue("setter", typeof(ReferenceSetter));
}
Пожалуйста, обратите внимание, что десериализация делегатов нестатических методов может быть проблематичной. Возможно, вам следует проверить Delegate.Method
свойство GetObjectData
и создать исключение, если установщик или получатель является методом экземпляра.
Комментарии:
1. Я решил реализовать первое решение (см. Мою правку в вопросе). Однако мне совершенно непонятно, почему FieldInfo настолько проблематичен при использовании внутри делегата. Есть ли у вас ссылка, которая объясняет это немного более подробно?
2. Это не относится конкретно к
FieldInfo
. В конце концов, лямбды — это сгенерированные методы, и если вы используете в них переменные, которые находятся во внешней области, они должны быть каким-то образом открыты для них. Решение компилятора C # для этого состояло в том, чтобы сгенерировать для них поля в скрытом классе, и тело лямбды обращается к этим захваченным локальным объектам через эти поля. Здесь есть хорошее объяснение: medium.com/@Coder_HarryLee / … — хотя в статье основное внимание уделяется побочному эффекту этой техники.3. Я пытаюсь повторить то, что, как мне кажется, я понял: единственная проблема заключается в том, что этот скрытый класс генерируется автоматически, поэтому у меня нет возможности объявить его с атрибутом Serializable, верно? Это не потому, что я фиксирую локальную переменную, которая каким-то образом выходит за рамки во время (де) сериализации, верно? Ваша рекомендация не захватывать местных жителей заключается только в том, что тогда этот скрытый класс не будет сгенерирован автоматически, верно?