1

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?

Danny
  • 181
  • 1
  • 14
  • Why won't you just write `deck_of_cards[4].rank = 8;`? – svick Jan 26 '12 at 01:13
  • because the actual method i'm using has multiple reference parameters and modifies objects much more complicated than a playing card in many places, and there are several of these methods. I want to keep the syntax and style as close to the C++ code as possible – Danny Jan 26 '12 at 01:17
  • It's hard to advise you what code should you write if you don't tell us what exactly do you want to do. Method with too many parameters may be a sing of bad design. Also, *why* do you want to stay as close as possible to your old code? Why are you even converting it? – svick Jan 26 '12 at 01:25
  • 2
    @user1130698: C# is not C++, as similar as it may seem on the first glance. While you may feel like you are safer using C++ style constructs in C#, you would benefit more from rewriting the logic in the long run. And if the code is that complex, then it may already be violating SOLID OOP principles, even if it's a "legacy" project. – vgru Jan 26 '12 at 01:25
  • in the C++ project there was a file with structs, a file with global functions, a file with #define constants, and file with a class that uses these. I placed the global functions and constants in a static Constants class, edited the structs, and am now modifying the class that uses these. the functions perform calculations with several inputs and outputs. This problem, I hope, is the only thing keeping me from using the translated class in a larger C# project – Danny Jan 26 '12 at 01:36

4 Answers4

2

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.

vgru
  • 49,838
  • 16
  • 120
  • 201
  • sorry, forgot to mention that "Card" is a struct, not a class – Danny Jan 26 '12 at 01:20
  • also, the method is more like ModifyInt() and is used for more than just the rank members of cards – Danny Jan 26 '12 at 01:21
  • 2
    Does it have to be a `struct`? And having mutable `struct`s is highly discouraged, you have to be very careful with them. – svick Jan 26 '12 at 01:26
  • 2
    @svick Agreed. As the great sage Eric Lippert says, mutable value types are evil. http://blogs.msdn.com/b/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx – Adam Mihalcin Jan 26 '12 at 01:29
  • 2
    @user1130698: that's another thing to avoid in C#, as svick mentioned. Structs are usually avoided (except for really small entities, for performance reasons only), but [mutable structs are even worse](http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil). – vgru Jan 26 '12 at 01:31
2

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;
Pent Ploompuu
  • 5,364
  • 1
  • 27
  • 47
  • I ended up using this solution; there weren't as many instances of this particular problem in my code as I originally thought. Thanks – Danny Jan 26 '12 at 04:39
0

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);
Adam Mihalcin
  • 14,242
  • 4
  • 36
  • 52
  • `deck_of_cards[4].rank = 8;` I don't believe this will work for the reason mentioned by Pent. I verified that the member is a field, not a property. I didn't know about extension methods... thanks for that tip – Danny Jan 26 '12 at 04:36
0

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&gt proc)
    { proc(ref _items[index]); }
  void ActOnItem<PT1>(int index, ActionByRef<T,PT1&gt proc, ref PT1 p1)
    { proc(ref _items[index], p1); }
  void ActOnItem<PT1,PT2>(int index, ActionByRef<T,PT1,PT2&gt 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.

supercat
  • 77,689
  • 9
  • 166
  • 211