views:

446

answers:

5

I have the following linq code.

searchResults = (from item1 in searchResults
              join item2 in coll
              on item1.skuID equals item2.Skuid
              where item2.SearchableValue == value
              select item1).ToList();

The variable searchResults is passed in the method as a generic List. The linq segment above filters the list. When returned from the method I expect the list to be modified but the reference remained unchanged as it was passed in. How do I achieve modifying my reference, not a copy? Thanks.

+4  A: 

You're assigning the searchResults parameter to point to a new List instance.

To see the change in the calling method, you'll need to make it a ref parameter.

Alternatively, you could modify the list in-place, like this:

var newResults = (from item1 in searchResults
                  join item2 in coll
                  on item1.skuID equals item2.Skuid
                  where item2.SearchableValue == value
                  select item1).ToArray();

searchResults.Clear();
searchResults.AddRange(newResults);
SLaks
Why was this downvoted?
SLaks
+2  A: 

You need to mark the searchResults parameter as ref.

public void Foo(ref List<T> searchResults) { ... }

Edit

Since you're using the list in a query (which is an anonymous method), you'll have to modify the list in-place rather than a ref parameter.

var results = (from item1 in searchResults
               join item2 in coll
               on item1.skuID equals item2.Skuid
               where item2.SearchableValue == value
               select item1).ToList();

searchResults.Clear();
searchResults.AddRange(results);

It looks like SLaks got to this answer more quickly.

Adam Robinson
I've tried but got an errorerror CS1628: Cannot use ref or out parameter 'searchResults' inside an anonymous method, lambda expression, or query expression
David
... adding ref does nothing. Lists are passed in by default as a reference.
Will
@Will: no, the reference to the list is passed by value, which is very different! The reference is changed inside the method.
Hans Kesting
@Hans: while you are right about the reference to the list being passed by value, the code sample in the answer does not change the reference, so the `ref` keyword doesn't really add anything.
Fredrik Mörk
Where exactly is the reference changed?
Will
@Will, Fredrick: My answer doesn't change the reference specifically for the reason that you can't use `ref` parameters in an anonymous method. The OP's question *does* change the reference by assigning the output of `ToList()` back into the original variable.
Adam Robinson
A: 

The variable searchResults needs to be passed in as a [ref][1] parameter.

The method signature would look like this.

//Note you only need to ref the one variable.
void filter(ref List searchResults, object otherParameter)
{
   //LINQ and stuff here
}

To call it you'll need to use ref as well:

filter(ref myResults, stuff);
C. Ross
+12  A: 

You should return the filtered result, so if your method looks like this:

public void Filter(List<Item> searchResults)

change it to:

public IEnumerable<Item> Filter(List<Item> searchResults)

and then return the result instead of assigning it to the variable. In other words, change this:

searchResults = (from item1 in searchResults

to this:

return (from item1 in searchResults

In your call, change from:

Filter(list)

to:

list = Filter(list).ToList();

This gives you maximum reusability, as you can pass in a list and trust the method not to change it, leaving the choice whether to place it into a new variable, or replacing the original list, up to you.

Also, as indicated by Fredrik in his comments, the call to .ToList() in the Linq query should be removed as well, and possible you might want to change the input parameter to be IEnumerable as well.

Let me summarize.

If you have code like this:

public void Filter(List<Item> searchResults)
{
    searchResults = (from item1 in searchResults
                     join item2 in coll
                     on item1.skuID equals item2.Skuid
                     where item2.SearchableValue == value
                     select item1).ToList();
}

...
list = ...
Filter(list);

and want to make list become updated, I'd change the code to this:

       __ changed ______        __ changed ______
public IEnumerable<Item> Filter(IEnumerable<Item> searchResults)
{
    return from item1 in searchResults               <-- changed
           join item2 in coll
           on item1.skuID equals item2.Skuid
           where item2.SearchableValue == value
           select item1;                             <-- changed
}

...
list = ...         _ added__
list = Filter(list).ToList();
Lasse V. Karlsen
+1 - This would *definitely* be my preferreed solution (even though I would possibly change the input parameter to be a `IEnumerable<Item>` instead). Additionally the `.ToList()` call in the original code sample could be removed.
Fredrik Mörk
+7  A: 

I see you've already got an answer to your question. But you might be curious as to why you cannot use a ref parameter in a query comprehension.

The reason is because the query comprehension expression represents the query, not its results. It represents a deferred execution of the query. So, suppose we allowed that:

IEnumerable<int> Frob(ref int x)
{  return from foo in whatever where foo.bar == x select foo.bar; }

IEnumerable<int> Blob()
{ 
    int y = 123; 
    var r = Frob(ref y);    
    y = 456;
    return r;
}
void Grob()
{
    foreach(int z in Blob()) { ... }
}

What does this do? The query is not executed until after Blob returns, but the query refers to a local variable reference on a frame that no longer exists.

The compiler has no way of knowing that you are not in this situation every time a ref is used in a query, so it forbids it entirely.

Eric Lippert