Modifying IQueryable like a Reference Type doesn't work?
Asked Answered
L

2

8

It's common in C# to modify objects in private methods as they are commonly reference-types instead of value types, for example:

public void Main()
{
    var person = new Person();

    SetPersonsName(person);

    System.Writeln(person.Firstname + " " + person.Lastname);
}

private void SetPersonsName(Person person)
{
    person.Firstname = "Jimi";
    person.Lastname = "Hendrix";
}

Firstname and Lastname are applied to the object and will be correctly printed as we pass in the object by reference (pointer to it's location in memory) rather than creating a copy of the object like we'd do for value-types.

So what if we have an IQueryable and which to use the same approach to add Where clauses to the object collection, like below?

public void Main()
{
    // Returns Jimi Hendrix and Justin Bieber
    var people = _workspace.GetDataSource<Person>();

    FilterByRockGods(people);

    foreach(var person in people)
    {
        // Will still print "Jimi Hendrix" and "Justin Bieber"
        // Should only print "Jimi Hendrix" (obviously)
        System.Writeln(person.Firstname + " " + person.Lastname);
    }
}

private void FilterByRockGods(IQueryable<Person> people)
{
    people = people.Where(x => x.IsRockGod);
}

This won't work, the Where clause applied in the private method is not applied to the collection

You have to do the following instead:

public void Main()
{
    // Returns Jimi Hendrix and Justin Bieber
    var people = _workspace.GetDataSource<Person>();

    people = FilterByRockGods(people);

    foreach(var person in people)
    {
        // Prints "Jimi Hendrix"
        System.Writeln(person.Firstname + " " + person.Lastname);
    }
}

private IQueryable<Person> FilterByRockGods(IQueryable<Person> people)
{
    people = people.Where(x => x.IsRockGod);

    return people;
}

Why is this?

Looseleaf answered 20/10, 2014 at 10:14 Comment(3)
You might observe the same thing about string - methods that suggest they modify the string instead return a new string that represents the altered state. Or any class which has been designed to be immutable but has methods suggesting change.Vancouver
In your method FilterByRockGods you are only setting an expression, the expression is not "applied" until the collection is enumerated, the fact that you are passing the the collection to a method is not relevent, you get this behaviour however you do it, it is called deferred execution.Antihero
@BenRobinson deferred execution isn't the issue here, regardless of when the collection is enumerated, the .Where clause in the private method is not applied to the expression in the calling scope. As Damien says, the issue is that is creates new new expression, so modifying it like a reference-type will not work.Looseleaf
Z
9

This is because when you write person.Firstname = ... you are invoking the FirstName's property setter which alters the instance's data.

Where, in contrast does not alter the IEnumerable it's called on but produces a new IEnumerable.

Zinkenite answered 20/10, 2014 at 10:19 Comment(8)
Thank you; does this make IQueryable a value-type or is there another term for this behaviour in this context?Looseleaf
It does not make it a value type. If IQueryable had a 'Name' property for instance with a setter, you would be able to execute 'people.Name = "Doctor Fred"'.Zinkenite
So is the problem that the method FilterByRocksGods() is attempting to overwrite the pointer passed into it? Is that what is not succeeding?Cornuted
FilterByRocksGods is not overwriting anything. people is this method is a local variable which value you can change inside the method without affecting the variable passed by the caller.Zinkenite
people = people.Where(x => x.IsRockGod); sure looks like it's attempting to overwrite the parameter people. I guess it can't be succeeding since after the call to FilterByRockGods(), the original IQueryable is unmodified. So is the lesson here that when a parameter is passed by reference, the 'pointed-to' object's state can be changed, but the reference cannot be changed to point at something else?Cornuted
It can if your use ref parametersZinkenite
Crazy. I thought reference types were passed by reference by default. But they're not. msdn.microsoft.com/en-us/library/s6938f28.aspx. Thank you, vc 74.Cornuted
They're not, even in C++ which is much less strict it's not the case.Zinkenite
S
2

The questions targets the fundamentals of the language. Hence it's worth a more precise explanation.

public void Main()
{
    // Returns Jimi Hendrix and Justin Bieber
    var outerPeople = _workspace.GetDataSource<Person>();

    outerPeople = FilterByRockGods(people);

    foreach(var person in outerPeople)
    {
        // Prints "Jimi Hendrix"
        System.Writeln(person.Firstname + " " + person.Lastname);
    }
}

private IQueryable<Person> FilterByRockGods(IQueryable<Person> innerPeople)
{
    innerPeople = people.Where(x => x.IsRockGod);

    return innerPeople;
}

What happens when outerPeople = FilterByRockGods(people); executes

people is a reference type variable, meaning that it is not hodling the object itself, but rather it is holding a refrence to where the object is actually being stored. As it is for reference types, upon assignments, the reference of the object (address of the object) is copied to the new variable on the left hand side of the assignment.
Passing an argument to a method, includes an assignment statement in itself. What we have after assignment is done could be illustrated like this:
enter image description here

Before we do an assignment to people variable (as in people = people.Where( ...), the reference type variable in the called method and the variable within the caller, both refer to the same object.

No matter what variable I use to alter the state of an object, What matters is that the state of the object has changed and anyone having a reference to the object can see that it has changed!

What happens when innerPeople = innerPeople.Where ... executes:

Right-hand side of the innerPeople = innerPeople.Where ... is creating a new object. After this assignment, the innerPeople is pointing to an object other than the one the outerPeople is pointing to! From this point on the innerPeople within the called method has nothing to do with the variable in the outer scope!
enter image description here

Sander answered 26/5, 2024 at 22:5 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.