简体   繁体   中英

Is there a way to inline delegate in C#?

Here's a simplified scenario that I'm dealing with. There are multiple methods with the looping structure.

for (int i=0; i<I; i++) {
    // Do something
    for (int j=0; j<J; j++) {
        // Do something
        for (int k=0; k<K; k++) {
            // Do something
            Update(a,b,c);
        }
    }
}

In one method, Update(a,b,c) is

a[i] += b[j] * c[k]

In another method, it is

b[j] += a[i] * c[k]

And yet in another method, it is

c[k] += a[i] * b[j]

At the moment, my code is duplicated everywhere. Is there a pattern in C# so that I don't duplicate code? I was tempted to use delegate but it seems that delegate would degrade the performance (which is critical in my case).

Is there a way to write a macro or an inline delegate function for such scenario?

Something like this?

void DoUpdates(Action<int, int, int> update)
{
  for (int i=0; i<I; i++) {
    // Do something
    for (int j=0; j<J; j++) {
      // Do something
      for (int k=0; k<K; k++) {
        // Do something
        update(i, j, k);
      }
    }
  }
}

and then in the caller

DoUpdates((int i, int j, int k) => { a[i] += b[j] * c[k]; });

Is that what you're looking for?

void Update<T>(T[] x, T[] y, T[] z, int i, int j, int k)
{
    x[i] += y[j] * z[k];
}

Usage:

Update(a, b, c, i, j, k);
Update(b, a, c, j, i, k);
Update(c, a, b, k, i, j);

I see that a is always accessed by i (and so on - b by j , c by k ). You can try to optimize the code using this fact.

If performance is critical you could avoid the method call in the inner loop, like this:

void Update(int[]x, int[]y, int[]z, int I, int J, int K)
{
    for (int i = 0; i < I; i++)
    {
        // Do something
        for (int j = 0; j < J; j++)
        {
            // Do something
            for (int k = 0; k < K; k++)
            {
                // Do something
                x[i] += y[j] * z[k];
            }
        }
    }
}

and the calling code:

Update(a, b, c, I, J, K);
Update(b, a, c, J, I, K);
Update(c, a, b, K, I, J);

Likely you are implementing something like large number multiplication or other linear combination of vectors. The reason you would need the approach you described as inline delegate is quite probably because of the varying place to store the result during the calculation and also because the nested for-loops are hard coded. Thus I suggest to revise your code like following:

public void Update(int destinationIndex, int[][] arrays, int[] indices) {
    var product=1;

    for(var i=indices.Length; i-->0; )
        if(destinationIndex!=i)
            product*=arrays[i][indices[i]];

    arrays[destinationIndex][indices[destinationIndex]]+=product;
}

public void PerformUpdate(
    int destinationIndex, int[] counts, int[][] arrays, Action<int, int>[] actions,
    List<int> indices=null, int level=0
    ) {
    if(level==counts.Length)
        Update(destinationIndex, arrays, (indices??new List<int>()).ToArray());
    else
        for(int count=counts[level], i=0; i<count; i++) {
            if(null!=actions&&level<actions.Length)
                actions[level](i, count); // do something according to nesting level

            (indices=indices??new List<int>()).Add(i);
            PerformUpdate(destinationIndex, counts, arrays, actions, indices, 1+level);
            indices.RemoveAt(indices.Count-1);
        }
}

This code is implemented in a recursive manner. The int[][] array can be replace with a generic array as long as you are going to define the calculation of operator * and operator + , rather the methods name in MutiplyScalar and AddScalar .

So, we would not use a delegate of Update to control the destination. Instead, we just use the destinationIndex to accomplish that. Following is a test case:

int[] a=new[] { 1, 2 }, b=new[] { 3, 4, 5 }, c=new[] { 6 };
Action<int, int> m=(index, count) => Debug.Print("index={0}; count={1}", index, count);
PerformUpdate(2, new[] { a.Length, b.Length, c.Length }, new[] { a, b, c }, new[] { m, m, m });

We still have inline delegates there, which are called Lambda Expressions in c#. According to the original code you provided, there're Do something s between the nested for-loops. However, there is not much information we can found which is non-globally known to Update ; the most significant difference we can see is the iterating index and the ending number , which are i, I , j, J and k, K . Thus we just take these as arguments to pass to the Action<int, int> for doing something, and they are variable for each level of for-loop.

The execution is much depended on the indices . It stores the iterating index of current for-loop and passed to next level of recursive call. Further if you passed the arrays with a count smaller than its Length in indices , it's treated as an array with the length of that count you passed to. Don't pass a negative count, neither a larger one. It can be lack of Action<int, int> , that would just mean do nothing instead of do something .

This will probably inline it.

interface IFunc<T>
{
    void Invoke(ref T a, ref T b, ref T c);
}

void DoUpdates<TFunc>(TFunc update)
    where TFunc : IFunc<int>
{
    for (int i = 0; i < I; i++)
        for (int j = 0; j < J; j++)
            for (int k = 0; k < K; k++)
                update.Invoke(ref i, ref j, ref k);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM