72

I just ran across this error message while working in C#

A property or indexer may not be passed as an out or ref parameter

I known what caused this and did the quick solution of creating a local variable of the correct type, calling the function with it as the out/ref parameter and then assigning it back to the property:

RefFn(ref obj.prop);

turns into

{
    var t = obj.prop;
    RefFn(ref t);
    obj.prop = t;
}

Clearly this would fail if the property doesn't support get and set in the current context.

Why doesn't C# just do that for me?


The only cases where I can think of where this might cause problems are:

  • threading
  • exceptions

For threading that transformation affects when the writes happen (after the function call vs. in the function call), but I rather suspect any code that counts on that would get little sympathy when it breaks.

For exceptions, the concern would be; what happens if the function assigns to one of several ref parameters than throws? Any trivial solution would result in all or none of the parameters being assigned to when some should be and some should not be. Again I don't think this would be supported use of the language.


Note: I understand the mechanics of why this error messages is generated. What I'm looking for is the rationale for why C# doesn't automatically implement the trivial workaround.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
BCS
  • 75,627
  • 68
  • 187
  • 294
  • It does not do the workaround because it is not always safe. The more fundamental problem is that a property of type `T` shouldn't just have a getter and setter method, but should also have an `ActUpon([indexparams...,] ActionByRef act, ref U param)` method. Such a method would allow a statement like `ListOfPoints[4].X += something` to be executed efficiently, using a static delegate and no closures (`ListOfPoints.ActUpon(4, (ref Point it, ref param) => {it.X += param;}, ref something);`). – supercat Jun 26 '12 at 17:45
  • 5
    The road to sugary compilers is paved with good intentions. – BC. Feb 09 '09 at 20:32
  • A solid way to solve this with Expressions at the bottom of this answer: http://stackoverflow.com/a/3059448/176877 – Chris Moschini Nov 03 '13 at 20:07

9 Answers9

31

Because you're passing the result of the indexer, which is really the result of a method call. There's no guarantee that the indexer property also has a setter, and passing it by ref would lead to a false security on the developer's part when he thinks that his property is going to be set without the setter being called.

On a more technical level, ref and out pass the memory address of the object passed into them, and to set a property, you have to call the setter, so there's no guarantee that the property would actually be changed especially when the property type is immutable. ref and out don't just set the value upon return of the method, they pass the actual memory reference to the object itself.

David Morton
  • 16,338
  • 3
  • 63
  • 73
  • 1
    +1, important to note that P/Invoke could choke as the object the property is a member of could no longer have a GC root and be collected! Doh... – user7116 Feb 09 '09 at 21:49
  • @sixletter: not so, for the object to be collected the ref to it in the calling code would have to be dropped. – BCS Feb 10 '09 at 04:57
  • 3
    The problem you described would resolve its self as the sugar would, at compile time, attempt to resolve the setter and fail with the same error that attempting to assign to a get only property would. – BCS Feb 10 '09 at 05:00
  • 1
    Yes, but keep in mind that the C# Property is syntactic sugar in and of itself. People already get confused because a property looks just like a field. Addding even more syntactic sugar would most certainly not help this issue. There is, and should be, a limit. – David Morton Feb 10 '09 at 12:22
  • If the property is a string, the result of the property is a pointer to that string. A ref is a pointer to that pointer. So it shouldn't matter whether it is a property or not, it should fixup the reference. This fixup is done and works just fine in VB.net, it is C# that seems lacking. And you're right, when passed as a ref the setter is called, as it should be, and there is no guarantee that the property will actually be changed. What is guaranteed is your setter code will run. This is also verifiable to work in VB.NET. – Brain2000 May 03 '13 at 16:26
16

Properties are nothing more than syntactic sugar over the Java style getX/setX methods. It doesn't make much sense for 'ref' on a method. In your instance it would make sense because your properties are merely stubbing out fields. Properties don't have to just be stubs, hence the framework cannot allow 'ref' on Properties.

EDIT: Well, the simple answer is that the mere fact that a Property getter or setter could include far more than just a field read/write makes it undesirable, not to mention possibly unexpected, to allow the sort of sugar you are proposing. This isn't to say I haven't been in need of this functionality before, just that I understand why they wouldn't want to provide it.

user7116
  • 63,008
  • 17
  • 141
  • 172
12

Just for info, C# 4.0 will have something like this sugar, but only when calling interop methods - partly due to the sheer propensity of ref in this scenario. I haven't tested it much (in the CTP); we'll have to see how it pans out...

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    MS c# futures page: http://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=csharpfuture&DownloadId=3550 – Marc Gravell Feb 09 '09 at 21:20
  • 1
    "specifically for COM methods, the C# compiler will allow you to pass arguments by value to such a method, and will automatically generate temporary variables to hold the passed-in values, subsequently discarding these when the call returns. " – Marc Gravell Feb 09 '09 at 21:21
  • C# is not a low level language, so a ref parameter should not be an address, but actually a pair of closures, a getter and a setter. If you pass a local variable as a ref parameter, the language should automatically create a getter/setter pair. And all this should be hidden from the unsuspecting programmer – isekaijin Jun 28 '11 at 20:12
  • @Eduardo that changes the semantic is breaking ways, especially for value-types; that will never happen. I wouldn't want it to – Marc Gravell Jun 28 '11 at 20:29
  • @Marc: How would that change the semantics of anything? After all, a variable is just a mechanism to read (get) and write (set) to a bunch of memory. – isekaijin Jun 28 '11 at 20:35
  • @Eduardo no it isn't; in the case of value-types it *is* the data. An accessor (property) takes a *copy*). Which is a problem in the case of mutable structs. Now, you could argue we shouldn't have mutable structs... but: we do. – Marc Gravell Jun 28 '11 at 20:40
8

You can use fields with ref/out, but not properties. The reason is that properties are really just a syntax short cut for special methods. The compiler actually translates get / set properties to corresponding get_X and set_X methods as the CLR has no immediate support for properties.

Brian Rasmussen
  • 114,645
  • 34
  • 221
  • 317
6

It wouldn't be thread-safe; if two threads simultaneously create their own copies of the property value and pass them to functions as ref parameters, only one of them ends up back in the property.

class Program
{
  static int PropertyX { get; set; }

  static void Main()
  {
    PropertyX = 0;

    // Sugared from: 
    // WaitCallback w = (o) => WaitAndIncrement(500, ref PropertyX);
    WaitCallback w = (o) => {
      int x1 = PropertyX;
      WaitAndIncrement(500, ref x1);
      PropertyX = x1;
    };
    // end sugar

    ThreadPool.QueueUserWorkItem(w);

    // Sugared from: 
    // WaitAndIncrement(1000, ref PropertyX);
    int x2 = PropertyX;      
    WaitAndIncrement(1000, ref x2);
    PropertyX = x2;
    // end sugar

    Console.WriteLine(PropertyX);
  }

  static void WaitAndIncrement(int wait, ref int i)
  {
    Thread.Sleep(wait);
    i++;
  }
}

PropertyX ends up as 1, whereas a field or local variable would be 2.

That code sample also highlights the difficulties introduced by things like anonymous methods when asking the compiler to do sugary stuff.

Mark Rendle
  • 9,274
  • 1
  • 32
  • 58
  • A good point, but I think what you have shown is that the transformation can alter the result of code that already has race conditions, e.g. non thread safe code. – BCS Feb 09 '09 at 21:52
  • That's true, in the example I've given. Oops. – Mark Rendle Feb 10 '09 at 00:45
  • I disagree with you. The above example simply shows that x1 and x2 are both set to 0, and then both are incremented to 1 after the period of 500 and 1000 milliseconds, and then they both set PropertyX to a 1. Neither local variable is ever a 2. You need to pass PropertyX into WaitAndIncrement( ) in order to truly test this, which can only be done in VB.NET. However, it probably still won't work because the getters are called first. It has nothing to do with it being thread safe. It is no less thread safe than using a local variable without any interlocked functions. – Brain2000 May 03 '13 at 17:04
  • Read my answer again, more carefully, and you'll find you don't disagree with me at all. – Mark Rendle May 04 '13 at 22:54
5

The reason for this is that C# does not support "parameterful" properties that accept parameters passed by reference. It is interesting to note that the CLR does support this functionalty but C# does not.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
4

When you pass ref/out prepended it means that you are passing a reference type which is stored in the heap.

Properties are wrapper methods, not variables.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Igor Zelaya
  • 4,167
  • 4
  • 35
  • 52
0

If you're asking why the compiler doesn't substitute the field returned by the property's getter, it's because the getter can return a const or readonly or literal or something else that shouldn't be re-initialized or overwritten.

BC.
  • 24,298
  • 12
  • 47
  • 62
0

This site appears to have a work around for you. I have not tested it though, so I can't guarantee it will work. The example appears to use reflection in order to gain access to the get and set functions of the property. This is probably not a recommended approach, but it might accomplish what you're asking for.

http://www.codeproject.com/KB/cs/Passing_Properties_byref.aspx

regex
  • 3,585
  • 5
  • 31
  • 41