2


3

シリアル化可能な匿名デリゲートの代替を作成する

これについてはかなりの投稿があり、すべてFuncデリゲートをシリアル化しようとしています。

しかし、デリゲートの使用が常に明確な場合、誰かが代替案を考えることができますか?

コンストラクターのパラメーターとしてデリゲートを使用する汎用のcreateコマンドがあります。 このデリゲートは、作成コマンドのアイテムを作成します。

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

    public ClientCreateCommand(Func constructor)
    {
        Constructor = constructor;
    }

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

コマンドは次のように使用されます。

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

次に、Historyはコマンドをシリアル化し、サーバーに送信します。 ofcデリゲートをそのままシリアル化できず、例外が発生します。

さて、誰かがシリアライズすることができ、ラムダ式と同じ仕事をする非常に単純なConstructorクラスを考えることができますか? パラメータのリストを受け取り、T型のインスタンスを返すことを意味し、次のように記述できます。

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

Constructorクラスはどのように見えますか? アイデアをありがとう!

1 Answer


2


EDIT(2):完全な実装例をいくつか提供しました。 それらは、以下の「実装1」と「実装2」に分類されます。

デリゲートは基本的に工場です。 ファクトリインターフェイスを定義し、Itemクラスにそのインターフェイスを実装するクラスを作成できます。 以下はその例です。

public interface IFactory
{
    T Create();
}

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

    public string Param2 { get; set; }

    #region IFactory Members

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

    #endregion
}

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

    public CreateCommand(IFactory factory)
    {
        _ItemFactory = factory;
    }

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

このコードは次の方法で利用します。

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

EDIT(1):アプリケーションが必要とする `IFactory`の特定の実装はあなた次第です。 必要なクラスごとに特定のファクトリクラスを作成することも、Activator.CreateInstance関数を使用してインスタンスを動的に作成する何らかの種類のファクトリを作成することも、SpringやStructureMap。

以下は、2つのファクトリー実装を使用する完全な実装例です。 1つの実装では、引数の配列を指定された型のコンストラクターと一致するパラメーターを使用して、任意の型を作成できます。 別の実装では、「Factory」クラスに登録されている型を作成します。

Debug.Assertステートメントは、すべてが意図したとおりに動作することを保証します。 このアプリケーションをエラーなしで実行しました。

実装1

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

public class Factory
{
    static Dictionary> _DelegateCache = new Dictionary>();

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

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

public interface IFactory
{
    T Create();
}

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

    public CreateCommand(IFactory 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 : IFactory
{
    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 Members

    public abstract T Create();

    #endregion
}

// This implementation uses the Activator.CreateInstance function to create an instance
[Serializable]
public class DynamicConstructorFactory : DynamicFactory
{
    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 : DynamicFactory
{
    public MyTypeFactory(params object[] args) : base(args) { }

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

    public override T Create()
    {
        return Factory.CreateMyType(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((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(null);
        var command1 = new CreateCommand(factory1);

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

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

        VerifySerializability(command1);
        VerifySerializability(command2);
        VerifySerializability(command3);
    }

    static void VerifySerializability(CreateCommand 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;
            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
        }
    }
}

EDIT(2):質問を読み直した後、質問者が実際に何を目指していたかについて、より良いアイデアを得たと思います。 基本的に、ラムダ式/匿名デリゲートによって提供される柔軟性を引き続き活用したいが、シリアル化の問題は避けたい。

以下は、T型のインスタンスを返すために使用されるデリゲートを格納するために `Factory`クラスを利用する別の実装例です。

実装2

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

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

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

    ///
    /// Registers a default Create Func delegate for type .
    ///
    public static void Register(Func 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).FullName, typeof(T).FullName));

            return _CreateFunc();
        }
    }

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

            _CreateFuncDictionary[key] = createFunc;
        }
    }

    public static T Create(string key)
    {
        lock (_SyncLock)
        {
            Func 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).FullName, key));
        }
    }
}

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

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

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

    public override void Execute()
    {
        this.Item = Factory.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.Register(() => new DefaultConstructorExample());
        Factory.Register(() => new NoDefaultConstructorExample(5, "ABC", 7.1f));
        Factory.Register(() => new PublicPropertiesExample() { A = 5, B = "ABC", C = 7.1f });

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

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


        // register additional delegates for each type, distinguished by key
        Factory.Register("CreateCommand", () => new DefaultConstructorExample());
        Factory.Register("CreateCommand", () => new NoDefaultConstructorExample(5, "ABC", 7.1f));
        Factory.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("CreateCommand");
        var command5 = new CreateCommandWithKeyedDelegate("CreateCommand");
        var command6 = new CreateCommandWithKeyedDelegate("CreateCommand");

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

    static void VerifySerializability(CreateCommand 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;
            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
        }
    }
}