【问题标题】:Deep copying a Func within an object in C#在 C# 中的对象内深度复制 Func
【发布时间】:2014-03-03 16:23:04
【问题描述】:

我正在构建一个单元测试列表,这些单元测试被组织为一个对象列表,每个对象都包含要作为 Func 执行的测试方法。每个对象都有一个在 Func 范围内并被它使用的变量。该变量不作为参数传入。

遍历列表并运行所有测试运行良好,但是否可以从一个对象复制 Func,-破坏对该对象的引用-,并将其分配给一个新对象?我认为这可以通过创建深层副本以某种方式实现,但我使用 BinaryFormatter 的尝试没有奏效,任何提示将不胜感激!

我有一个简化的表单应用程序如下来说明我的问题:

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

     }

 } }

【问题讨论】:

  • 当您说“它不起作用”时,究竟发生了什么?你收到错误信息了吗?那是什么?
  • 我会试试 iCloneable
  • @Alex 看看它说什么 .... 未标记为可序列化.
  • 如果您正在深度克隆Func&lt;string&gt;,这是否意味着您正试图在Func&lt;string&gt; 中保留某些状态? Func&lt;string&gt; 在设计上不应该是无状态的吗?
  • @RobertHarvey 我想保留对函数的封闭/外部对象中的变量的引用,而不是该变量的特定值。

标签: c# .net linq delegates


【解决方案1】:

这是我第一次听说深度复制委托(这不起作用,因为委托(Func 是委托的类型)包含对其闭包的引用(其环境,其中包含该委托的任何变量)使用)。

我建议更改参数本身,或者将其作为参数发送(也有一个委托类型:Func&lt;object, string &gt;)。

(而且,我认为你应该考虑重新设计整个事情:-/)

【讨论】:

  • 你可以克隆一个函数,这很好用,不过我同意你关于重新设计的观点: Func newfunc = delegate() { return "clone me"; }; var clonedfunc = (Func)DeepClone(newfunc);
  • 好吧,正如我写的为什么它不起作用 - 我意识到它会,但是 - 闭包也会被克隆,没有能力将委托“附加”到新对象(并且实际上,将无法对关闭进行任何更改)
【解决方案2】:

7 年后...

所以我在编写测试时也遇到了几乎相同的问题(基本上,我有一个委托从程序的不同部分捕获了很多信息,并在程序结束时将其详细显示,容易出错的方式。我想检查在捕获的变量变为空/其他奇怪值的所有情况下,委托都会优雅地处理它)。我有一个普遍的想法,“不,你不能”并不完全正确,所以就这样吧。

TL;DR:

  1. 闭包:编译器生成一个包含所有引用的类 “捕获”的变量。可以通过以下方式访问和编辑此类 反射,从而“重定向”闭包指向另一个 变量。
  2. 代理目标也可以通过反射进行编辑。
  3. 要在需要更改的对象中“深拷贝”委托 代表的目标和(如果需要)重定向指向的任何字段 原始对象。

长版:

原来,这里有两个问题:第一个(也是对我来说更重要的一个) - 重定向闭包。当编译器检测到一个委托形成一个超出当前范围的闭包时,就会生成一个类。此类包含对“捕获”的对象的引用,因此即使超出范围,它们也不会被垃圾收集。 (另外,事实证明,这个类的字段通过被捕获的变量的名称来调用是很有帮助的。)你可以看到使用反射生成的类:

<!-- 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);
    }
}

以及用于经典闭包示例时的输出:

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

PrintClass 函数的输出:

c__DisplayClass0_1 有字段:System.Int32 索引

可以通过反射来修改闭包,如下所示:

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;
}

使用前面的例子:

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

现在对于这个具体的例子(委托在一个类中,它捕获了同一个类的一个字段),第二个问题:委托的目标也需要改变。为此,我修改了复制功能以重定向委托的目标。 (在委托上使用 .Clone() 或直接复制它会产生副作用:如果我将委托从源复制到目标并仅修改其“捕获的对象”字段以指向第二个目标 - 源委托也开始指向那里。我猜想它们仍然共享对同一个 Target 的引用。)修改后的函数(复制委托并重定向到新对象):

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;
}

这是一个在原始海报相同的情况下使用它的示例。

测试类:

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

将委托从一个实例“深度复制”到另一个实例:

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

就是这样:将委托从一个实例复制到另一个实例,现在它在新实例上运行。旧的不受影响。

现在再说一次“为什么要这样做”——是的,更好的架构会阻止我找到这个问题。但是,这确实允许我编写一个测试,用“损坏”值替换所有捕获的特定类型的变量,并且“糟糕的架构 + 测试”比“糟糕的架构 + 没有测试”更好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-07-07
    • 2012-12-15
    • 2018-01-28
    • 1970-01-01
    • 2011-07-25
    • 2023-03-17
    • 2011-09-02
    相关资源
    最近更新 更多