Question Reference to List item

Merden

New member
Joined
Aug 31, 2022
Messages
3
Programming Experience
Beginner
Hi! I have a question, but at first I will describe task.
I have a library. I create books and add to the library.
Everyone can come to library, select name of book, get reference to bookshelf and place where it is and can read it.
I have Book class, static Library class with list/array of Book
In Library class I have method GetBookByTitle where I can get reference to book.
Everything works fine for an array, but I get an error when using a list:
An expression cannot be used in this context because it may not be passed or returned by reference
C#:
public static ref Book GetBookByTitle(string title)
{
    for (int i = 0; i < booksList.Count; i++)
        if (booksList[i].Title.Contains(title))
            return ref booksList[i];  // error there
    return ref emptyBook;
}
Of cource we can just use array, but for me really interesting a reason of this error and if it can be solve, how?
 
Solution
So, you say, that I don't need ref in method
We hardly ever need ref, anywhere, ever. My advice to new programmers is to avoid it, avoid `out`, and avoid `static`

If you want to return multiple things from a method, use a Tuple or make a class so you can return eg a Book that represents multiple related bits of information like Author and Title. If you're trying to use ref because you want some method to swap out an object for another, even when it's none of that method's business to do so, then don't; it leads to obscure bugs and hard to follow logic if e.g. the person that borrowed a book is capable of swapping it for a new one and affecting the library contents

Avoid `static` because static is like a weird opposite...
Unless your Book is defined as a struct, returning a Book returns a reference already. You don't need to use the ref keyword. In C# all objects are reference types and therefore always passed by reference.
 
Anyway to answer your question, a reference to an array entry can be returned because the [] operator actually indexes into an array, and the address of that array element can be known by the compiler/runtime.

On the other hand, the [] on a list actually calls this property getter/setter of a List<T>. The compiler/runtime has no way of knowing what the address of the object returned by List<T> because it calls a getter. Although we know that current implementation of List<T> uses an underlying array and it just maps indexes to that underlying array, but recall that the List<T> can also move that array around as the list grows. Who knows if some future implementation implements a list as a sparse linked list depending on the usage pattern that it detects?

 
Unless your Book is defined as a struct, returning a Book returns a reference already. You don't need to use the ref keyword. In C# all objects are reference types and therefore always passed by reference.
So, you say, that I don't need ref in method. But i tried that.
C#:
public static Book GetBookByTitle(string title)
{
    for (int i = 0; i < booksList.Count; i++)
        if (booksList[i].Title.Contains(title))
            return booksList[i];
return emptyBook;
}
So, I tried different ways:
1) if I understand you right, we must get link, but when we change object, we not change it in list.
C#:
Book book = Library.GetBookByTitle("Call");
book = new Book();
I thought I create a copy higher, mbe I need to call like:
ref Book bookInList = ref Library.GetBookByTitle("Call");
But I get error A ref or out value must be an assignable variable
2) so, I tried create a method that will be change selected book to emty.
But, nothing changed in list.
C#:
        public static void ClearBookByTitle(string title)
        {
            for (int i = 0; i < booksList.Count; i++)
                if (booksList[i].Title.Contains(title)) {
                    booksList[i] = new Book();
                    return;
                }
        }
Does it mean that when we get object from list, we get copy of it?
If I understand you right, list position in memory can change during runtime, so, we can't get refs?
 
So, I tried different ways:
1) if I understand you right, we must get link, but when we change object, we not change it in list.
C#:
Book book = Library.GetBookByTitle("Call");
book = new Book();
Why would you expect that to do anything useful? The first line gets a Book object from the list and assigns it to a variable. That variable knows about the book but it doesn't know anything about the list. You then assign a new Book object to the variable. The variable no longer refers to the Book from the list, but that doesn't change the list because it doesn't change the Book that is in the list. If you change a property of the Book object you retrieved, then you'll see that change reflected in the list, because its the same Book.

Consider this. Let's say that a car rental company has a list of cars that they own. You get one of the cars from their list to drive. You then decide to buy a new car and use that instead of the rental car. Does that mean that the rental company now have your new car on their list? Of course not! That would be madness. If you were to repaint the car you got though, the rental company would now have a repainted car on their list. OOP is designed to mimic the behaviour of real-life objects. If you want to know how class instances work, just think about an analogous situation in real life and that's it.
 
So, you say, that I don't need ref in method
We hardly ever need ref, anywhere, ever. My advice to new programmers is to avoid it, avoid `out`, and avoid `static`

If you want to return multiple things from a method, use a Tuple or make a class so you can return eg a Book that represents multiple related bits of information like Author and Title. If you're trying to use ref because you want some method to swap out an object for another, even when it's none of that method's business to do so, then don't; it leads to obscure bugs and hard to follow logic if e.g. the person that borrowed a book is capable of swapping it for a new one and affecting the library contents

Avoid `static` because static is like a weird opposite universe where there is only ever one of anything, and that's more or less the opposite of what you're trying to achieve with OOP. The OO universe can reach into the static universe but not the other way round, which means that a beginner's (usually) poorly structured programs start acquiring `static` all over the place because the developer "just want the 'an object reference is required for the non static..' error messages to go away"

we must get link, but when we change object
It's not the responsibility of the person borrowing the book, to change it to something else. The book never leaves the library's management system. You look the book record up, you change the property that tracks who has borrowed it, and maybe the date, and there it stays in the list, but updated with new properties (or if you're on a non mutable streak you make a new object with all the old properties and the new borrower/date info.
What you're currently trying to do is conceptually give the person borrowing the book the keys to the library and the password to the computer, so that they have the power to sneak in late at night and change out the book for another one, and erase the knowledge the library computer has that there ever was the old book in the library
I tried create a method that will be change selected book to emty.
And it looks fine. If it didn't work, check that the book title you entered really was found; c# is case sens in your code
Does it mean that when we get object from list, we get copy of it?
If it's a class, then no; your variable reference points to the original data. This is a point of confusion for many newbies. The original data that makes up a class lives in one place in memory and it is not copied unless you explicitly perform a cloning operation (which you have to implement explicitly)

C#:
var book = new Book("Bob C Martin", "Clean Code");
var book2 = book;
var books = new List<Book>();
books.Add(book);
var book3 = books[0];
books.Add(book3);

In all these lines, there is still only one set of book data, and there are 5 different references to it. If it were a dog, it has 5 leads attached to its collar.

9658222B-747C-4083-8D03-EB45427335E6.jpeg


Every time we do `var x = ...` we're making another variable reference and then with the assignment, we're making it points to the same data. You have 5 copies of a variable, but only one set of data.

They do not chain like this:

886F771F-EFCD-409D-AF52-5EBE5385490F.jpeg


Everything individually points to the original data. If you change what `book` points to, all the others stay pointing to the original data

C#:
book = new Book("Another", "Book");

Gives:
37207E90-671D-41E8-9D2E-9E615873206B.jpeg


so, we can't get refs?

You really don't need to. In the case where you have multiple references to one book, you can alter something about the book and all references see it

C#:
book.Author = "JKR";
book.Title = "HPotter";

27D4D9A4-D438-43B1-AF5B-EEA202FA9003.jpeg


But you cannot(should not) try to arrange a setup where any one variable reference is capable of swapping out the entire book object for a whole new one. That's realistically the job of just one of your objects- the library. If the librarian decides to throw out some old book and store a new one on position 2 in the shelf then the method that does that should be inside the library. It should not be something the person who borrowed the book is capable of doing

If you're doing this because you're trying to make some sort of internal find method inside library, that can be used in various places, consider returning the index the book was found at instead of returning the book:

C#:
class Library{

    List<Book> books = new();

    private int FindIndex(string t){
      for(int x = 0; x<books.Count; x++){
        if(books[x].Title.Contains(t) return x;
      }
      return -1;
    }

    public Book GetBookByTitle(string title){
      var idx = FindIndex(title);
      return books[idx];
    }

   
    public void EraseBookByTitle(string title){
      var idx = FindIndex(title);
      books[idx] = new Book();
    }
 

Attachments

  • 78582A81-DFEB-486A-9F12-C42E3EF5FA1D.jpeg
    78582A81-DFEB-486A-9F12-C42E3EF5FA1D.jpeg
    226.8 KB · Views: 16
Last edited:
Solution
*** removed unnecessary quote ***

Very in-depth explanation, even more than was needed, but still, thank you very much, I will remember your advices. ( ◜‿◝ ) ♡
 
Last edited by a moderator:
What does that buy you if bookList is already a list of references to Books?

It's not like it lets you replace the book reference that is the list:
C#:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

record class Book(string Title, string Author);

class Library
{
	List<Book> _books = new ();
	
	public void Add(Book book)
		=> _books.Add(book);
	
	public Book GetBook(int index)
		=> _books[index];

	public ref Book GetBookRef(int index)
	{
		var booksListSpan = CollectionsMarshal.AsSpan(_books);
		return ref booksListSpan[index];
	}
	
	public void ShowBooks()
	{
		Console.WriteLine(">>>");
		foreach(var book in _books)
			Console.WriteLine(book);
		Console.WriteLine("<<<");
	}
}

public class Program
{
	public static void Main()
	{
		var theHobbit = new Book("The Hobbit", "J.R.R. Tolkien");
		var theEight = new Book("The Eight", "Katherine Neville");

		var library = new Library();
		library.Add(theHobbit);
		library.Add(theEight);

		library.ShowBooks();
		
		var xRef = library.GetBookRef(1);
		Console.WriteLine(xRef);
		xRef = new Book("The Anarchist's Work Bench", "Chris Schwartz");
		Console.WriteLine(xRef);

		library.ShowBooks();
	}
}

Output:
Code:
>>>
Book { Title = The Hobbit, Author = J.R.R. Tolkien }
Book { Title = The Eight, Author = Katherine Neville }
<<<
Book { Title = The Eight, Author = Katherine Neville }
Book { Title = The Anarchist's Work Bench, Author = Chris Schwartz }
>>>
Book { Title = The Hobbit, Author = J.R.R. Tolkien }
Book { Title = The Eight, Author = Katherine Neville }
<<<
 
Talk about breaking encapsulation... :)
 
Back
Top Bottom