Создать альтернативу сериализуемым анонимным делегатам - PullRequest
2 голосов
/ 20 июля 2010

было довольно много сообщений об этом, все они пытались сериализовать делегат Func.

Но может ли кто-нибудь придумать альтернативу, когда использование делегата всегда понятно?

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

public class CreateCommand<T> : Command
{
    public T Item;
    protected Func<T> Constructor;

    public ClientCreateCommand(Func<T> constructor)
    {
        Constructor = constructor;
    }

    public override void Execute()
    {
        Item = Constructor();
    }
}

Команда используется следующим образом:

var c = new CreateCommand<MyType>( () => Factory.CreateMyType(param1, param2, ...) );
History.Insert(c);

Затем History сериализует команду и отправляет ее на сервер.ofc делегат не может быть сериализован как есть, и мы получаем исключение.

Теперь кто-то может подумать об очень простом классе Constructor, который может быть сериализован и выполняет ту же работу, что и лямбда-выражение?Означает, что он принимает список параметров и возвращает экземпляр типа T, который мы затем можем написать примерно так:

var constructor = new Constructor<MyType>(param1, param2, ...);
var c = new CreateCommand<MyType>(constructor);
History.Insert(c);

Как будет выглядеть класс Constructor?Спасибо за любые идеи!

1 Ответ

2 голосов
/ 20 июля 2010

EDIT (2): я предоставил несколько полных примеров реализации.Ниже они классифицируются как «Реализация 1» и «Реализация 2».

Ваш делегат по сути является фабрикой.Вы можете определить интерфейс фабрики и создать класс, который реализует этот интерфейс для вашего класса Item.Ниже приведен пример:

public interface IFactory<T>
{
    T Create();
}

[Serializable]
public class ExampleItemFactory : IFactory<T>
{
    public int Param1 { get; set; }

    public string Param2 { get; set; }

    #region IFactory<T> Members

    public Item Create()
    {
        return new Item(this.Param1, this.Param2);
    }

    #endregion
}

public class CreateCommand<T> : Command
{
    public T Item;
    protected IFactory<T> _ItemFactory;

    public CreateCommand(IFactory<T> factory)
    {
        _ItemFactory = factory;
    }

    public override void Execute()
    {
        Item = _ItemFactory.Create();
    }
}

Этот код будет использоваться следующим образом:

        IFactory<Item> itemFactory = new ExampleItemFactory { Param1 = 5, Param2 = "Example!" };
        CreateCommand<Item> command = new CreateCommand<Item>(itemFactory);
        command.Execute();

РЕДАКТИРОВАТЬ (1): конкретные реализации IFactory<T>, которые нужны вашему приложению, будутбыть до вас.Вы можете создать определенные классы фабрики для каждого нужного вам класса или создать некую фабрику, которая динамически создает экземпляр, используя, например, функцию Activator.CreateInstance или, возможно, используя некую платформу Inversion of Control, такую ​​как Spring илиStructureMap.

Ниже приведен полный пример реализации, который использует две реализации фабрики.Одна реализация может создать любой тип, заданный массивом аргументов, используя конструктор этого типа с соответствующими параметрами.Другая реализация создает любой тип, который был зарегистрирован в моем классе «Фабрика».

Операторы Debug.Assert гарантируют, что все ведет себя как задумано.Я запустил это приложение без ошибок.

Реализация 1

[Serializable]
public abstract class Command
{
    public abstract void Execute();
}

public class Factory
{
    static Dictionary<Type, Func<object[], object>> _DelegateCache = new Dictionary<Type, Func<object[], object>>();

    public static void Register<T>(Func<object[], object> @delegate)
    {
        _DelegateCache[typeof(T)] = @delegate;
    }

    public static T CreateMyType<T>(params object[] args)
    {
        return (T)_DelegateCache[typeof(T)](args);
    }
}

public interface IFactory<T>
{
    T Create();
}

[Serializable]
public class CreateCommand<T> : Command
{
    public T Item { get; protected set; }
    protected IFactory<T> _ItemFactory;

    public CreateCommand(IFactory<T> itemFactory)
    {
        this._ItemFactory = itemFactory;
    }

    public override void Execute()
    {
        this.Item = this._ItemFactory.Create();
    }
}

// This class is a base class that represents a factory capable of creating an instance using a dynamic set of arguments.
[Serializable]
public abstract class DynamicFactory<T> : IFactory<T>
{
    public object[] Args { get; protected set; }

    public DynamicFactory(params object[] args)
    {
        this.Args = args;
    }

    public DynamicFactory(int numberOfArgs)
    {
        if (numberOfArgs < 0)
            throw new ArgumentOutOfRangeException("numberOfArgs", "The numberOfArgs parameter must be greater than or equal to zero.");

        this.Args = new object[numberOfArgs];
    }

    #region IFactory<T> Members

    public abstract T Create();

    #endregion
}

// This implementation uses the Activator.CreateInstance function to create an instance
[Serializable]
public class DynamicConstructorFactory<T> : DynamicFactory<T>
{
    public DynamicConstructorFactory(params object[] args) : base(args) { }

    public DynamicConstructorFactory(int numberOfArgs) : base(numberOfArgs) { }

    public override T Create()
    {
        return (T)Activator.CreateInstance(typeof(T), this.Args);
    }
}

// This implementation uses the Factory.CreateMyType function to create an instance
[Serializable]
public class MyTypeFactory<T> : DynamicFactory<T>
{
    public MyTypeFactory(params object[] args) : base(args) { }

    public MyTypeFactory(int numberOfArgs) : base(numberOfArgs) { }

    public override T Create()
    {
        return Factory.CreateMyType<T>(this.Args);
    }
}

[Serializable]
class DefaultConstructorExample
{
    public DefaultConstructorExample()
    {
    }
}

[Serializable]
class NoDefaultConstructorExample
{
    public NoDefaultConstructorExample(int a, string b, float c)
    {
    }
}

[Serializable]
class PrivateConstructorExample
{
    private int _A;
    private string _B;
    private float _C;

    private PrivateConstructorExample()
    {
    }

    public static void Register()
    {
        // register a delegate with the Factory class that will construct an instance of this class using an array of arguments
        Factory.Register<PrivateConstructorExample>((args) =>
            {
                if (args == null || args.Length != 3)
                    throw new ArgumentException("Expected 3 arguments.", "args");

                if (!(args[0] is int))
                    throw new ArgumentException("First argument must be of type System.Int32.", "args[0]");

                if (!(args[1] is string))
                    throw new ArgumentException("Second argument must be of type System.String.", "args[1]");

                if (!(args[2] is float))
                    throw new ArgumentException("Third argument must be of type System.Single.", "args[2]");

                var instance = new PrivateConstructorExample();

                instance._A = (int)args[0];
                instance._B = (string)args[1];
                instance._C = (float)args[2];

                return instance;
            });
    }
}

class Program
{
    static void Main(string[] args)
    {
        var factory1 = new DynamicConstructorFactory<DefaultConstructorExample>(null);
        var command1 = new CreateCommand<DefaultConstructorExample>(factory1);

        var factory2 = new DynamicConstructorFactory<NoDefaultConstructorExample>(3);
        factory2.Args[0] = 5;
        factory2.Args[1] = "ABC";
        factory2.Args[2] = 7.1f;
        var command2 = new CreateCommand<NoDefaultConstructorExample>(factory2);

        PrivateConstructorExample.Register(); // register this class so that it can be created by the Factory.CreateMyType function
        var factory3 = new MyTypeFactory<PrivateConstructorExample>(3);
        factory3.Args[0] = 5;
        factory3.Args[1] = "ABC";
        factory3.Args[2] = 7.1f;
        var command3 = new CreateCommand<PrivateConstructorExample>(factory3);

        VerifySerializability<DefaultConstructorExample>(command1);
        VerifySerializability<NoDefaultConstructorExample>(command2);
        VerifySerializability<PrivateConstructorExample>(command3);
    }

    static void VerifySerializability<T>(CreateCommand<T> originalCommand)
    {
        var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        using (var stream = new System.IO.MemoryStream())
        {
            System.Diagnostics.Debug.Assert(originalCommand.Item == null); // assert that originalCommand does not yet have a value for Item
            serializer.Serialize(stream, originalCommand); // serialize the originalCommand object

            stream.Seek(0, System.IO.SeekOrigin.Begin); // reset the stream position to the beginning for deserialization

            // deserialize
            var deserializedCommand = serializer.Deserialize(stream) as CreateCommand<T>;
            System.Diagnostics.Debug.Assert(deserializedCommand.Item == null); // assert that deserializedCommand still does not have a value for Item
            deserializedCommand.Execute();
            System.Diagnostics.Debug.Assert(deserializedCommand.Item != null); // assert that deserializedCommand now has a value for Item
        }
    }
}

РЕДАКТИРОВАТЬ (2): После перечитывания вопроса, я думаю, я получил лучшее представление о том, что на самом деле пытался спрашивающийчтобы получить в.По сути, мы все еще хотим использовать преимущества гибкости, предоставляемой лямбда-выражениями / анонимными делегатами, но избегаем проблем с сериализацией.

Ниже приведен другой пример реализации, который использует класс Factory<T> для хранения делегатов, используемых для возврата экземпляров.типа T.

Реализация 2

[Serializable]
public abstract class Command
{
    public abstract void Execute();
}

[Serializable]
public abstract class CreateCommand<T> : Command
{
    public T Item { get; protected set; }
}

public class Factory<T>
{
    private static readonly object _SyncLock = new object();
    private static Func<T> _CreateFunc;
    private static Dictionary<string, Func<T>> _CreateFuncDictionary;

    /// <summary>
    /// Registers a default Create Func delegate for type <typeparamref name="T"/>.
    /// </summary>
    public static void Register(Func<T> createFunc)
    {
        lock (_SyncLock)
        {
            _CreateFunc = createFunc;
        }
    }

    public static T Create()
    {
        lock (_SyncLock)
        {
            if(_CreateFunc == null)
                throw new InvalidOperationException(string.Format("A [{0}] delegate must be registered as the default delegate for type [{1}]..", typeof(Func<T>).FullName, typeof(T).FullName));

            return _CreateFunc();
        }
    }

    /// <summary>
    /// Registers a Create Func delegate for type <typeparamref name="T"/> using the given key.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="createFunc"></param>
    public static void Register(string key, Func<T> createFunc)
    {
        lock (_SyncLock)
        {
            if (_CreateFuncDictionary == null)
                _CreateFuncDictionary = new Dictionary<string, Func<T>>();

            _CreateFuncDictionary[key] = createFunc;
        }
    }

    public static T Create(string key)
    {
        lock (_SyncLock)
        {
            Func<T> createFunc;
            if (_CreateFuncDictionary != null && _CreateFuncDictionary.TryGetValue(key, out createFunc))
                return createFunc();
            else
                throw new InvalidOperationException(string.Format("A [{0}] delegate must be registered with the given key \"{1}\".", typeof(Func<T>).FullName, key));
        }
    }
}

[Serializable]
public class CreateCommandWithDefaultDelegate<T> : CreateCommand<T>
{
    public override void Execute()
    {
        this.Item = Factory<T>.Create();
    }
}

[Serializable]
public class CreateCommandWithKeyedDelegate<T> : CreateCommand<T>
{
    public string CreateKey { get; set; }

    public CreateCommandWithKeyedDelegate(string createKey)
    {
        this.CreateKey = createKey;
    }

    public override void Execute()
    {
        this.Item = Factory<T>.Create(this.CreateKey);
    }
}

[Serializable]
class DefaultConstructorExample
{
    public DefaultConstructorExample()
    {
    }
}

[Serializable]
class NoDefaultConstructorExample
{
    public NoDefaultConstructorExample(int a, string b, float c)
    {
    }
}

[Serializable]
class PublicPropertiesExample
{
    public int A { get; set; }
    public string B { get; set; }
    public float C { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        // register delegates for each type
        Factory<DefaultConstructorExample>.Register(() => new DefaultConstructorExample());
        Factory<NoDefaultConstructorExample>.Register(() => new NoDefaultConstructorExample(5, "ABC", 7.1f));
        Factory<PublicPropertiesExample>.Register(() => new PublicPropertiesExample() { A = 5, B = "ABC", C = 7.1f });

        // create commands
        var command1 = new CreateCommandWithDefaultDelegate<DefaultConstructorExample>();
        var command2 = new CreateCommandWithDefaultDelegate<DefaultConstructorExample>();
        var command3 = new CreateCommandWithDefaultDelegate<DefaultConstructorExample>();

        // verify that each command can be serialized/deserialized and that the creation logic works
        VerifySerializability<DefaultConstructorExample>(command1);
        VerifySerializability<DefaultConstructorExample>(command2);
        VerifySerializability<DefaultConstructorExample>(command3);


        // register additional delegates for each type, distinguished by key
        Factory<DefaultConstructorExample>.Register("CreateCommand", () => new DefaultConstructorExample());
        Factory<NoDefaultConstructorExample>.Register("CreateCommand", () => new NoDefaultConstructorExample(5, "ABC", 7.1f));
        Factory<PublicPropertiesExample>.Register("CreateCommand", () => new PublicPropertiesExample() { A = 5, B = "ABC", C = 7.1f });

        // create commands, passing in the create key to the constructor
        var command4 = new CreateCommandWithKeyedDelegate<DefaultConstructorExample>("CreateCommand");
        var command5 = new CreateCommandWithKeyedDelegate<DefaultConstructorExample>("CreateCommand");
        var command6 = new CreateCommandWithKeyedDelegate<DefaultConstructorExample>("CreateCommand");

        // verify that each command can be serialized/deserialized and that the creation logic works
        VerifySerializability<DefaultConstructorExample>(command4);
        VerifySerializability<DefaultConstructorExample>(command5);
        VerifySerializability<DefaultConstructorExample>(command6);
    }

    static void VerifySerializability<T>(CreateCommand<T> originalCommand)
    {
        var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        using (var stream = new System.IO.MemoryStream())
        {
            System.Diagnostics.Debug.Assert(originalCommand.Item == null); // assert that originalCommand does not yet have a value for Item
            serializer.Serialize(stream, originalCommand); // serialize the originalCommand object

            stream.Seek(0, System.IO.SeekOrigin.Begin); // reset the stream position to the beginning for deserialization

            // deserialize
            var deserializedCommand = serializer.Deserialize(stream) as CreateCommand<T>;
            System.Diagnostics.Debug.Assert(deserializedCommand.Item == null); // assert that deserializedCommand still does not have a value for Item
            deserializedCommand.Execute();
            System.Diagnostics.Debug.Assert(deserializedCommand.Item != null); // assert that deserializedCommand now has a value for Item
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...