Question

I am building a list of unit tests, which are organised as a list of objects, each of which contain the test method to be executed as a Func. Each object has a variable which is within scope of the Func and is used by it. The variable is not passed in as a parameter.

Iterating over the list and running all the tests runs fine, but is is possible to copy a Func from one object, -breaking the reference to that object-, and assign it to a new object? I assume this is possible somehow by creating a Deep Copy, but my attempt using BinaryFormatter has not worked, any tips would be appreciated!

I have a simplified forms application as follows to illustrate my problem:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; using
using System.IO;
using System.Linq; 
using System.Runtime.Serialization.Formatters.Binary; 
using System.Text;
using System.Threading.Tasks; using System.Windows.Forms;

namespace WindowsFormsApplication4 {
 public partial class Form1 : Form
 {
     public Form1()
     {
         InitializeComponent();
     }




     public static object DeepClone(object obj)
     {
         object objResult = null;
         using (MemoryStream ms = new MemoryStream())
         {
             BinaryFormatter bf = new BinaryFormatter();
             bf.Serialize(ms, obj);

             ms.Position = 0;
             objResult = bf.Deserialize(ms);
         }
         return objResult;
     }

     [Serializable]
     public class POCOwithFunc {

         public POCOwithFunc(Func<string> myfunc)
         {
             mqi = myfunc;
         }

         public POCOwithFunc() { }

         public Func<string> mqi;

         public object parm;

     }



     private void button1_Click(object sender, EventArgs e)
     {


         List<POCOwithFunc> testList = new List<POCOwithFunc>();

         for (int x = 0; x < 5; x++)
         {

             var pc = new POCOwithFunc();
             pc.parm = x;
             pc.mqi = delegate()
        {
            var rrq = pc.parm;      
            return "result: " + pc.parm;
        };

             testList.Add(pc);
         }

         String output = "";
         foreach (var test in testList)
         {
             output += test.mqi() + "\r\n";
        }
//output:
//result: 0
//result: 1
//result: 2
//result: 3
//result: 4





         var pocoToBeCopied = testList[2];

         var newpoco = new POCOwithFunc();
         newpoco.parm = 10;
         newpoco.mqi = pocoToBeCopied.mqi;

         var res = newpoco.mqi();  //returns 2


         newpoco = (POCOwithFunc)DeepClone(pocoToBeCopied);  //fails

     }

 } }

No correct solution

OTHER TIPS

This is the first time I'm hearing about deep copy a delegate (which would not work, as the delegate (Func is type of delegate) contain reference to its closure (its environment, which contain any variable that that delegate is using).

I would suggest to change the parameter itself, or, to send it as a parameter (there is a delegate type for it too: Func<object, string >).

(and, I think you should think about redesign the whole thing :-/)

7 years later...

So I had encountered pretty much the same problem, also when writing tests (basically, I had a delegate that captured a lot of info from different parts of the program, and at the end of the program displayed it in a elaborate, error-prone way. I wanted to check that in all cases where captured variable turns to null/other weird values, the delegate handles it gracefully). I had a general idea that "no you can't" isn't completely true, so here goes.

TL;DR:

  1. Closures: compiler generates a class that contains references to all "captured" variables. This class can be accessed and edited via reflection, thus "redirecting" the closure to point to another variable.
  2. Delegate target can also be edited via reflection.
  3. To "deep copy" a delegate within an object you need to change delegates' Target and (if needed) redirect any fields that point to original object.

Long version:

Turned out, there is two questions here: first (and the one that was more important for me) - redirecting closures. When compiler detects a delegate that forms a closure that is moved beyond the current scope, a class is generated. This class contains references to the objects that were "captured", so that they don't get garbage collected despite being out of scope. (Also, turned out, the fields of this class are helpfully called by the name of variable that got captured.) You can see the generated class with Reflection:

<!-- language: csharp -->
public static void PrintClass(System.Delegate f)
{
    var compilerGeneratedType = f.Target.GetType();
    var fields = compilerGeneratedType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    Console.WriteLine(compilerGeneratedType.Name + " has fields:");
    foreach (var field in fields)
    {
        Console.WriteLine(field.FieldType + "\t" + field.Name);
    }
}

and the output when used on a classic closure example:

Action a = () => { };
for (int index = 0; index != 3; index++)
{
    a += () => { Console.Write(index + " "); };
}
a(); //output: 3 3 3
PrintClass(a);

output of PrintClass function:

<>c__DisplayClass0_1 has fields: System.Int32 index

Modifying that closure can be done with reflection, like that:

static System.Delegate RedirectClosure(System.Delegate f, object newTarget, string originalVariableName)
{
    System.Delegate result = (System.Delegate)f.Clone();

    var compilerGeneratedType = f.Target.GetType();
    var fields = compilerGeneratedType.GetFields();

    foreach (var field in fields)
    {
        if (field.FieldType == newTarget.GetType() && field.Name == originalVariableName)
            field.SetValue(result.Target, newTarget);
    }
    return result;
}

Using that earlier example:

Action a = () => { };
for (int index = 0; index != 3; index++)
{
    a += () => { Console.Write(index + " "); };
}
a(); //output: 3 3 3
            
int j = 42;
RedirectClosure(a, (object)j, "index");
a(); //output: 42 42 42

Now for this specific example (delegate is inside a class, and it captured a field of this same class), second problem: the delegate's Target also needs to be changed. For this I modified the copying function to also redirect delegate's target. (Using .Clone() on delegate or straight-up copying it has the side effect: if I copy delegate from source to destination and modify only its "captured object" field to point to second destination - source delegate also starts pointing there. I guess they still share a reference to same Target.) Modified function (copies the delegate and redirects to new object):

static System.Delegate CopyDelegateAndRedirectClosure<T1>(System.Delegate f, T1 originalTarget, T1 newTarget)
{
    System.Delegate result = (System.Delegate)f.Clone();

    // I bet there is a better way to get a copy then this =(
    var serialized = JsonSerializer.Serialize(result.Target);
    var deserialized = JsonSerializer.Deserialize(serialized, result.Target.GetType());

    var targetField = result.GetType().GetField("_target", BindingFlags.Instance | BindingFlags.NonPublic);
    targetField.SetValue(result,deserialized);

    var compilerGeneratedType = f.Target.GetType();
    var fields = compilerGeneratedType.GetFields();
                        
    foreach (var field in fields)
    {
        if (field.FieldType == originalTarget.GetType() && field.GetValue(f.Target) == (object)originalTarget)
            field.SetValue(result.Target, newTarget);
    }
    return result;
}

And here is an example of using it on the same situation the original poster had.

Test class:

class A
{
    public int param;
    public Func<int> del;
}

"Deep copying" the delegate from one instance to another:

var destination = new A { param = 10 };

var source = new A { param = 2 };
source.del = () => { return source.param + 100; };

Console.WriteLine($"source: param = {source.param}, del() result = {source.del()}");

//  output:
//  source: param = 2, del() result = 102

destination.del = (System.Func<int>)CopyDelegateAndRedirectClosure(source.del, source, destination);

Console.WriteLine($"destination: param = {destination.param}, del() result = {destination.del()}");
Console.WriteLine($"source: param = {source.param}, del() result = {source.del()}");

//  output:
//  destination: destination: param = 10, del() result = 110
//  source: param = 2, del() result = 102

So there it is: copied the delegate from one instance to other, and it now operates on the new instance. The old one is unaffected.

Now again to "why would one do that" - yep, better architecture would have prevented me from ever finding this question. However, this did allow me to write a test replacing all captured variables of a specific type with "broken" value, and "bad architecture + tests" is better then "bad architecture + no tests".

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top