Reputation: 301
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
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
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
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
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