委托的 out / ref 参数

3

委托:


return delegate( IQueryable<MySearchResultItem> query, Expression<Func<MySearchResultItem, object>> lambda, Wrapper wrapper)
{
    wrapper.query = query.OrderBy(lambda);
    query = query.OrderBy(lambda);
};

包装类:

public class Wrapper
{
    public IQueryable<MySearchResultItem> query { get; set; }
}

当我执行这个委托时,我期望这个函数结束后查询会被更改,但事实并非如此。因此,我认为查询是按值传递的(而不是按引用传递)。
但是,当我为这个查询创建一个包装类,将查询添加到包装类中并同时传递它。然后,在此方法完成后,包装类内部的查询已经被更改了(所以这个包装类是按引用传递的?)
这里发生了什么?

2
你将查询重新分配给query.OrderBy(lambda)。这不会创建一个新的引用吗?因此,输入的查询参数不会改变吗? - Wicher Visser
2个回答

1
它是通过引用传递的,但你不是在操作引用,而是覆盖它。这就像在C语言中,当你给指针赋一个新地址时,而不是操作指针的值。它可以使用包装类,因为你是在引用上工作,而不是覆盖它。
如果你想修改引用,也要使用ref运算符。
return delegate( ref IQueryable<MySearchResultItem> query,

编辑:当然需要具有匹配的委托签名,而且它不能与Func<T1,T2, TResult>一起使用。

public delegate void MyDelegate(ref IQueryable<object> query, Expression<Func<object, object>> lambda);

private MyDelegate Create()
{
    return delegate(ref IQueryable<object> query, Expression<Func<object, object>> lambda)
    {
        query = query.OrderBy(lambda);
    };
}

这给了我一个错误:“参数1不应该使用'ref'关键字声明”。 - Timon
当然,这是一个非常糟糕的想法。只需遵循通用做法并像其他人一样返回新查询即可。你正在处理函数式编程,而查询是不可变的。返回一个新的东西,不要改变旧的东西。 - Luaan
请问您能否发布您的其余代码?我假设您必须匹配委托的某个特定签名。 - Toxantron
@Luaan 我同意这是一个可怕的想法。然而,我假设他没有编写扩展程序是有他的理由的。所以我只是试图解释为什么他想做的任何事情都不起作用。 - Toxantron

1
你可能将C#中的“按引用”与C++中的“按引用”混淆了。
在你的代码中,query是按引用传递的,这意味着它的值的引用被按值传递。因此,改变query将改变引用所引用的值。但是,改变引用本身不会有任何作用。
query是不可变的-没有办法改变它的值。你只能创建一个包含旧查询的新查询。这正是例如OrderBy所做的-它不会改变查询。这是LINQ和类似的C#函数式方法的核心特性之一-可变代码通常更难以处理,因此你要避免它,特别是在接口上。
所以你需要做的是通过引用传递引用,而不是值。这正是您通过提供Wrapper类所做的。也可以使用ref关键字来实现这一点,但在您的情况下,这完全是不必要的且相当难处理的。ref只有在值类型中才真正有意义,尽管对于引用类型也有有用的情况;但这种情况非常罕见。
但最好、最简单的方法是遵循一个简单的原则:不要改变任何东西,只需返回包含更改的对象。使您的委托返回查询,而不是修改参数。
delegate IQueryable<...> YourDelegate(IQueryable<...> query);

IQueryable<...> YourMethod(IQueryable<...> query)
{
  return query.OrderBy(...);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接