【问题标题】:Can I return a reference to new object instance from a C# delegate?我可以从 C# 委托返回对新对象实例的引用吗?
【发布时间】:2013-03-16 23:01:30
【问题描述】:

我正在学习/尝试 C# 中的一些功能模式,但遇到了一个我无法解释的问题。我确定这是一个简单的答案(我希望如此),但我很难看到它。可能与闭包等有关,而我无法开箱即用是对我隐瞒答案!

这是我的实验:我正在尝试从函数委托中返回特定类的全新实例..

public class Foo{
    string A { get; set ; }
}

static void Main( string[] args ){
    // the delegate...
    Func<Foo,bool> someFunc = o => {
        o = new Foo { A = "A new instance of o?" };
        return true;
    };

    Foo foo = null;   // was hoping to replace this via delegate
    var myFunc = someFunc;
    var result = myFunc( foo );

    if ( foo == null )
        Console.WriteLine( "foo unchanged :-(" );
    else
        Console.WriteLine( foo.A ); // hoping for 'A new instance of o?'

当然,我只是在输出中得到“foo 不变 :-(”。 我在测试中做了一点改动,我传入了一个非空 Foo 实例并修改了属性“A”(而不是返回一个新实例)并且效果很好(也就是说,我可以像我一样改变现有对象期望将对象引用传递给函数时)我似乎无法从我的委托中获取新实例。

所以?我只是在代码中做错了吗?这完全可以做到吗?很想知道为什么这不起作用。

【问题讨论】:

    标签: c# anonymous-delegates


    【解决方案1】:

    形参ofoo的值的副本;变异 o 不会变异 foo。和你说的一样:

    int x = 1;
    int y = x;
    y = 2;
    

    这不会改变xyx 值的副本,而不是x别名

    你想多了这个问题。如果您想要一个改变本地的委托,那么只需编写一个改变本地的委托:

    Foo foo = null;   // was hoping to replace this via delegate
    Action mutateFoo = () => { foo = new Foo() { A = "whatever"}; };
    mutateFoo();
    if ( foo == null )
        Console.WriteLine( "foo unchanged :-(" );
    else
        Console.WriteLine( foo.A );
    

    如果你只想改变一个变量,那么就改变这个变量。如果您只想执行副作用,则无需从委托中传入或传出任何内容。

    我注意到您说您正在试验功能模式。请记住,函数式编程不鼓励变量突变,因此您可能会走上一条错误的道路。

    【讨论】:

    • 在您的示例中(使用整数),我知道这适用于 值类型,但如果 xy引用类型那么,在y = x 语句之后,我现在不是在处理对同一个对象的两个引用吗?我通过y 所做的任何更改都会影响x。我错过了什么吗?
    • 哦,我知道有人会告诉我那个功能性评论 ;-) 我明白,当我在这条功能性道路上前进时,突变不是答案。很好的收获,感谢您的提醒。
    • @robcom88: 如果xy 指的是同一个对象的字段z 然后x.zy.z 指的是同一个字段,但x 和@987654342 @ 不指代彼此。它们都引用同一个对象
    • @robcom88:这样想。您有两个标签为xy 的抽屉。在x 中有一张纸上写着123 Sesame Street。当您说y = x; 时,这意味着拿出复印机并将第二张写有123 Sesame Street 的纸放入yxy 现在指的是同一个房子,但是如果你把x 中的那张纸扔掉并用写着1600 Pennsylvania Avenue 的那张纸代替,那不会改变抽屉@987654354 的内容@。这里的关键点是引用只是另一种价值
    【解决方案2】:

    您可以将Foo 作为 lambda 表达式的返回值返回:

    Func<Foo> someFunc = o =>
    {
        return new Foo { A = "A new instance of o?" };
    };
    

    如果您确实需要返回bool,也可以返回Tuple&lt;bool, Foo&gt;

    Func<Tuple<bool, Foo>> someFunc = o =>
    {
        return Tuple.Create(true, new Foo { A = "A new instance of o?" });
    };
    

    或者,如果你真的真的确定你想要那个,你可以用out parameter声明你自己的自定义Func-like委托:

    delegate TResult FuncOut<T, TResult>(out T arg);
    
    FuncOut<Foo, bool> someFunc = (out Foo o) =>
    {
         o = new Foo { A = "A new instance of o?" };
         return true;
    };
    
    Foo foo;
    var result = someFunc(out foo);
    

    但我不建议这样做。

    【讨论】:

    • 我同意!没有'out' parms ;-) 所以,在元组中返回对象是我可能会去的方式。我认为lazyberezovsky 给了我关于“为什么”的线索,但我将其标记为答案,因为您已经给了我“什么”和我需要的解决方法。谢谢
    【解决方案3】:

    您正在将 Foo 对象的引用(即地址)传递给您的委托。该地址分配给委托的参数o(将其视为局部变量)。当您更改委托中的Foo 对象时,您将要引用对象并更改该地址上的内容。这就是对象发生变化的原因。

    但是,当您将新地址分配给委托的局部变量(即参数)时,您只会丢失传递给委托的原始 Foo 对象的地址。分配后局部变量只保存新的Foo 对象的地址。它不影响调用者的foo 变量,它仍然保存另一个地址。

    【讨论】:

    • 好的,这很有意义。最终,这回答了我关于为什么我的测试不起作用的潜在问题(希望我能给出部分答案)。我认为我在这里错过了对象引用的一个基本课程,该课程被委托方面所掩盖。事实上,即使使用传统(即非委托)方法,我的测试也无法正常工作。感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多