Подтвердить что ты не робот

Как исключить несериализуемых наблюдателей из [Serializable] исполнителя INotifyPropertyChanged?

У меня есть почти сто классов сущностей, которые выглядят так:

[Serializable]
public class SampleEntity : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return this.name; }
        set { this.name = value; FirePropertyChanged("Name"); }
    }

    [field:NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;

    private void FirePropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this,
                new PropertyChangedEventArgs(propertyName));
    }
}

Обратите внимание на атрибут [field:NonSerialized] на PropertyChanged. Это необходимо, так как некоторые наблюдатели (в моем случае - сетка, отображающая объекты для издания) не могут быть сериализуемыми, а сущность должна быть сериализуема, поскольку она предоставляется - посредством удаленного доступа - приложением, запущенным на машине сепаратора.

Это решение отлично подходит для тривиальных случаев. Однако возможно, что некоторые из наблюдателей [Serializable], и их нужно будет сохранить. Как мне это сделать?

Решения, которые я рассматриваю:

  • full ISerializable - пользовательская сериализация требует написания большого количества кода, я бы предпочел не делать этого
  • используя атрибуты [OnSerializing] и [OnDeserializing] для сериализации PropertyChanged вручную, но эти вспомогательные методы предоставляют только SerializationContext, которые AFAIK не хранит данные сериализации (SerializationInfo делает это)
4b9b3361

Ответ 1

Вы правы, что первый вариант - большая работа. Хотя это потенциально может дать вам более эффективную реализацию, это значительно усложнит ваши сущности. Учтите, что если у вас есть базовый класс Entity, который реализует ISerializable, все подклассы также должны вручную реализовать сериализацию!

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

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var entity = new Entity();
            entity.PropertyChanged += new SerializableHandler().PropertyChanged;
            entity.PropertyChanged += new NonSerializableHandler().PropertyChanged;

            Console.WriteLine("Before serialization:");
            entity.Name = "Someone";

            using (var memoryStream = new MemoryStream())
            {
                var binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(memoryStream, entity);
                memoryStream.Position = 0;
                entity = binaryFormatter.Deserialize(memoryStream) as Entity;
            }

            Console.WriteLine();
            Console.WriteLine("After serialization:");
            entity.Name = "Kent";

            Console.WriteLine();
            Console.WriteLine("Done - press any key");
            Console.ReadKey();
        }

        [Serializable]
        private class SerializableHandler
        {
            public void PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                Console.WriteLine("  Serializable handler called");
            }
        }

        private class NonSerializableHandler
        {
            public void PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                Console.WriteLine("  Non-serializable handler called");
            }
        }
    }

    [Serializable]
    public class Entity : INotifyPropertyChanged
    {
        private string _name;
        private readonly List<Delegate> _serializableDelegates;

        public Entity()
        {
            _serializableDelegates = new List<Delegate>();
        }

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        [field:NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        [OnSerializing]
        public void OnSerializing(StreamingContext context)
        {
            _serializableDelegates.Clear();
            var handler = PropertyChanged;

            if (handler != null)
            {
                foreach (var invocation in handler.GetInvocationList())
                {
                    if (invocation.Target.GetType().IsSerializable)
                    {
                        _serializableDelegates.Add(invocation);
                    }
                }
            }
        }

        [OnDeserialized]
        public void OnDeserialized(StreamingContext context)
        {
            foreach (var invocation in _serializableDelegates)
            {
                PropertyChanged += (PropertyChangedEventHandler)invocation;
            }
        }
    }
}

Ответ 2

Согласиться с Rich L. [field: NonSerialized] решает проблему с сериализацией объекта с встроенным уведомлением об изменении свойства. Фрагмент золота точно. Благодаря