Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v7.x Q.s re the "ref local and returns" spec #214

Closed
RexJaeschke opened this issue Jan 24, 2021 · 3 comments
Closed

v7.x Q.s re the "ref local and returns" spec #214

RexJaeschke opened this issue Jan 24, 2021 · 3 comments
Labels
type: feature This issue describes a new feature
Milestone

Comments

@RexJaeschke
Copy link
Contributor

RexJaeschke commented Jan 24, 2021

In Draft PR #213, I've spec'd "ref locals and returns." This issue asks some questions that might impact that spec.

Question 1:

Given the following method declarations:

public static int M1() {}
public static ref int M2() {}
public static ref readonly int M3() {}

and the following ref-assignment:

int i;
i = M1() - M2() + M3();

the values returned from each method are used to compute the result that is stored in i. However, M2 and M3 actually return a reference to a writeable and read-only int, respectively. Is there an implicit conversion going on here? That is, when one has a reference, but a value is expected, the value is taken. I ask, as my spec does not mention any such conversion.

Question 2:

Given

foreach_statement
    : 'foreach' '(' local_variable_type identifier 'in' expression ')' embedded_statement
    ;

it seems like local_variable_type could/should allow a ref or ref readonly prefix, as follows:

foreach_statement
    : 'foreach' '(' (ref readonly?)? local_variable_type identifier 'in' expression ')' embedded_statement
    ;

While my compiler allows these keywords there, I was not able to get an example to compile. Can someone show me correct code examples using ref and ref readonly, so I can write text for this case?

Question 3:

In 13.11, "The try statement," we have

...
catch_clause
    :  'catch' exception_specifier?  block
    ;

exception_specifier
    : '(' type identifier? ')'
    ;

The compiler does not allow ref or ref readonly prior to type. Should it? That is, can we catch-by-ref?

@RexJaeschke RexJaeschke added the type: feature This issue describes a new feature label Jan 24, 2021
@RexJaeschke RexJaeschke added this to the C# 7.x milestone Jan 24, 2021
@KalleOlaviNiemitalo
Copy link
Contributor

Re question 3, I don't think catch-by-ref should be allowed. If it were allowed, I imagine it could be used for:

  • Not making a copy of the caught value.
    • The type in exception_specifier must be derived from System.Exception and thus a reference type, so it's cheap to copy anyway.
  • Reassigning the reference, to make a subsequent throw; rethrow an exception instance different from the one that was caught.
    • It is not clear how that should affect Exception.StackTrace.
    • Would need to change ECMA-335 6th ed. §II.19 (Exception handling) and §II.25.4.6 (Exception handling clauses) to describe how the catch block is marked to give it a managed pointer rather than a reference.
    • One can already use throw expression for a similar effect.

.NET SDK 5.0.203 apparently allows returning by reference in property_declaration and indexer_declaration but not operator_declaration. I haven't found a rationale for that.

@KalleOlaviNiemitalo
Copy link
Contributor

Re question 2:

using System;

class ForeachRefDemo
{
    static void Main()
    {
        int[] array = new int[5];

        foreach (ref int i in array.AsSpan())
        {
            i = 42;
        }

        Console.WriteLine(array[2]);
    }
}

Or without using Span:

using System;

class ForeachRefDemo
{
    static void Main()
    {
        int[] array = new int[5];

        foreach (ref int i in new IntArrayRefEnumerable(array))
        {
            i = 42;
        }

        Console.WriteLine(array[2]);
    }
}

struct IntArrayRefEnumerable
{
    int[] array;

    public IntArrayRefEnumerable(int[] array)
    {
        this.array = array;
    }

    public IntArrayRefEnumerator GetEnumerator() => new IntArrayRefEnumerator(this.array);
}

struct IntArrayRefEnumerator
{
    int[] array;
    int index;

    public IntArrayRefEnumerator(int[] array)
    {
        this.array = array;
        this.index = -1;
    }

    public bool MoveNext()
    {
        if (this.index < this.array.Length - 1)
        {
            this.index++;
            return true;
        }
        else
        {
            return false;
        }
    }

    public ref int Current => ref this.array[this.index];
}

@RexJaeschke
Copy link
Contributor Author

Re Question 1: This is answered in 12.2.2 Values of expressions:

Most of the constructs that involve an expression ultimately require the expression to denote a value. In such cases, if the actual expression denotes a namespace, a type, a method group, or nothing, a compile-time error occurs. However, if the expression denotes a property access, an indexer access, or a variable, the value of the property, indexer, or variable is implicitly substituted:

  • The value of a variable is simply the value currently stored in the storage location identified by the variable. A variable shall be considered definitely assigned (§10.4) before its value can be obtained, or otherwise a compile-time error occurs.
    ...

Re Question 2: Thanks, @KalleOlaviNiemitalo for the working example. I have added support for ref readonly to foreach.

Re Question 3: @KalleOlaviNiemitalo answered this. No change is needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: feature This issue describes a new feature
Projects
None yet
Development

No branches or pull requests

2 participants