Question

I want to use a list as storing actions to a framework while making async operations.
Then later when the framework synchronizes, then I will loop trought the list and perform the actions.

Is there a way to do this, with this in focus:
- No Reflection/DynamicInvoke.
- Not creating classes/structs for each new method to call.
- Type safe.
- store the actions in a list.
- store different kinds of methods and parameters.
- Execute the list later.

The reason I don't want to use reflection, is because it is a performance concern. It is gonna be use very frequently.
In this case, it is game related, but the code can be used as an allround, and would be excellent for multithreading, if reflection/DynamicInvoke could be avoided.

If this is not possible, then are there other nice alternatives?

I've made an example in code, but it uses reflection and isn't type safe.
Basicly the steps are:
1. Populate a list with methods with multiple different parameters.
2. Loop through the list & execute all methods with there parameters.
3. Clear the list for the next cycle.

{
    struct MyDelayedCaller
    {
        public Delegate TheTarget;
        public object[] MyParameters;

        public MyDelayedCaller(Delegate target, object[] parameters)
        {
            TheTarget = target;
            MyParameters = parameters;
        }
    }

    List<MyDelayedCaller> Temporary = new List<MyDelayedCaller>();
    void Update()
    {
        //something happened and another class needs to know
        //but it will have to wait for the sync so as to not cause any treading problems

        Temporary.Add(new MyDelayedCaller(new DelDoSomething1(DoSomething1), new object[] { 10, false }));
        Temporary.Add(new MyDelayedCaller(new DelDoSomething1(DoSomething1), new object[] { 11, true }));

        Temporary.Add(new MyDelayedCaller(new DelDoSomething3(DoSomething3), new object[] { "Some text" }));
        Temporary.Add(new MyDelayedCaller(new DelDoSomething2(DoSomething2), new object[] { 1, 9999, 0.4f }));
    }
    void Sync()
    {
        foreach (var item in Temporary)
        {
            item.TheTarget.DynamicInvoke(item.MyParameters);
        }
        Temporary.Clear();
    }

    delegate void DelDoSomething1(int index, bool alive);
    void DoSomething1(int index, bool alive)
    {

    }
    delegate void DelDoSomething2(int index, int amount, float scale);
    void DoSomething2(int index, int amount, float scale)
    {

    }
    delegate void DelDoSomething3(string text);
    void DoSomething3(string text)
    {

    }
}
Était-ce utile?

La solution 2

I would go with following:

  1. IMyDelayedCaller interface:

    interface IMyDelayedCaller
    {
        void Invoke();
    }
    
  2. Set of MyDelayedCaller generic classes:

    class MyDelayedCaller<T1> : IMyDelayedCaller
    {
        private Action<T1> _target;
        public T1 _param;
    
        public MyDelayedCaller(Action<T1> target, T1 parameter)
        {
            _target = target;
            _param = parameter;
        }
    
        public void Invoke()
        {
            _target(_param);
        }
    }
    
    class MyDelayedCaller<T1, T2> : IMyDelayedCaller
    {
        private Action<T1, T2> _target;
        public T1 _param1;
        public T2 _param2;
    
        public MyDelayedCaller(Action<T1, T2> target, T1 param1, T2 param2)
        {
            _target = target;
            _param1 = param1;
            _param2 = param2;
        }
    
        public void Invoke()
        {
            _target(_param1, _param2);
        }
    }
    

    I only showed for up to 2 parameters, you can make more if you need.

  3. Change your list to List<IMyDelayedCaller>:

    List<IMyDelayedCaller> Temporary = new List<IMyDelayedCaller>();
    
  4. Add items to the list with compile time type safety:

    Temporary.Add(new MyDelayedCaller<int, bool>(DoSomething1, 10, true));
    Temporary.Add(new MyDelayedCaller<string>(DoSomething3, "Some text"));
    
  5. Invoke using interface method:

    foreach (var item in Temporary)
    {
        item.Invoke();
    }
    Temporary.Clear();
    

You can make stop 4. easier by providing static class which will allow your type parameters be inferred by compiler:

static class MyDelayedCaller
{
    public static MyDelayedCaller<T1> Create<T1>(Action<T1> target, T1 param)
    {
        return new MyDelayedCaller<T1>(target, param1);
    }

    public static MyDelayedCaller<T1, T2> Create<T1, T2>(Action<T1, T2> target, T1 param1, T2 param2)
    {
        return new MyDelayedCaller<T1, T2>(target, param1, param2);
    }
}

and usage:

Temporary.Add(MyDelayedCaller.Create(DoSomething1, 10, true));
Temporary.Add(MyDelayedCaller.Create(DoSomething3, "Some text"));

Autres conseils

I hope I understand the question because the answer seems so simple: Just store a List<Action>. You can put whatever you want in there. You can enumerate the list and call everything.

You absolutely can add parameterized calls to such a list: () => DoSomething1(10, false). The parameters are packaged inside of the Action. The C# compiler generates a (strongly-typed) closure class and does all of that for you.

Doesn't this do what you want?

You could queue up your inputs and actions in parallel collections:



            var actions = new Dictionary<int, Func<object[], object>>();
            var inputs = new Dictionary<int, object[]>();

            //when you want to store the action and it's input
            int counter = 0;
            object[] someObjects = new object[] {};
            actions.Add(counter, x => { return x[0]; });
            inputs.Add(counter, someObjects);
            counter++;


            //and then later when it's time to execute
            foreach (var input in inputs)
            {
                actions[input.Key].Invoke(input.Value);
            }

Alternatively, you could roll a class that stores both the input and the action, so the input is coupled to the action at execution time by something other than a matching dictionary key.

My other answer shows quite complicated way to do that. There is however one much easier. Why don't you make your list List<Action>?

List<Action> Temporary = new List<Action>();
void Update()
{
    //something happened and another class needs to know
    //but it will have to wait for the sync so as to not cause any treading problems

    Temporary.Add(() => DoSomething1(1, true));
    Temporary.Add(() => DoSomething3("Some text"));
}
void Sync()
{
    foreach (var item in Temporary)
    {
        item.Invoke();
    }
    Temporary.Clear();
}

Should work just fine.

@Eli, you're getting 4's as the input value in your for loop because execution of the actions is deferred. When they actually execute, i = 4;

To avoid this, queue up your inputs.

I'd respond to your comment with a comment, but don't have the rep yet.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top