Danny
Danny

Reputation: 301

Passing by reference, a member, of an object in a generic collection being accessed by indexer

I have created an instance of a generic collection in C#, and need to pass one of the members of a struct in this collection by reference to a method. Can I use the generic collection's indexer to select which object's member I want to modify in the method? I seem to get an error ("Cannot modify the return value of 'expression' because it is not a variable") but what I have is similar to this:

Deque<Card> deck_of_cards = new Deque<Card>();  // standard deck of 52 playing cards (structs)

ModifyRank( ref deck_of_cards[4].rank, 8);  // Changes the rank (field, int) of the 5th card to 8

I'm converting C++ code which is using std::deque and global methods, and I want to preserve as much as I can in terms of syntax. Does anyone know an elegant solution to this problem?

Upvotes: 1

Views: 849

Answers (4)

supercat
supercat

Reputation: 81169

Although the the .net generic collections don't provide good support for working with mutable structs, if you design your own collections you can make them support mutable structs efficiently. I would suggest something like the following pattern (assuming the object is something like EnhancedList<T>:

// Delegate type definitions--should go somewhere
  delegate ActionByRef<T1>(ref T1 p1);
  delegate ActionByRef<T1,T2>(ref T1 p1, ref T2 p2);
  delegate ActionByRef<T1,T2,T3>(ref T1 p1, ref T2 p2, ref T3 p3);
// Code within the EnhancedList<T> type:
  T _items[];  // Array to hold actual items
  void ActOnItem(int index, ActionByRef<T> proc)
    { proc(ref _items[index]); }
  void ActOnItem<PT1>(int index, ActionByRef<T,PT1> proc, ref PT1 p1)
    { proc(ref _items[index], p1); }
  void ActOnItem<PT1,PT2>(int index, ActionByRef<T,PT1,PT2> proc, 
                          ref PT1 p1, ref PT2 p2)
    { proc(ref _items[index], ref p1, ref p2); }

Using such an approach, one can have items of the collection passed by ref to any desired code. It's a bit annoying that there's no way to handle variadic generics, but the approach allows arbitrary true pass-by-reference semantics provided one defines ActOnItem variants with enough parameters.

Upvotes: 0

Pent Ploompuu
Pent Ploompuu

Reputation: 5414

It's not possible to pass by reference a field of a struct got through an indexer. To modify the content of a struct in such a collection requires separate get and set operations because an indexer can't return a value by reference (at least not in C# - it might be possible with unverifiable MSIL).

The most straightforward solution to the specific problem would be:

Deque<Card> deck_of_cards = new Deque<Card>();  // standard deck of 52 playing cards
var tmp = deck_of_cards[4];
ModifyRank( ref tmp.rank, 8);  // Changes the rank (int) of the 5th card to 8
deck_of_cards[4] = tmp;

Upvotes: 2

vgru
vgru

Reputation: 51224

Passing parameters with the ref keyword is discouraged in C#. Instead, presuming that a Card is a class, you may want to change the method's signature to simply pass the card:

ModifyRank(deck_of_cards[4], 8); 

Since classes are reference types in C#, ModifyRank will modify the contents of the card passed as the parameter.

Upvotes: 2

Adam Mihalcin
Adam Mihalcin

Reputation: 14458

Without seeing the Card class I can't be sure, but I suspect that the rank property of a Card is a property, not a field. In other words, you have the declaration

public class Card
{
    public int rank { get; set; }
}

rather than

public class Card
{
    public int rank;
}

But the property syntax (my first example) is actually syntactic sugar that is rewritten by the compiler to something like

public class Card
{
    private int _rank;

    public int get_rank()
    {
        return rank;
    }

    public int set_rank(int rank)
    {
        _rank = rank;
    }
}

This means that the rank property on a Card isn't an int at all, but a wrapper around two methods. Since rank is not an int, you can't pass it by reference.

And now the elegant solution:

If the ModifyRank method is only setting the value of its ref parameter, you can simplify your second line of code to be

deck_of_cards[4].rank = 8;

which is much more clear to any reader. And if your ModifyRank method is doing something special, you can add this extension method to some static class:

public static void ModifyRank(this Card c, int newRank)
{
    int rank = c.rank;
    ModifyRank(ref rank, newRank);
    c.rank = rank;
}

This allows you to replace your second line with

deck[4].ModifyRank(8);

Upvotes: 0

Related Questions