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:
- 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.
- Delegate target can also be edited via reflection.
- 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".