Исключение: анализ данных.Ссылка c__DisplayClass4 не сериализуема

#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, верно? Это не потому, что я фиксирую локальную переменную, которая каким-то образом выходит за рамки во время (де) сериализации, верно? Ваша рекомендация не захватывать местных жителей заключается только в том, что тогда этот скрытый класс не будет сгенерирован автоматически, верно?