10

I want to do something like this:

double a, b, c, d, e;
ParseAndWrite("{1, 2, 3}", ref a, ref b, ref c);
ParseAndWrite("{4, 5}", ref d, ref e);

-> a = 1, b = 2, c = 3, d = 4, e = 5

However, I can not write a function like this:

private void ParseAndWrite(string leInput, params ref double[] targets)
{
   (...)
}

This doesn't work, for some reason one can not use ref and params at the same time. Why so?

edit: OK, here's some more information on why I need this: Through an Interface I get a lot of strings containing values, with a syntax like this:

inputConfig : " step, stepHeight, rStep, rStepHeight, (nIterations, split, smooth) "
outputConfig : " dataSelection, (corrected, units, outlierCount, restoreOriginalRange) "

(names in brackets are optional). Those values need to be parsed and stored in all specific variables. That is - they are not arrays at all. They are more like command-line arguments, but around 20 of them. I can of course do all that sequencially, but that produces hundreds of lines of code that contain a redundant pattern and are not well maintainable.

user
  • 5,335
  • 7
  • 47
  • 63
Efrain
  • 3,248
  • 4
  • 34
  • 61
  • 1
    Possible duplicate of http://stackoverflow.com/questions/1776020/interesting-params-of-ref-feature-any-workarounds – Dennis Traub Dec 07 '11 at 14:18
  • I get a lot of comma-separated strings through an interface. They contain different numbers of values that need to be filled into a large number of destination parameters. To reduce code redundancies, I'm looking for a function like this. – Efrain Dec 07 '11 at 14:23
  • 1
    I can appreciate the dilemma, but the standard way of returning a sequence of values would be to return an `IEnumerable`, this is what most of the BCL does. – James Michael Hare Dec 07 '11 at 14:45
  • @Dennis Traub: Thanks. This *IS* a duplicate, although the context is slightly different. The answer is long and complicated, and can be found in the here: http://stackoverflow.com/questions/1776020/interesting-params-of-ref-feature-any-workarounds – Efrain Dec 07 '11 at 15:35
  • I always find it fascinating what lengths people will go to to avoid actually doing good OO. – Bill K Dec 07 '11 at 19:28

4 Answers4

16

for some reason one can not use ref and params at the same time. Why so?

Consider these three desirable characteristics of cameras:

  1. lightweight

  2. high-quality lens

  3. inexpensive

You get to have at most two of those in any camera. You never get all three. You can get inexpensive heavy big-lens cameras, or expensive lightweight big-lens cameras, or inexpensive lightweight snapshot cameras, but there are no inexpensive, lightweight cameras with big lenses.

Returning now to your question. Consider these three desirable characteristics of a runtime:

  1. param arrays of ref-to-variable types are legal

  2. type system is safe

  3. local variables that have refs to them are still fast to allocate and deallocate

You get to have any two but you can't have all three. Which two would you like?

You can have ref param arrays and a safe type system at the cost of ref'd local variables being allocated on the garbage collected heap. To my knowledge no one does this, but it is certainly possible.

You can have ref param arrays, all local variables allocated on the temporary pool, and a type system that crashes when you use it wrong. This is the "C/C++" approach; it is possible to take a reference and store it in a location that has longer lifetime than the lifetime of the thing being referenced. If you do that, you can expect to have your C++ program crash and die in the most horrible possible ways by making easy mistakes that are hard to detect. Welcome to the pit of despair.

Or we can make ref param arrays illegal, allocate all local variables on the temporary pool, and have a type system that is verifiably memory-safe. That's the choice we made when building C# and the CLR; welcome to the pit of quality.


Now, what I said above is actually all a big lie. It's a lie because the runtime, and the C# language, actually do support a feature akin to (but not identical to) parameter arrays of refs. It is a pleasant lie and believe me, you want to live in the world where you believe the lie, so I recommend that you take the blue pill and continue to do so.

If you want to take the red pill and find out how deep the rabbit hole goes, you can use the undocumented __arglist feature of C# to build a C-style variadic method, and then make TypedReference objects that refer to references of arbitrarily fields or locals of struct types, and then pass arbitrarily many of them to the variadic method. Use either the factory methods of TypedReference or the undocumented __makeref feature of C#.

The reason we've kept these features undocumented for the last decade is because they are by design there to be used only for the extremely rare situations where you must write C# code that interoperates cleanly with variadic methods written in other languages.

Correctly using TypedReference objects is not for the faint of heart and again I strongly recommend against doing so. I have never once done so in production code in many years of writing C#. If you want to pass around references to arbitrarily many variables, pass an array. An array is by definition a collection of variables. That is much safer than passing around a bunch of objects that represent managed addresses of instance or local variables.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Couldn't you instead use something akin to closures as is done with lambdas? I.e., local variables are fast to allocate and deallocate, except when want to use arrays of ref-to-variable types. I'm not claiming this feature is worth the cost of implementing it. I just wonder if my initial inclination is feasible. – Brian Dec 07 '11 at 16:34
  • 1
    @Brian: You are correct in that we could realize *only ref'd locals* as fields of a closure. However, we'd have to realize *all* ref'd locals as fields of a closure, not just ones that happened to end up in an array. A ref local that does not end up in an array immediately could end up in an array later; it might be passed to a method that then stores it in an array. I'll clarify the answer a bit, thanks. – Eric Lippert Dec 07 '11 at 16:36
3

There's a lot of "why doesn't language X do Y" type questions one could ask. Most often, the answer is because there probably wasn't a great need for that feature.

There's something about a variadic ref parameter set that feels very odd, and I can't imagine it would be useful except in some limited circumstances, and there are typically better abstractions for returning a list of items.

You could pass in a pre-sized double[] for the method to fill, of course, though my preference is to just return an IEnumerable<double>

James Michael Hare
  • 37,767
  • 9
  • 73
  • 83
  • Returning a value means I need to unpack those results and assign these values. But that's exactly what I need the function for. I'm asking "why" because when I use param object[] (not built-in types) I actually DO pass references to objects that can be altered. Why shouldn't this work with, say, fields that are built-in types? – Efrain Dec 07 '11 at 14:30
  • 1
    @Efrain: Because it's the difference between reference and value types, when you pass in a variadic parameter list, the parameters are immutable. You have to remember that when you pass in references (instead of value types) the reference still can't change, only the object that it refers to (which is not what's directly passed in). – James Michael Hare Dec 07 '11 at 14:35
  • @Efrain: Does that make sense? That's like asking why calling Func(myObject) can change the contents of the object but Func(myInt) can't change the value of the int. It's just the difference between value and reference types in C#. – James Michael Hare Dec 07 '11 at 14:36
  • Uh, yes? But the 'ref' keyword is for this exact purpose: to alter the actual value-type variables like so: Func(ref myInt) – Efrain Dec 07 '11 at 14:48
  • 1
    Close, but not just for value-type variables, it's to modify any variable including reference types. If you want to pass a reference parameter into a method by reference, you'd also use `ref` (for example, if you wanted the reference to be changed by a method to `null` or a new reference (for example, if parsing a `string` and returing in a parameter, you'd need `ref`). – James Michael Hare Dec 07 '11 at 14:55
  • So, as I said, it really has nothing to do with value vs reference types, it's just that `ref`/`out` and `params` can't be combined. – James Michael Hare Dec 07 '11 at 14:56
  • Okay. Found a good answer on this topic regarding ref/out formalism: http://stackoverflow.com/questions/186891/why-use-ref-keyword-when-passing-an-object – Efrain Dec 07 '11 at 15:10
2

I don't know if this is the only reason, but here is one potential explanation:

The actual parameter is still ultimately a double[], so in order to do what you want, the compiler would actually need to write that as:

{
    double[] arr = new double[] {a, b, c, d}; // copy in
    YourMethod(arr);
    a = arr[0]; // copy back out
    b = arr[1];
    c = arr[2];
    d = arr[3];
}

However! this changes the semantic. Imagine the calling method was actually:

void SomeCallingMethod(ref double a)
{
    double b = ..., c = ... d = ...;
    YourMethod(ref a, ref b, ref c, ref d);
    /*  which, say, is translated to
    {
        double[] arr = new double[] {a, b, c, d}; // copy in
        YourMethod(arr);
        a = arr[0]; // copy back out
        b = arr[1];
        c = arr[2];
        d = arr[3];
    }  */      
}

The expected behaviour is that we are updating a directly at all times, but in reality we aren't. This could lead to odd and confusing bugs. Indeed, to do it properly the "copy back out" would need to go into a finally, since actually ref values should show changes until the exception, but that still doesn't change the semantic of updating after the method call rather than during.

You also get into a very odd question of: for how long does updating the array update the caller? after all, an array can be stored anywhere, since it isn't on the stack.


There is another way of sending that, as an array of references to the values, but that could be really deadly:

  • it would make the code essentially unsafe
  • if the array was stored anywhere (which arrays can be), the calling method could return and de-scope a local that was being used, meaning that there would be a valid-looking reference that actually points to garbage - major corruption ahoy

All in all, no good things come out of doing it, and lots of bad things.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
0

The reason is probably technical. 'params' is just a syntaxic sugar for an array argument. So ParseAndWrite(string leInput, params double[] targets) is compiled as ParseAndWrite(string leInput, params double[] targets).

Then, ParseAndWrite(string leInput, params ref double[] targets) would be compiled as ParseAndWrite(string leInput, ref double[] targets). Basically, it would be the array that is passed as reference, not the actual content of the array. Which obviously wouldn't be the behaviour expected by developers, and would therefore be error-prone.

Kevin Gosse
  • 38,392
  • 3
  • 78
  • 94