읽기 전용 참조Readonly references

  • [x] 제안 됨[x] Proposed
  • [x] 프로토타입[x] Prototype
  • [x] 구현: 시작 됨[x] Implementation: Started
  • [] 사양: 시작 되지 않음[ ] Specification: Not Started

요약Summary

"Readonly 참조" 기능은 실제로 변수를 참조로 전달 하는 효율성을 활용 하 고 데이터를 수정 사항에 노출 시 키 지 않는 기능 그룹입니다.The "readonly references" feature is actually a group of features that leverage the efficiency of passing variables by reference, but without exposing the data to modifications:

  • in 변수의in parameters
  • ref readonly returnref readonly returns
  • readonly structsreadonly structs
  • ref/in 확장 메서드ref/in extension methods
  • ref readonly 창을ref readonly locals
  • ref 조건식ref conditional expressions

인수를 readonly 참조로 전달 합니다.Passing arguments as readonly references.

이 항목에는 https://github.com/dotnet/roslyn/issues/115 다양 한 세부 정보를 포함 하지 않고 readonly 매개 변수의 특수 한 경우에이 항목을 포함 하는 기존 제안이 있습니다.There is an existing proposal that touches this topic https://github.com/dotnet/roslyn/issues/115 as a special case of readonly parameters without going into many details. 여기서는 자체 아이디어가 그다지 새로운 것이 아님을 인정 하려고 합니다.Here I just want to acknowledge that the idea by itself is not very new.

동기Motivation

이 기능을 사용 하기 전에 c #에는를 수정할 필요 없이 읽기 전용 목적으로 구조체 변수를 메서드 호출에 전달 하는 효율적인 방법이 없습니다.Prior to this feature C# did not have an efficient way of expressing a desire to pass struct variables into method calls for readonly purposes with no intention of modifying. 값으로 정규 인수 전달은 복사를 의미 하므로 불필요 한 비용을 추가 합니다.Regular by-value argument passing implies copying, which adds unnecessary costs. 이렇게 하면 사용자가-ref 인수를 전달 하는 데 사용 하 고 주석/설명서를 사용 하 여 호출 수신자가 데이터를 변경할 수 없음을 나타낼 수 있습니다.That drives users to use by-ref argument passing and rely on comments/documentation to indicate that the data is not supposed to be mutated by the callee. 여러 가지 이유로 좋은 솔루션은 아닙니다.It is not a good solution for many reasons.
이러한 예제는 그래픽 라이브러리의 다양 한 벡터/행렬 수학 연산자입니다. XNA 는 성능 고려 사항으로 인해 순수 하 게 ref 피연산자를 포함 하는 것으로 알려져 있습니다.The examples are numerous - vector/matrix math operators in graphics libraries like XNA are known to have ref operands purely because of performance considerations. Roslyn 컴파일러 자체에는 구조체를 사용 하 여 할당을 방지 한 다음이를 참조로 전달 하 여 비용 복사를 방지 하는 코드가 있습니다.There is code in Roslyn compiler itself that uses structs to avoid allocations and then passes them by reference to avoid copying costs.

솔루션 ( in 매개 변수)Solution (in parameters)

매개 변수와 마찬가지로 out in 매개 변수는 호출 수신자의 추가 보증을 포함 하는 관리 되는 참조로 전달 됩니다.Similarly to the out parameters, in parameters are passed as managed references with additional guarantees from the callee.
out다른 사용 전에 호출 수신자가 할당 해야 하는 매개 변수와 달리 in 매개 변수는 호출 수신자가 지정할 수 없습니다.Unlike out parameters which must be assigned by the callee before any other use, in parameters cannot be assigned by the callee at all.

결과 in 매개 변수를 사용 하면 호출 수신자가 변경이에 인수를 노출 하지 않고도 간접 인수를 전달할 수 있습니다.As a result in parameters allow for effectiveness of indirect argument passing without exposing arguments to mutations by the callee.

in 매개 변수 선언Declaring in parameters

in 매개 변수는 in 매개 변수 시그니처에서 한정자로 키워드를 사용 하 여 선언 됩니다.in parameters are declared by using in keyword as a modifier in the parameter signature.

모든 용도의 in 매개 변수는 변수로 처리 됩니다 readonly .For all purposes the in parameter is treated as a readonly variable. 메서드 내의 매개 변수 사용에 대 한 대부분의 제한은 in 필드와 동일 합니다 readonly .Most of the restrictions on the use of in parameters inside the method are the same as with readonly fields.

실제로 in 매개 변수는 필드를 나타낼 수 있습니다 readonly .Indeed an in parameter may represent a readonly field. 제한의 유사성은 우연의 아닙니다.Similarity of restrictions is not a coincidence.

in구조체 형식이 있는 매개 변수의 필드 예는 모두 변수로 재귀적으로 분류 됩니다 readonly .For example fields of an in parameter which has a struct type are all recursively classified as readonly variables .

static Vector3 Add (in Vector3 v1, in Vector3 v2)
{
    // not OK!!
    v1 = default(Vector3);

    // not OK!!
    v1.X = 0;

    // not OK!!
    foo(ref v1.X);

    // OK
    return new Vector3(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}
  • in 일반 byval 매개 변수가 허용 되는 모든 위치에서 매개 변수를 사용할 수 있습니다.in parameters are allowed anywhere where ordinary byval parameters are allowed. 여기에는 인덱서, 연산자 (변환 포함), 대리자, 람다, 로컬 함수 등이 포함 됩니다.This includes indexers, operators (including conversions), delegates, lambdas, local functions.
 (in int x) => x                                                     // lambda expression  
 TValue this[in TKey index];                                         // indexer
 public static Vector3 operator +(in Vector3 x, in Vector3 y) => ... // operator
  • in 는와 함께 사용할 수 없으며와 결합 되지 않는 모든 항목과 함께 사용할 수 없습니다 out out .in is not allowed in combination with out or with anything that out does not combine with.

  • 차이점에 대 한 오버 로드는 허용 되지 않습니다 ref / out / in .It is not permitted to overload on ref/out/in differences.

  • 일반적인 byval 및 차이점에 대 한 오버 로드를 허용 in 합니다.It is permitted to overload on ordinary byval and in differences.

  • OHI의 목적 (오버 로드, 숨기기, 구현)은 in out 매개 변수와 비슷하게 동작 합니다.For the purpose of OHI (Overloading, Hiding, Implementing), in behaves similarly to an out parameter. 동일한 규칙이 모두 적용 됩니다.All the same rules apply. 예를 들어, 재정의 하는 메서드는 in 매개 변수와 in id 변환이 가능한 형식의 매개 변수를 일치 시켜야 합니다.For example the overriding method will have to match in parameters with in parameters of an identity-convertible type.

  • 대리자/람다/메서드 그룹 변환의 목적에 맞게는 in 매개 변수와 비슷하게 동작 out 합니다.For the purpose of delegate/lambda/method group conversions, in behaves similarly to an out parameter. 람다 및 적용 가능한 메서드 그룹 변환 후보는 in 대상 대리자의 매개 변수와 id로 변환할 수 있는 in 형식의 매개 변수를 일치 시켜야 합니다.Lambdas and applicable method group conversion candidates will have to match in parameters of the target delegate with in parameters of an identity-convertible type.

  • 제네릭 가변성의 목적을 위해 in 매개 변수는 변형이 아닙니다.For the purpose of generic variance, in parameters are nonvariant.

참고: in 참조 또는 기본 형식을 포함 하는 매개 변수에 대 한 경고는 없습니다.NOTE: There are no warnings on in parameters that have reference or primitives types. 일반적으로는 무의미 할 수 있지만 일부 경우에는 사용자가 기본 형식을로 전달 해야 합니다 in .It may be pointless in general, but in some cases user must/want to pass primitives as in. 예제- Method(in T param) T 가로 대체 int 되거나 메서드를 사용할 때와 같은 제네릭 메서드를 재정의 합니다. Volatile.Read(in int location)Examples - overriding a generic method like Method(in T param) when T was substituted to be int, or when having methods like Volatile.Read(in int location)

매개 변수를 비효율적으로 사용 하는 경우에 경고를 표시 하는 분석기를 사용할 수 in 있지만, 이러한 분석에 대 한 규칙이 너무 유사 하 여 언어 사양의 일부가 될 수 없습니다.It is conceivable to have an analyzer that warns in cases of inefficient use of in parameters, but the rules for such analysis would be too fuzzy to be a part of a language specification.

in호출 사이트에서를 사용 합니다.Use of in at call sites. ( in 인수)(in arguments)

매개 변수에 인수를 전달 하는 방법에는 두 가지가 있습니다 in .There are two ways to pass arguments to in parameters.

in 인수는 매개 변수와 일치할 수 있습니다 in .in arguments can match in parameters:

호출 사이트에서 한정자를 사용 하는 인수는 in 매개 변수와 일치할 수 있습니다 in .An argument with an in modifier at the call site can match in parameters.

int x = 1;

void M1<T>(in T x)
{
  // . . .
}

var x = M1(in x);  // in argument to a method

class D
{
    public string this[in Guid index];
}

D dictionary = . . . ;
var y = dictionary[in Guid.Empty]; // in argument to an indexer
  • in 인수는 읽을 수 있는 LValue (*) 여야 합니다.in argument must be a readable LValue(*). 예: M1(in 42) 가 잘못 되었습니다.Example: M1(in 42) is invalid

(*) LValue/RValue 의 개념은 언어 마다 다릅니다.(*) The notion of LValue/RValue vary between languages.
여기서 LValue는 직접 참조할 수 있는 위치를 나타내는 식을 의미 합니다.Here, by LValue I mean an expression that represent a location that can be referred to directly. 및 RValue는 자체에 유지 되지 않는 임시 결과를 생성 하는 식을 의미 합니다.And RValue means an expression that yields a temporary result which does not persist on its own.

  • 특히 readonly , 필드, in 매개 변수 또는 기타 공식적 변수를 인수로 전달 하는 것은 유효 readonly in 합니다.In particular it is valid to pass readonly fields, in parameters or other formally readonly variables as in arguments. 예: dictionary[in Guid.Empty] 는 올바릅니다.Example: dictionary[in Guid.Empty] is legal. Guid.Empty 는 정적 읽기 전용 필드입니다.Guid.Empty is a static readonly field.

  • in 인수의 형식 id는 매개 변수 형식으로 변환 될 수 있어야 합니다.in argument must have type identity-convertible to the type of the parameter. 예: M1<object>(in Guid.Empty) 가 잘못 되었습니다.Example: M1<object>(in Guid.Empty) is invalid. Guid.Emptyid로 변환할 수 없습니다. objectGuid.Empty is not identity-convertible to object

위의 규칙에 대 한 동기는 인수가 in 인수 변수의 별칭 을 보장 한다는 것입니다.The motivation for the above rules is that in arguments guarantee aliasing of the argument variable. 호출 수신자는 항상 인수로 표시 되는 것과 같은 위치에 대 한 직접 참조를 받습니다.The callee always receives a direct reference to the same location as represented by the argument.

  • 드문 경우 지만 in 인수가 동일한 호출의 피연산자로 사용 되는 식으로 인해 스택에 분산 되어야 하는 경우가 await 있습니다 .이 동작은 및 인수를 사용 하는 것과 동일 합니다 out ref . 변수를 엔터티와 투명 한 방식으로 연결할 수 없는 경우 오류가 보고 됩니다.in rare situations when in arguments must be stack-spilled due to await expressions used as operands of the same call, the behavior is the same as with out and ref arguments - if the variable cannot be spilled in referentially-transparent manner, an error is reported.

예제:Examples:

  1. M1(in staticField, await SomethingAsync()) 가 유효 합니다.M1(in staticField, await SomethingAsync()) is valid. staticField 는 관찰 가능한 부작용 없이 두 번 이상 액세스할 수 있는 정적 필드입니다.staticField is a static field which can be accessed more than once without observable side effects. 따라서 파생 된 결과와 별칭 요구 사항의 순서를 모두 제공할 수 있습니다.Therefore both the order of side effects and aliasing requirements can be provided.
  2. M1(in RefReturningMethod(), await SomethingAsync()) 에서 오류를 생성 합니다.M1(in RefReturningMethod(), await SomethingAsync()) will produce an error. RefReturningMethod() 는를 ref 반환 하는 메서드입니다.RefReturningMethod() is a ref returning method. 메서드 호출에는 관찰 가능한 부작용이 있을 수 있으므로 피연산자 앞에서 계산 해야 합니다 SomethingAsync() .A method call may have observable side effects, therefore it must be evaluated before the SomethingAsync() operand. 그러나 호출 결과는 await 직접 참조 요구 사항을 불가능 하 게 하는 일시 중단 지점에서 유지 될 수 없는 참조입니다.However the result of the invocation is a reference that cannot be preserved across the await suspension point which make the direct reference requirement impossible.

참고: stack 분산 오류는 구현 별 제한 사항으로 간주 됩니다.NOTE: the stack spilling errors are considered to be implementation-specific limitations. 따라서 오버 로드 확인 또는 람다 유추에는 영향을 주지 않습니다.Therefore they do not have effect on overload resolution or lambda inference.

일반 byval 인수는 매개 변수와 일치할 수 있습니다 in .Ordinary byval arguments can match in parameters:

한정자가 없는 일반 인수는 in 매개 변수와 일치할 수 있습니다.Regular arguments without modifiers can match in parameters. 이러한 경우 인수에는 일반 byval 인수와 동일한 완화 된 제약 조건이 있습니다.In such case the arguments have the same relaxed constraints as an ordinary byval arguments would have.

이 시나리오에 대 한 동기는 in api의 매개 변수가 인수를 직접 참조로 전달할 수 없는 경우 (예: 리터럴, 계산 됨 또는 불편 await 결과 또는 더 구체적인 형식을 사용 하 여 발생 하는 인수로) 사용자에 게이를 발생 시킬 수 있습니다.The motivation for this scenario is that in parameters in APIs may result in inconveniences for the user when arguments cannot be passed as a direct reference - ex: literals, computed or await-ed results or arguments that happen to have more specific types.
이러한 모든 경우에는 인수 값을 적절 한 형식의 임시 로컬에 저장 하 고 해당 로컬을 인수로 전달 하는 간단한 솔루션이 있습니다 in .All these cases have a trivial solution of storing the argument value in a temporary local of appropriate type and passing that local as an in argument.
이러한 상용구 코드의 필요성을 줄이기 위해 필요한 경우 in 호출 사이트에 한정자가 없는 경우 컴파일러에서 동일한 변환을 수행할 수 있습니다.To reduce the need for such boilerplate code compiler can perform the same transformation, if needed, when in modifier is not present at the call site.

또한 연산자 호출 또는 확장 메서드와 같은 일부 경우에 in 는를 지정 하는 구문상 방법이 없습니다 in .In addition, in some cases, such as invocation of operators, or in extension methods, there is no syntactical way to specify in at all. 이 경우 매개 변수와 일치할 경우에만 일반 byval 인수의 동작을 지정 해야 합니다 in .That alone requires specifying the behavior of ordinary byval arguments when they match in parameters.

특히 다음 사항에 주의하십시오.In particular:

  • Rvalue를 전달 하는 것은 유효 합니다.it is valid to pass RValues. 이 경우 임시에 대 한 참조가 전달 됩니다.A reference to a temporary is passed in such case. 예제:Example:
Print("hello");      // not an error.

void Print<T>(in T x)
{
  //. . .
}
  • 암시적 변환이 허용 됩니다.implicit conversions are allowed.

이는 실제로 RValue를 전달 하는 특별 한 사례입니다.This is actually a special case of passing an RValue

변환 된 임시 값에 대 한 참조는 이러한 경우에 전달 됩니다.A reference to a temporary holding converted value is passed in such case. 예제:Example:

Print<int>(Short.MaxValue)     // not an error.
  • 확장 메서드와 달리 확장 메서드를 받는 수신기의 경우 in ref rvalue 또는 암시적인 이 인수 변환이 허용 됩니다.in a case of a receiver of an in extension method (as opposed to ref extension methods), RValues or implicit this-argument-conversions are allowed. 변환 된 임시 값에 대 한 참조는 이러한 경우에 전달 됩니다.A reference to a temporary holding converted value is passed in such case. 예제:Example:
public static IEnumerable<T> Concat<T>(in this (IEnumerable<T>, IEnumerable<T>) arg)  => . . .;

("aa", "bb").Concat<char>()    // not an error.

확장 메서드에 대 한 자세한 내용은 ref / in 이 문서에 나와 있습니다.More information on ref/in extension methods is provided further in this document.

  • 피연산자로 인 한 인수 분산은 await 필요한 경우 "값으로" 분할할 수 있습니다.argument spilling due to await operands could spill "by-value", if necessary. 중간으로 인해 인수에 대 한 직접 참조를 제공 하는 것이 불가능 한 경우에는 await 인수 값의 복사본이 대신 분산 됩니다.In scenarios where providing a direct reference to the argument is not possible due to intervening await a copy of the argument's value is spilled instead.
    예제:Example:
M1(RefReturningMethod(), await SomethingAsync())   // not an error.

주는 호출의 결과는 일시 중단 간에 유지 될 수 없는 참조 이므로 await 실제 값을 포함 하는 임시는 대신 보존 됩니다 (일반적인 byval 매개 변수의 경우).Since the result of a side-effecting invocation is a reference that cannot be preserved across await suspension, a temporary containing the actual value will be preserved instead (as it would in an ordinary byval parameter case).

생략 가능한 선택적 인수Omitted optional arguments

in매개 변수가 기본값을 지정할 수 있습니다.It is permitted for an in parameter to specify a default value. 해당 인수를 선택적으로 만듭니다.That makes the corresponding argument optional.

호출 사이트에서 선택적 인수를 생략 하면 임시를 통해 기본값이 전달 됩니다.Omitting optional argument at the call site results in passing the default value via a temporary.

Print("hello");      // not an error, same as
Print("hello", c: Color.Black);

void Print(string s, in Color c = Color.Black)
{
    // . . .
}

일반적인 별칭 동작Aliasing behavior in general

ref및 변수와 마찬가지로 out in 변수는 기존 위치에 대 한 참조/별칭입니다.Just like ref and out variables, in variables are references/aliases to existing locations.

호출 수신자는 이러한 변수에 쓸 수 없습니다 in . 매개 변수를 읽으면 다른 평가의 부작용으로 다른 값을 관찰할 수 있습니다.While callee is not allowed to write into them, reading an in parameter can observe different values as a side effect of other evaluations.

예제:Example:

static Vector3 v = Vector3.UnitY;

static void Main()
{
    Test(v);
}

static void Test(in Vector3 v1)
{
    Debug.Assert(v1 == Vector3.UnitY);
    // changes v1 deterministically (no races required)
    ChangeV();
    Debug.Assert(v1 == Vector3.UnitX);
}

static void ChangeV()
{
    v = Vector3.UnitX;
}

in 지역 변수 및 매개 변수 캡처.in parameters and capturing of local variables.

람다/비동기 캡처 in 매개 변수는 out 및 매개 변수와 동일 하 게 동작 합니다 ref .For the purpose of lambda/async capturing in parameters behave the same as out and ref parameters.

  • in 매개 변수는 클로저로 캡처할 수 없습니다.in parameters cannot be captured in a closure
  • in 반복기 메서드에서는 매개 변수를 사용할 수 없습니다.in parameters are not allowed in iterator methods
  • in 비동기 메서드에서는 매개 변수를 사용할 수 없습니다.in parameters are not allowed in async methods

임시 변수.Temporary variables.

매개 변수 전달을 사용 하는 경우에는 in 임시 지역 변수를 간접적으로 사용 해야 할 수도 있습니다.Some uses of in parameter passing may require indirect use of a temporary local variable:

  • in 호출 사이트에서 사용 하는 경우 인수는 항상 직접 별칭으로 전달 됩니다 in .in arguments are always passed as direct aliases when call-site uses in. 임시는 이러한 경우에 사용 되지 않습니다.Temporary is never used in such case.
  • in 호출 사이트에서을 사용 하지 않는 경우 인수는 직접 별칭으로 지정할 필요가 없습니다 in .in arguments are not required to be direct aliases when call-site does not use in. 인수가 LValue가 아니면 임시를 사용할 수 있습니다.When argument is not an LValue, a temporary may be used.
  • in 매개 변수는 기본값을 가질 수 있습니다.in parameter may have default value. 호출 사이트에서 해당 인수를 생략 하면 임시를 통해 기본값이 전달 됩니다.When corresponding argument is omitted at the call site, the default value are passed via a temporary.
  • in 인수에는 id를 유지 하지 않는 암시적 변환이 포함 될 수 있습니다.in arguments may have implicit conversions, including those that do not preserve identity. 이러한 경우 임시가 사용 됩니다.A temporary is used in those cases.
  • 일반 구조체 호출의 수신자는 쓰기 가능한 LValues (기존 경우)가 될 수 없습니다.receivers of ordinary struct calls may not be writeable LValues (existing case!). 이러한 경우 임시가 사용 됩니다.A temporary is used in those cases.

인수 임시 개체의 수명은 호출 사이트의 가장 근접 한 범위와 일치 합니다.The life time of the argument temporaries matches the closest encompassing scope of the call-site.

임시 변수의 공식 수명은 참조로 반환 되는 변수의 이스케이프 분석과 관련 된 시나리오에서 의미가 매우 중요 합니다.The formal life time of temporary variables is semantically significant in scenarios involving escape analysis of variables returned by reference.

매개 변수의 메타 데이터 표현 in 입니다.Metadata representation of in parameters.

System.Runtime.CompilerServices.IsReadOnlyAttribute가 byref 매개 변수에 적용 되는 경우 매개 변수가 매개 변수 임을 의미 in 합니다.When System.Runtime.CompilerServices.IsReadOnlyAttribute is applied to a byref parameter, it means that the parameter is an in parameter.

또한 메서드가 abstract 또는 virtual 이면 이러한 매개 변수의 시그니처와 이러한 매개 변수만이 있어야 합니다 modreq[System.Runtime.InteropServices.InAttribute] .In addition, if the method is abstract or virtual, then the signature of such parameters (and only such parameters) must have modreq[System.Runtime.InteropServices.InAttribute].

동기: 메서드를 재정의/구현 하 여 매개 변수를 구현 하는 경우에 수행 됩니다 in .Motivation: this is done to ensure that in a case of method overriding/implementing the in parameters match.

대리자의 메서드에 동일한 요구 사항이 적용 Invoke 됩니다.Same requirements apply to Invoke methods in delegates.

동기: readonly 대리자를 만들거나 할당할 때 기존 컴파일러가 단순히 무시 하지 않도록 하기 위한 것입니다.Motivation: this is to ensure that existing compilers cannot simply ignore readonly when creating or assigning delegates.

Readonly 참조로 반환 됩니다.Returning by readonly reference.

동기Motivation

이 하위 기능에 대 한 동기는 in 매개 변수를 복사 하지 않고 반환 하는 쪽의 이유로 거의 대칭 됩니다.The motivation for this sub-feature is roughly symmetrical to the reasons for the in parameters - avoiding copying, but on the returning side. 이 기능 이전에 메서드 또는 인덱서에는 두 가지 옵션이 있습니다. 1) 참조로 반환 되 고 가능한 변경이에 노출 되거나 2) 값으로 반환 되어 복사 됩니다.Prior to this feature, a method or an indexer had two options: 1) return by reference and be exposed to possible mutations or 2) return by value which results in copying.

솔루션 ( ref readonly 반환)Solution (ref readonly returns)

이 기능을 사용 하면 멤버가 변경이에 노출 하지 않고 변수를 참조로 반환할 수 있습니다.The feature allows a member to return variables by reference without exposing them to mutations.

ref readonly반환 멤버 선언Declaring ref readonly returning members

반환 시그니처에 한정자를 조합 하 여 ref readonly 멤버가 읽기 전용 참조를 반환 함을 나타내는 데 사용 됩니다.A combination of modifiers ref readonly on the return signature is used to to indicate that the member returns a readonly reference.

모든 용도의 멤버는 ref readonly readonly readonly 필드 및 매개 변수와 유사 하 게 변수로 처리 됩니다 in .For all purposes a ref readonly member is treated as a readonly variable - similar to readonly fields and in parameters.

구조체 형식이 있는 멤버의 필드 예 ref readonly 는 모두 변수로 재귀적으로 분류 됩니다 readonly .For example fields of ref readonly member which has a struct type are all recursively classified as readonly variables. -인수로는 전달할 수 in 있지만 또는 인수로는 전달할 수 없습니다 ref out .- It is permitted to pass them as in arguments, but not as ref or out arguments.

ref readonly Guid Method1()
{
}

Method2(in Method1()); // valid. Can pass as `in` argument.

Method3(ref Method1()); // not valid. Cannot pass as `ref` argument
  • ref readonly 반환은 반환 되는 같은 위치에서 허용 됩니다 ref .ref readonly returns are allowed in the same places were ref returns are allowed. 여기에는 인덱서, 대리자, 람다, 로컬 함수가 포함 됩니다.This includes indexers, delegates, lambdas, local functions.

  • /차이를 오버 로드 하는 것은 허용 되지 않습니다 ref / ref readonly .It is not permitted to overload on ref/ref readonly / differences.

  • 일반 byval에서 오버 로드 하 고 차이를 ref readonly 반환할 수 있습니다.It is permitted to overload on ordinary byval and ref readonly return differences.

  • OHI의 목적 (오버 로드, 숨기기, 구현) ref readonly 은 비슷하지만와는 다릅니다 ref .For the purpose of OHI (Overloading, Hiding, Implementing), ref readonly is similar but distinct from ref. 예를 들어, 하나를 재정의 하는 메서드는 자체 여야 하 ref readonly ref readonly 고 id로 변환할 수 있는 형식 이어야 합니다.For example the a method that overrides ref readonly one, must itself be ref readonly and have identity-convertible type.

  • 대리자/람다/메서드 그룹 변환의 목적에 맞게 ref readonly 는 비슷하지만와는 다릅니다 ref .For the purpose of delegate/lambda/method group conversions, ref readonly is similar but distinct from ref. 람다 및 적용 가능한 메서드 그룹 변환 후보는 id로 변환할 수 있는 ref readonly 형식의 반환과 일치 하는 대상 대리자의 반환을 일치 해야 ref readonly 합니다.Lambdas and applicable method group conversion candidates have to match ref readonly return of the target delegate with ref readonly return of the type that is identity-convertible.

  • 제네릭 가변성의 목적에 맞게 ref readonly 는 변형이 아닌를 반환 합니다.For the purpose of generic variance, ref readonly returns are nonvariant.

참고: ref readonly 참조 또는 기본 형식이 있는 반환에 대 한 경고는 없습니다.NOTE: There are no warnings on ref readonly returns that have reference or primitives types. 일반적으로는 무의미 할 수 있지만 일부 경우에는 사용자가 기본 형식을로 전달 해야 합니다 in .It may be pointless in general, but in some cases user must/want to pass primitives as in. 예제- ref readonly T Method() 가로 대체 되는 경우와 같은 제네릭 메서드를 재정의 T int 합니다.Examples - overriding a generic method like ref readonly T Method() when T was substituted to be int.

에서의 비효율적인 사용에 대해 경고 하는 분석기를 사용 하는 것이 가능 ref readonly 하지만, 이러한 분석에 대 한 규칙이 너무 유사 하 여 언어 사양의 일부가 될 수 없습니다.It is conceivable to have an analyzer that warns in cases of inefficient use of ref readonly returns, but the rules for such analysis would be too fuzzy to be a part of a language specification.

멤버에서 반환 ref readonlyReturning from ref readonly members

메서드 본문 내에서 구문은 일반 ref 반환의 구문과 같습니다.Inside the method body the syntax is the same as with regular ref returns. readonly 포함 하는 메서드에서 유추 됩니다.The readonly will be inferred from the containing method.

동기는 불필요 하 return ref readonly <expression> readonly 게 길고 항상 오류가 발생 하는 부분에 대 한 불일치만 허용 한다는 것입니다.The motivation is that return ref readonly <expression> is unnecessary long and only allows for mismatches on the readonly part that would always result in errors. ref그러나는 엄격한 앨리어싱 및 값을 통해 무언가를 전달 하는 다른 시나리오와의 일관성을 유지 하는 데 필요 합니다.The ref is, however, required for consistency with other scenarios where something is passed via strict aliasing vs. by value.

매개 변수를 사용 하는 경우와 달리는 in ref readonly 로컬 복사본을 통해 반환 되지 않습니다.Unlike the case with in parameters, ref readonly returns never return via a local copy. 이러한 연습을 반환 하는 즉시 복사본이 존재 하지 않는 것으로 간주 하면 무의미 하 고 위험할 수 있습니다.Considering that the copy would cease to exist immediately upon returning such practice would be pointless and dangerous. 따라서 ref readonly 는 항상 직접 참조를 반환 합니다.Therefore ref readonly returns are always direct references.

예제:Example:

struct ImmutableArray<T>
{
    private readonly T[] array;

    public ref readonly T ItemRef(int i)
    {
        // returning a readonly reference to an array element
        return ref this.array[i];
    }
}

  • 의 인수는 return ref LValue 여야 합니다 (기존 규칙).An argument of return ref must be an LValue (existing rule)
  • 의 인수는 return ref "안전 하 게 반환" (기존 규칙) 이어야 합니다.An argument of return ref must be "safe to return" (existing rule)
  • 멤버에서는 ref readonly 의 인수를 return ref 쓰기 가능으로 설정할 필요가 없습니다 .In a ref readonly member an argument of return ref is not required to be writeable . 예를 들어 이러한 멤버는 참조로 읽기 전용 필드 또는 해당 매개 변수 중 하나를 반환할 수 있습니다 in .For example such member can ref-return a readonly field or one of its in parameters.

규칙을 반환 하는 것이 안전 합니다.Safe to Return rules.

참조에 대 한 일반적인 반환 규칙은 읽기 전용 참조에도 적용 됩니다.Normal safe to return rules for references will apply to readonly references as well.

ref readonly 일반 ref 로컬/매개 변수/반환에서 가져올 수 있지만 다른 방식으로는 가져올 수 없습니다.Note that a ref readonly can be obtained from a regular ref local/parameter/return, but not the other way around. 그렇지 않으면 ref readonly 일반적인 반환의 경우와 동일한 방식으로 반환의 안전성이 유추 됩니다 ref .Otherwise the safety of ref readonly returns is inferred the same way as for regular ref returns.

Rvalue를 in 매개 변수로 전달 하 고 하나 이상의 규칙이 필요한 것으로 반환 될 수 있습니다 ref readonly . rvalue는 참조로 안전 하 게 반환 되지 않습니다.Considering that RValues can be passed as in parameter and returned as ref readonly we need one more rule - RValues are not safe-to-return by reference.

RValue가 in 복사를 통해 매개 변수로 전달 된 다음 형식으로 다시 반환 되는 경우를 고려해 야 합니다 ref readonly .Consider the situation when an RValue is passed to an in parameter via a copy and then returned back in a form of a ref readonly. 호출자의 컨텍스트에서 이러한 호출의 결과는 로컬 데이터에 대 한 참조 이며 반환 하는 것은 안전 하지 않습니다.In the context of the caller the result of such invocation is a reference to local data and as such is unsafe to return. Rvalue가 반환 하기에 안전 하지 않은 경우 기존 규칙은 #6 이 사례를 이미 처리 합니다.Once RValues are not safe to return, the existing rule #6 already handles this case.

예제:Example:

ref readonly Vector3 Test1()
{
    // can pass an RValue as "in" (via a temp copy)
    // but the result is not safe to return
    // because the RValue argument was not safe to return by reference
    return ref Test2(default(Vector3));
}

ref readonly Vector3 Test2(in Vector3 r)
{
    // this is ok, r is returnable
    return ref r;
}

업데이트 된 safe to return 규칙:Updated safe to return rules:

  1. 힙의 refs를 변수로 반환 하는 것이 안전 합니다.refs to variables on the heap are safe to return
  2. ref/in 매개 변수는 반환 in 해도 안전 합니다. 매개 변수는 기본적으로 readonly로만 반환 될 수 있습니다.ref/in parameters are safe to return in parameters naturally can only be returned as readonly.
  3. out 매개 변수는 반환 해도 안전 하지만 이미 있는 경우와 마찬가지로 명확 하 게 할당 해야 합니다.out parameters are safe to return (but must be definitely assigned, as is already the case today)
  4. 수신자가 안전 하 게 반환할 수 있는 경우 인스턴스 구조체 필드를 반환 해도 안전 합니다.instance struct fields are safe to return as long as the receiver is safe to return
  5. ' t r u e '는 구조체 멤버에서 반환 하기에 안전 하지 않습니다.'this' is not safe to return from struct members
  6. 다른 메서드에서 반환 된 ref는 정식 매개 변수로 해당 메서드에 전달 된 모든 refs/초과가 반환 해도 안전 합니다. 특히 수신기가 구조체, 클래스 또는 제네릭 형식 매개 변수로 형식화 되어 있는지 여부에 관계 없이 수신자가 반환 하기에 안전 하지 않은 경우에는 의미가 없습니다.a ref, returned from another method is safe to return if all refs/outs passed to that method as formal parameters were safe to return. Specifically it is irrelevant if receiver is safe to return, regardless whether receiver is a struct, class or typed as a generic type parameter.
  7. Rvalue는 참조로 반환 하기에 안전 하지 않습니다. 특히 rvalue는 매개 변수로 전달 해도 안전 합니다.RValues are not safe to return by reference. Specifically RValues are safe to pass as in parameters.

참고: ref like 형식 및 ref-재배정이 관련 된 경우에 발생 하는 반환의 안전 성과 관련 된 추가 규칙이 있습니다.NOTE: There are additional rules regarding safety of returns that come into play when ref-like types and ref-reassignments are involved. 규칙은 및 멤버에 동일 하 게 적용 ref ref readonly 되므로 여기서 설명 하지 않습니다.The rules equally apply to ref and ref readonly members and therefore are not mentioned here.

별칭 동작입니다.Aliasing behavior.

ref readonly 멤버는 일반 멤버와 동일한 앨리어싱 동작을 제공 ref 합니다 (읽기 전용은 제외).ref readonly members provide the same aliasing behavior as ordinary ref members (except for being readonly). 따라서 람다, 비동기, 반복기, stack 분산 등에서 캡처할 목적으로 동일한 제한이 적용 됩니다.Therefore for the purpose of capturing in lambdas, async, iterators, stack spilling etc... the same restrictions apply. 핵.- I.E. 실제 참조를 캡처할 수 없기 때문에 멤버 평가의 주는 특성으로 인해 이러한 시나리오가 허용 되지 않습니다.due to inability to capture the actual references and due to side-effecting nature of member evaluation such scenarios are disallowed.

이 메서드는 반환 될 때 복사를 수행 하는 데 필요 하며 ref readonly , 일반 this 쓰기 가능 참조로 사용 되는 일반 구조체 메서드의 수신자 인 경우에 필요 합니다.It is permitted and required to make a copy when ref readonly return is a receiver of regular struct methods, which take this as an ordinary writeable reference. 지금까지 이러한 호출이 readonly 변수에 적용 되는 모든 경우에 로컬 복사본이 생성 됩니다.Historically in all cases where such invocations are applied to readonly variable a local copy is made.

메타 데이터 표현.Metadata representation.

System.Runtime.CompilerServices.IsReadOnlyAttribute가 byref 반환 메서드의 반환에 적용 되는 경우이 메서드는 읽기 전용 참조를 반환 함을 의미 합니다.When System.Runtime.CompilerServices.IsReadOnlyAttribute is applied to the return of a byref returning method, it means that the method returns a readonly reference.

또한 이러한 메서드의 결과 시그니처와 해당 메서드만 포함 해야 합니다 modreq[System.Runtime.CompilerServices.IsReadOnlyAttribute] .In addition, the result signature of such methods (and only those methods) must have modreq[System.Runtime.CompilerServices.IsReadOnlyAttribute].

동기: 반환이 있는 메서드를 호출 하는 경우 기존 컴파일러에서 무시 하지 않도록 하기 위한 것입니다. readonly ref readonlyMotivation: this is to ensure that existing compilers cannot simply ignore readonly when invoking methods with ref readonly returns

읽기 전용 구조체Readonly structs

간단히 말해서 this 생성자를 제외 하 고 구조체의 모든 인스턴스 멤버 매개 변수를 매개 변수로 사용 하는 기능입니다 in .In short - a feature that makes this parameter of all instance members of a struct, except for constructors, an in parameter.

동기Motivation

컴파일러는 구조체 인스턴스의 모든 메서드 호출에서 인스턴스를 수정할 수 있다고 가정 합니다.Compiler must assume that any method call on a struct instance may modify the instance. 실제로 쓰기 가능한 참조는 메서드에 매개 변수로 전달 되 this 고이 동작을 완전히 활성화 합니다.Indeed a writeable reference is passed to the method as this parameter and fully enables this behavior. 변수에 대해 이러한 호출을 허용 하기 위해 readonly 호출이 임시 복사본에 적용 됩니다.To allow such invocations on readonly variables, the invocations are applied to temp copies. Unintuitive 수 있으며 때로는 사용자가 성능상의 이유로 중단 하 게 할 수 있습니다 readonly .That could be unintuitive and sometimes forces people to abandon readonly for performance reasons.
예: https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/Example: https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/

매개 변수에 대 한 지원을 추가 in 하 고 ref readonly 방어 중인 복사의 문제를 반환 하 고 나면 readonly 변수가 더 보편화 되기 때문에 더 나빠질 수 있습니다.After adding support for in parameters and ref readonly returns the problem of defensive copying will get worse since readonly variables will become more common.

해결 방법Solution

readonly this in 생성자를 제외 하 고 모든 구조체 인스턴스 메서드에서 매개 변수로 처리 되도록 하는 구조체 선언에 한정자를 허용 합니다.Allow readonly modifier on struct declarations which would result in this being treated as in parameter on all struct instance methods except for constructors.

static void Test(in Vector3 v1)
{
    // no need to make a copy of v1 since Vector3 is a readonly struct
    System.Console.WriteLine(v1.ToString());
}

readonly struct Vector3
{
    . . .

    public override string ToString()
    {
        // not OK!!  `this` is an `in` parameter
        foo(ref this.X);

        // OK
        return $"X: {X}, Y: {Y}, Z: {Z}";
    }
}

Readonly 구조체의 멤버에 대 한 제한 사항Restrictions on members of readonly struct

  • 읽기 전용 구조체의 인스턴스 필드는 읽기 전용 이어야 합니다.Instance fields of a readonly struct must be readonly.
    동기: 외부에만 쓸 수 있으며 멤버를 통해서는 쓸 수 없습니다.Motivation: can only be written to externally, but not through members.
  • Readonly 구조체의 인스턴스 자동 속성은 get 전용 이어야 합니다.Instance autoproperties of a readonly struct must be get-only.
    동기: 인스턴스 필드에 대 한 제한의 결과입니다.Motivation: consequence of restriction on instance fields.
  • Readonly 구조체는 필드와 유사한 이벤트를 선언할 수 없습니다.Readonly struct may not declare field-like events.
    동기: 인스턴스 필드에 대 한 제한의 결과입니다.Motivation: consequence of restriction on instance fields.

메타 데이터 표현.Metadata representation.

System.Runtime.CompilerServices.IsReadOnlyAttribute가 값 형식에 적용 되는 경우 형식은 readonly struct 입니다.When System.Runtime.CompilerServices.IsReadOnlyAttribute is applied to a value type, it means that the type is a readonly struct.

특히 다음 사항에 주의하십시오.In particular:

  • 형식의 id는 IsReadOnlyAttribute 중요 하지 않습니다.The identity of the IsReadOnlyAttribute type is unimportant. 실제로 필요한 경우 포함 하는 어셈블리에 컴파일러에 포함 될 수 있습니다.In fact it can be embedded by the compiler in the containing assembly if needed.

ref/in 확장 메서드ref/in extension methods

실제로 기존 제안 ( https://github.com/dotnet/roslyn/issues/165) 및 해당 프로토타입 PR ()이 있습니다 https://github.com/dotnet/roslyn/pull/15650) .There is actually an existing proposal (https://github.com/dotnet/roslyn/issues/165) and corresponding prototype PR (https://github.com/dotnet/roslyn/pull/15650). 이 아이디어가 완전히 새로운 것은 아니라는 것을 인정 하려고 합니다.I just want to acknowledge that this idea is not entirely new. 그러나 ref readonly 원활는 이러한 메서드에 대 한 가장 contentious 문제 (RValue 수신자로 수행할 작업)를 제거 하기 때문에 여기서는 관련이 있습니다.It is, however, relevant here since ref readonly elegantly removes the most contentious issue about such methods - what to do with RValue receivers.

일반적으로 this 형식은 구조체 형식으로 알려진 경우에만 매개 변수를 참조로 사용할 수 있습니다.The general idea is allowing extension methods to take the this parameter by reference, as long as the type is known to be a struct type.

public static void Extension(ref this Guid self)
{
    // do something
}

이러한 확장 메서드를 작성 하는 이유는 주로 다음과 같습니다.The reasons for writing such extension methods are primarily:

  1. 수신기가 클 때 복사 방지Avoid copying when receiver is a large struct
  2. 구조체에서 확장 메서드를 변경할 수 있습니다.Allow mutating extension methods on structs

클래스에서이를 허용 하지 않으려는 이유The reasons why we do not want to allow this on classes

  1. 매우 제한적입니다.It would be of very limited purpose.
  2. 호출 후에는 메서드 호출이 수신기를 사용 하지 않도록 설정할 수 없는 긴 고정 고정을 중단 null null 합니다.It would break long standing invariant that a method call cannot turn non-null receiver to become null after invocation.

실제로는 null null 또는에 의해 명시적으로 할당 되거나 전달 되지 않는 한 현재 비 변수가 될 수 없습니다 ref out .In fact, currently a non-null variable cannot become null unless explicitly assigned or passed by ref or out. 이는 가독성 또는 다른 형태의 "여기에서 null이 될 수 있습니다." 분석을 크게 지원 합니다.That greatly aids readability or other forms of "can this be a null here" analysis. 3. Null 조건부 액세스의 "한 번 평가" 의미 체계를 사용 하 여 조정 하는 것은 어렵습니다.It would be hard to reconcile with "evaluate once" semantics of null-conditional accesses. 예: obj.stringField?.RefExtension(...) -의 복사본을 캡처하여 stringField null 확인을 의미 하는 것으로 지정 하 고, 그 다음에는 this refextension 내에 할당이 필드에 다시 반영 되지 않습니다.Example: obj.stringField?.RefExtension(...) - need to capture a copy of stringField to make the null check meaningful, but then assignments to this inside RefExtension would not be reflected back to the field.

참조로 첫 번째 인수를 사용 하는 구조체 에 대 한 확장 메서드를 선언 하는 기능은 오래 된 요청입니다.An ability to declare extension methods on structs that take the first argument by reference was a long-standing request. 차단 고려 사항 중 하나는 "수신자가 LValue가 아닌 경우 어떻게 되나요?" 였습니다.One of the blocking consideration was "what happens if receiver is not an LValue?".

  • 모든 확장 메서드가 정적 메서드로 호출 될 수도 있다는 것을 참조 합니다 (때로는 모호성을 해결 하는 유일한 방법).There is a precedent that any extension method could also be called as a static method (sometimes it is the only way to resolve ambiguity). RValue 수신기를 허용 하지 않는 것이 좋습니다.It would dictate that RValue receivers should be disallowed.
  • 반면에 구조체 인스턴스 메서드를 사용할 때 유사한 상황에서 복사본에 대 한 호출을 수행 하는 방법을 사용할 수 있습니다.On the other hand there is a practice of making invocation on a copy in similar situations when struct instance methods are involved.

"암시적 복사"가 존재 하는 이유는 대부분의 구조체 메서드가 실제로 구조체를 수정 하지는 않지만이를 나타낼 수 없기 때문입니다.The reason why the "implicit copying" exists is because the majority of struct methods do not actually modify the struct while not being able to indicate that. 따라서 가장 실용적인 솔루션은 단순히 복사본에 대 한 호출을 수행 하는 것 이지만이 방법은 저하 성능 및 버그 발생에 대해 알려져 있습니다.Therefore the most practical solution was to just make the invocation on a copy, but this practice is known for harming performance and causing bugs.

이제는 in 매개 변수를 사용할 수 있으므로 확장이 의도에 신호를 보낼 수 있습니다.Now, with availability of in parameters, it is possible for an extension to signal the intent. 따라서 난제은 ref 쓰기 가능한 수신기를 사용 하 여 확장을 호출 하도록 하 고, in 필요한 경우 확장에서 암시적 복사를 허용 하 여 해결할 수 있습니다.Therefore the conundrum can be resolved by requiring ref extensions to be called with writeable receivers while in extensions permit implicit copying if necessary.

// this can be called on either RValue or an LValue
public static void Reader(in this Guid self)
{
    // do something nonmutating.
    WriteLine(self == default(Guid));
}

// this can be called only on an LValue
public static void Mutator(ref this Guid self)
{
    // can mutate self
    self = new Guid();
}

in 확장명 및 제네릭.in extensions and generics.

ref확장 메서드의 목적은 수신기를 직접 변경 하거나 변경 멤버를 호출 하는 것입니다.The purpose of ref extension methods is to mutate the receiver directly or by invoking mutating members. 따라서 ref this T 확장이 구조체로 제한 된 경우에만 확장이 허용 됩니다 T .Therefore ref this T extensions are allowed as long as T is constrained to be a struct.

반면에, in 암시적 복사를 줄이기 위해 특히 확장 방법이 있습니다.On the other hand in extension methods exist specifically to reduce implicit copying. 그러나 in T 매개 변수 사용은 인터페이스 멤버를 통해 수행 해야 합니다.However any use of an in T parameter will have to be done through an interface member. 모든 인터페이스 멤버는 변경 되는 것으로 간주 되므로 이러한 사용에는 복사본이 필요 합니다.Since all interface members are considered mutating, any such use would require a copy. -복사를 줄이는 대신 효과는 반대입니다.- Instead of reducing copying, the effect would be the opposite. 따라서 in this T T 이 제약 조건에 관계 없이 제네릭 형식 매개 변수인 경우에는 허용 되지 않습니다.Therefore in this T is not allowed when T is a generic type parameter regardless of constraints.

올바른 종류의 확장 메서드 (요약):Valid kinds of extension methods (recap):

this이제 확장 메서드에서 다음 형식의 선언이 허용 됩니다.The following forms of this declaration in an extension method are now allowed:

  1. this T arg -일반 byval 확장명입니다.this T arg - regular byval extension. (기존 사례)(existing case)
  • T는 참조 형식 또는 형식 매개 변수를 비롯 한 모든 형식일 수 있습니다.T can be any type, including reference types or type parameters. 인스턴스는 호출 후의 동일한 변수가 됩니다.Instance will be the same variable after the call. 이 인수 변환 종류를 암시적으로 변환할 수 있습니다.Allows implicit conversions of this-argument-conversion kind. Rvalue에 대해 호출할 수 있습니다.Can be called on RValues.

  • in this T self - in 프로그램이.in this T self - in extension. T는 실제 구조체 형식 이어야 합니다.T must be an actual struct type. 인스턴스는 호출 후의 동일한 변수가 됩니다.Instance will be the same variable after the call. 이 인수 변환 종류를 암시적으로 변환할 수 있습니다.Allows implicit conversions of this-argument-conversion kind. Rvalue에 대해 호출할 수 있습니다 (필요한 경우 temp에서 호출 될 수 있음).Can be called on RValues (may be invoked on a temp if needed).

  • ref this T self - ref 프로그램이.ref this T self - ref extension. T는 구조체로 제한 되는 구조체 형식 또는 제네릭 형식 매개 변수 여야 합니다.T must be a struct type or a generic type parameter constrained to be a struct. 호출을 통해 인스턴스를 쓸 수 있습니다.Instance may be written to by the invocation. Id 변환만 허용 합니다.Allows only identity conversions. 쓰기 가능한 LValue에서 호출 해야 합니다.Must be called on writeable LValue. (temp를 통해 호출 되지 않습니다.)(never invoked via a temp).

Readonly 참조 로컬입니다.Readonly ref locals.

부여.Motivation.

멤버가 도입 된 후에는 ref readonly 적절 한 종류의 로컬에서 쌍으로 연결 되어야 한다는 점에서 사용이 명확 했습니다.Once ref readonly members were introduced, it was clear from the use that they need to be paired with appropriate kind of local. 멤버를 평가 하면 부작용이 발생 하거나 결과가 나타날 수 있습니다. 따라서 결과가 두 번 이상 사용 되어야 하는 경우에는 저장 해야 합니다.Evaluation of a member may produce or observe side effects, therefore if the result must be used more than once, it needs to be stored. 일반적인 ref 지역에는 참조를 할당할 수 없기 때문에이 작업은 유용 하지 않습니다 readonly .Ordinary ref locals do not help here since they cannot be assigned a readonly reference.

해결할.Solution.

지역 선언을 허용 ref readonly 합니다.Allow declaring ref readonly locals. 이는 ref 쓸 수 없는 새 종류의 지역입니다.This is a new kind of ref locals that is not writeable. 결과적으로 지역 변수는 ref readonly 쓰기에 이러한 변수를 노출 하지 않고 readonly 변수에 대 한 참조를 수락할 수 있습니다.As a result ref readonly locals can accept references to readonly variables without exposing these variables to writes.

지역 선언 및 사용 ref readonlyDeclaring and using ref readonly locals.

이러한 로컬 구문은 ref readonly 선언 사이트에서 한정자를 사용 합니다 (특정 순서로).The syntax of such locals uses ref readonly modifiers at declaration site (in that specific order). 일반적인 지역에 마찬가지로 ref ref readonly 지역 변수는 선언 시에 ref로 초기화 되어야 합니다.Similarly to ordinary ref locals, ref readonly locals must be ref-initialized at declaration. 일반 ref 지역과 달리 ref readonly 지역에서 readonly in 매개 변수, readonly 필드, 메서드 등의 lvalues를 참조할 수 있습니다 ref readonly .Unlike regular ref locals, ref readonly locals can refer to readonly LValues like in parameters, readonly fields, ref readonly methods.

모든 용도의 ref readonly 로컬은 변수로 처리 됩니다 readonly .For all purposes a ref readonly local is treated as a readonly variable. 사용에 대 한 대부분의 제한은 readonly 필드 또는 매개 변수를 사용 하는 것과 같습니다 in .Most of the restrictions on the use are the same as with readonly fields or in parameters.

in구조체 형식이 있는 매개 변수의 필드 예는 모두 변수로 재귀적으로 분류 됩니다 readonly .For example fields of an in parameter which has a struct type are all recursively classified as readonly variables .

static readonly ref Vector3 M1() => . . .

static readonly ref Vector3 M1_Trace()
{
    // OK
    ref readonly var r1 = ref M1();

    // Not valid. Need an LValue
    ref readonly Vector3 r2 = ref default(Vector3);

    // Not valid. r1 is readonly.
    Mutate(ref r1);

    // OK.
    Print(in r1);

    // OK.
    return ref r1;
}

지역 사용에 대 한 제한 ref readonlyRestrictions on use of ref readonly locals

이러한 특성을 제외 하 고 readonly ref readonly 로컬은 일반 ref 지역 처럼 동작 하며, 정확히 동일한 제한이 적용 됩니다.Except for their readonly nature, ref readonly locals behave like ordinary ref locals and are subject to exactly same restrictions.
클로저의 캡처와 관련 된 예를 들어, 메서드에서 선언 async 또는 분석은 safe-to-return 지역에 동일 하 게 적용 됩니다 ref readonly .For example restrictions related to capturing in closures, declaring in async methods or the safe-to-return analysis equally applies to ref readonly locals.

삼항 ref 식.Ternary ref expressions. (즉, "조건부 LValues")(aka "Conditional LValues")

동기Motivation

ref및 지역을 사용 ref readonly 하면 조건에 따라 하나 또는 다른 대상 변수를 사용 하 여 이러한 로컬을 ref로 초기화할 필요가 없습니다.Use of ref and ref readonly locals exposed a need to ref-initialize such locals with one or another target variable based on a condition.

일반적인 해결 방법은 다음과 같은 메서드를 도입 하는 것입니다.A typical workaround is to introduce a method like:

ref T Choice(bool condition, ref T consequence, ref T alternative)
{
    if (condition)
    {
         return ref consequence;
    }
    else
    {
         return ref alternative;
    }
}

Choice 모든 인수를 호출 사이트에서 평가 해야 하 고 unintuitive 동작 및 버그로 전달 되기 때문에는 삼항을 정확 하 게 대체 하지는 않습니다.Note that Choice is not an exact replacement of a ternary since all arguments must be evaluated at the call site, which was leading to unintuitive behavior and bugs.

다음은 예상 대로 작동 하지 않습니다.The following will not work as expected:

    // will crash with NRE because 'arr[0]' will be executed unconditionally
    ref var r = ref Choice(arr != null, ref arr[0], ref otherArr[0]);

해결 방법Solution

조건에 따라 LValue 인수 중 하나에 대 한 참조로 계산 되는 특수 한 종류의 조건식을 허용 합니다.Allow special kind of conditional expression that evaluates to a reference to one of LValue argument based on a condition.

ref삼항 식 사용.Using ref ternary expression.

조건식의 버전에 대 한 구문은 ref 입니다. <condition> ? ref <consequence> : ref <alternative>;The syntax for the ref flavor of a conditional expression is <condition> ? ref <consequence> : ref <alternative>;

일반적인 조건식과 마찬가지로 <consequence> 또는 <alternative> 부울 조건 식의 결과에 따라 계산 됩니다.Just like with the ordinary conditional expression only <consequence> or <alternative> is evaluated depending on result of the boolean condition expression.

일반적인 조건식과 달리 ref 조건식은 다음과 같습니다.Unlike ordinary conditional expression, ref conditional expression:

  • 및는 <consequence> <alternative> lvalues 여야 합니다.requires that <consequence> and <alternative> are LValues.
  • ref 조건식은 LValue 이며ref conditional expression itself is an LValue and
  • ref``<consequence><alternative> 가 모두 쓰기 가능한 lvalues 인 경우 조건식은 쓰기 가능 합니다.ref conditional expression is writeable if both <consequence> and <alternative> are writeable LValues

예제:Examples:
ref 삼항은 LValue 이며,이를 전달/할당/참조로 반환할 수 있습니다.ref ternary is an LValue and as such it can be passed/assigned/returned by reference;

     // pass by reference
     foo(ref (arr != null ? ref arr[0]: ref otherArr[0]));

     // return by reference
     return ref (arr != null ? ref arr[0]: ref otherArr[0]);

LValue 인 경우에도 할당할 수 있습니다.Being an LValue, it can also be assigned to.

     // assign to
     (arr != null ? ref arr[0]: ref otherArr[0]) = 1;

     // error. readOnlyField is readonly and thus conditional expression is readonly
     (arr != null ? ref arr[0]: ref obj.readOnlyField) = 1;

메서드 호출의 수신자로 사용 하 고 필요한 경우 복사를 건너뛸 수 있습니다.Can be used as a receiver of a method call and skip copying if necessary.

     // no copies
     (arr != null ? ref arr[0]: ref otherArr[0]).StructMethod();

     // invoked on a copy.
     // The receiver is `readonly` because readOnlyField is readonly.
     (arr != null ? ref arr[0]: ref obj.readOnlyField).StructMethod();

     // no copies. `ReadonlyStructMethod` is a method on a `readonly` struct
     // and can be invoked directly on a readonly receiver
     (arr != null ? ref arr[0]: ref obj.readOnlyField).ReadonlyStructMethod();

ref 삼항은 일반 (참조 아님) 컨텍스트에서만 사용할 수 있습니다.ref ternary can be used in a regular (not ref) context as well.

     // only an example
     // a regular ternary could work here just the same
     int x = (arr != null ? ref arr[0]: ref otherArr[0]);

단점Drawbacks

참조 및 읽기 전용 참조의 향상 된 지원에 대 한 두 가지 주요 인수를 볼 수 있습니다.I can see two major arguments against enhanced support for references and readonly references:

  1. 여기에서 해결 된 문제는 매우 오래 된 것입니다.The problems that are solved here are very old. 특히 기존 코드를 지원 하지 않기 때문에 갑자기 해결 하는 이유는 무엇 인가요?Why suddenly solve them now, especially since it would not help existing code?

새 도메인에서 c # 및 .Net을 사용 하는 경우 일부 문제가 더욱 두드러집니다.As we find C# and .Net used in new domains, some problems become more prominent.
계산 오버 헤드에 대 한 평균 보다 더 중요 한 환경의 예로 다음을 나열할 수 있습니다.As examples of environments that are more critical than average about computation overheads, I can list

  • 계산 비용이 청구 되 고 응답성이 경쟁 우위에 있는 클라우드/데이터 센터 시나리오입니다.cloud/datacenter scenarios where computation is billed for and responsiveness is a competitive advantage.
  • 대기 시간에 대 한 소프트 실시간 요구 사항이 포함 된 게임/VR/ARGames/VR/AR with soft-realtime requirements on latencies

이 기능은 몇 가지 일반적인 시나리오에서 오버 헤드를 줄일 수 있는 반면, 형식 안전 등의 기존 강점은 희생 하지 않습니다.This feature does not sacrifice any of the existing strengths such as type-safety, while allowing to lower overheads in some common scenarios.

  1. 계약에 opts 때 호출 수신자가 규칙에 따라 실행 되도록 보장할 수 readonly 있나요?Can we reasonably guarantee that the callee will play by the rules when it opts into readonly contracts?

을 사용 하는 경우에도 유사한 신뢰가 있습니다 out .We have similar trust when using out. 의 잘못 된 구현 out 으로 인해 지정 되지 않은 동작이 발생할 수 있지만 실제로는 거의 발생 하지 않습니다.Incorrect implementation of out can cause unspecified behavior, but in reality it rarely happens.

에 친숙 한 정식 확인 규칙을 설정 하면 ref readonly 신뢰 문제가 추가로 완화 됩니다.Making the formal verification rules familiar with ref readonly would further mitigate the trust issue.

대안Alternatives

경쟁 하는 주요 디자인은 사실상 "아무 작업도 수행 하지 않음"입니다.The main competing design is really "do nothing".

확인 되지 않은 질문Unresolved questions

디자인 회의Design meetings

https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-02-22.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-03-01.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-08-28.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-09-25.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-09-27.mdhttps://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-02-22.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-03-01.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-08-28.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-09-25.md https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-09-27.md