針對類似 ref 類型的安全編譯時間強制執行Compile time enforcement of safety for ref-like types

簡介Introduction

處理類型(例如和)時,其他安全規則的主要 Span<T> 原因 ReadOnlySpan<T> 是這類類型必須局限于執行堆疊。The main reason for the additional safety rules when dealing with types like Span<T> and ReadOnlySpan<T> is that such types must be confined to the execution stack.

有兩個原因 Span<T> 和類似的類型必須是僅限堆疊的類型。There are two reasons why Span<T> and similar types must be a stack-only types.

  1. Span<T> 在語義上是包含參考和範圍的結構 (ref T data, int length)Span<T> is semantically a struct containing a reference and a range - (ref T data, int length). 無論實際的執行方式為何,寫入這類結構都不是不可部分完成的。Regardless of actual implementation, writes to such struct would not be atomic. 這類結構的並行「卸載」可能會導致 length 不相符的情況 data ,導致超出範圍存取和型別安全違規,最後可能會導致看似「安全」程式碼的 GC 堆積損毀。Concurrent "tearing" of such struct would lead to the possibility of length not matching the data, causing out-of-range accesses and type-safety violations, which ultimately could result in GC heap corruption in seemingly "safe" code.
  2. 某些實 Span<T> 作為實際上會在其中一個欄位包含 managed 指標。Some implementations of Span<T> literally contain a managed pointer in one of its fields. Managed 指標不支援做為堆積物件的欄位,以及管理將 managed 指標放在 GC 堆積上的程式碼,通常會在 JIT 時間損毀。Managed pointers are not supported as fields of heap objects and code that manages to put a managed pointer on the GC heap typically crashes at JIT time.

如果的實例 Span<T> 受限於只能存在於執行堆疊上,則上述所有問題都能獲得緩解。All the above problems would be alleviated if instances of Span<T> are constrained to exist only on the execution stack.

由於組合的緣故,因此會產生其他問題。An additional problem arises due to composition. 通常需要建立更複雜的資料類型,以內嵌 Span<T>ReadOnlySpan<T> 實例。It would be generally desirable to build more complex data types that would embed Span<T> and ReadOnlySpan<T> instances. 這類複合類型必須是結構,而且會共用的所有風險和需求 Span<T>Such composite types would have to be structs and would share all the hazards and requirements of Span<T>. 如此一來,此處所述的安全性規則應該會被視為適用于完整的 類似 ref 類型As a result the safety rules described here should be viewed as applicable to the whole range of ref-like types.

Draft 語言規格的目的是要確保類似 ref 類型的值只會出現在堆疊上。The draft language specification is intended to ensure that values of a ref-like type occur only on the stack.

ref-like原始碼中的一般化類型Generalized ref-like types in source code

ref-like 在原始程式碼中使用修飾詞明確地標示結構 refref-like structs are explicitly marked in the source code using ref modifier:

ref struct TwoSpans<T>
{
    // can have ref-like instance fields
    public Span<T> first;
    public Span<T> second;
} 

// error: arrays of ref-like types are not allowed. 
TwoSpans<T>[] arr = null;

將結構指定為類似參考將可讓結構具有類似 ref 的實例欄位,而且也會對類似 ref 的型別套用適用于結構的所有需求。Designating a struct as ref-like will allow the struct to have ref-like instance fields and will also make all the requirements of ref-like types applicable to the struct.

中繼資料標記法或類似參考的結構Metadata representation or ref-like structs

類似參考的結構會以 CompilerServices. IsRefLikeAttribute 屬性標記。Ref-like structs will be marked with System.Runtime.CompilerServices.IsRefLikeAttribute attribute.

屬性將會新增至一般基底程式庫(例如) mscorlibThe attribute will be added to common base libraries such as mscorlib. 如果屬性無法使用,編譯器就會產生類似于其他內嵌隨選屬性(例如)的內部 IsReadOnlyAttributeIn a case if the attribute is not available, compiler will generate an internal one similarly to other embedded-on-demand attributes such as IsReadOnlyAttribute.

將會採取額外的量值,以防止在編譯器中使用類似 ref 的結構,但不熟悉安全性規則 (這包括了這項功能在其中執行) 之前的 c # 編譯器。An additional measure will be taken to prevent the use of ref-like structs in compilers not familiar with the safety rules (this includes C# compilers prior to the one in which this feature is implemented).

如果沒有任何其他適合在舊編譯器中使用的替代方案,則 Obsolete 會將包含已知字串的屬性新增至所有類似參考的結構。Having no other good alternatives that work in old compilers without servicing, an Obsolete attribute with a known string will be added to all ref-like structs. 知道如何使用類似 ref 型別的編譯器將會忽略這個特定的形式 ObsoleteCompilers that know how to use ref-like types will ignore this particular form of Obsolete.

典型的中繼資料標記法:A typical metadata representation:

    [IsRefLike]
    [Obsolete("Types with embedded references are not supported in this version of your compiler.")]
    public struct TwoSpans<T>
    {
       // . . . .
    }

注意:這並不是將它設為在舊編譯器上使用類似 ref 類型的目標,會導致100% 失敗。NOTE: it is not the goal to make it so that any use of ref-like types on old compilers fails 100%. 這是很難達成的,並不是絕對必要的。That is hard to achieve and is not strictly necessary. 例如,一律會有方法可以 Obsolete 使用動態程式碼,或透過反映建立類似 ref 類型的陣列。For example there would always be a way to get around the Obsolete using dynamic code or, for example, creating an array of ref-like types through reflection.

尤其是,如果使用者想要實際將 ObsoleteDeprecated 屬性放在類似 ref 的型別上,除了未發出預先定義的類型之外,我們也不會有任何選擇,因為 Obsolete 屬性不能套用一次以上。In particular, if user wants to actually put an Obsolete or Deprecated attribute on a ref-like type, we will have no choice other than not emitting the predefined one since Obsolete attribute cannot be applied more than once..

範例:Examples:

SpanLikeType M1(ref SpanLikeType x, Span<byte> y)
{
    // this is all valid, unconcerned with stack-referring stuff
    var local = new SpanLikeType(y);
    x = local;
    return x;
}

void Test1(ref SpanLikeType param1, Span<byte> param2)
{
    Span<byte> stackReferring1 = stackalloc byte[10];
    var stackReferring2 = new SpanLikeType(stackReferring1);

    // this is allowed
    stackReferring2 = M1(ref stackReferring2, stackReferring1);

    // this is NOT allowed
    stackReferring2 = M1(ref param1, stackReferring1);

    // this is NOT allowed
    param1 = M1(ref stackReferring2, stackReferring1);

    // this is NOT allowed
    param2 = stackReferring1.Slice(10);

    // this is allowed
    param1 = new SpanLikeType(param2);

    // this is allowed
    stackReferring2 = param1;
}

ref SpanLikeType M2(ref SpanLikeType x)
{
    return ref x;
}

ref SpanLikeType Test2(ref SpanLikeType param1, Span<byte> param2)
{
    Span<byte> stackReferring1 = stackalloc byte[10];
    var stackReferring2 = new SpanLikeType(stackReferring1);

    ref var stackReferring3 = M2(ref stackReferring2);

    // this is allowed
    stackReferring3 = M1(ref stackReferring2, stackReferring1);

    // this is allowed
    M2(ref stackReferring3) = stackReferring2;

    // this is NOT allowed
    M1(ref param1) = stackReferring2;

    // this is NOT allowed
    param1 = stackReferring3;

    // this is NOT allowed
    return ref stackReferring3;

    // this is allowed
    return ref param1;
}


草稿語言規格Draft language specification

以下將描述一組類似 ref 類型的安全性規則 (ref struct s) ,以確保這些類型的值只會出現在堆疊上。Below we describe a set of safety rules for ref-like types (ref structs) to ensure that values of these types occur only on the stack. 如果無法以傳址方式傳遞區域變數,則可以使用不同、更簡單的安全性規則集。A different, simpler set of safety rules would be possible if locals cannot be passed by reference. 此規格也允許安全重新指派 ref 區域變數。This specification would also permit the safe reassignment of ref locals.

概觀Overview

在編譯時期,我們會將運算式所允許的運算式範圍(「安全轉型」)的概念與每個運算式建立關聯。We associate with each expression at compile-time the concept of what scope that expression is permitted to escape to, "safe-to-escape". 同樣地,對於每個左值而言,我們會維護參考範圍的概念,並將它的參考設為「ref-安全到 escape」。Similarly, for each lvalue we maintain a concept of what scope a reference to it is permitted to escape to, "ref-safe-to-escape". 如果是給定的左值運算式,這些運算式可能會不同。For a given lvalue expression, these may be different.

這些類似于 ref 區域變數功能的「可安全傳回」,但是更細微。These are analogous to the "safe to return" of the ref locals feature, but it is more fine-grained. 如果運算式的「安全轉型」只會記錄 (或不是) 它可能會將封入方法全部 escape,則安全到 escape 的記錄可能會被 escape,以 (它可能不會在) 以外被 escape 的範圍。Where the "safe-to-return" of an expression records only whether (or not) it may escape the enclosing method as a whole, the safe-to-escape records which scope it may escape to (which scope it may not escape beyond). 這會強制執行基本的安全機制,如下所示。The basic safety mechanism is enforced as follows. 從具有安全到 escape 範圍 S1 的運算式 E1 指派給 (左值) 運算式 E2 (具有安全對 escape 範圍 S2),如果 S2 的範圍超出 S1,則會產生錯誤。Given an assignment from an expression E1 with a safe-to-escape scope S1, to an (lvalue) expression E2 with safe-to-escape scope S2, it is an error if S2 is a wider scope than S1. 根據結構,S1 和 S2 這兩個範圍是在嵌套關聯性中,因為合法的運算式一律會從包含運算式的某些範圍中安全地傳回。By construction, the two scopes S1 and S2 are in a nesting relationship, because a legal expression is always safe-to-return from some scope enclosing the expression.

在這段時間內,為了進行分析,只支援方法的外部,以及方法的最上層範圍,以支援這兩個範圍。For the time being it is sufficient, for the purpose of the analysis, to support just two scopes - external to the method, and top-level scope of the method. 這是因為無法建立與內部範圍類似的 ref 值,且 ref 區域變數不支援重新指派。That is because ref-like values with inner scopes cannot be created and ref locals do not support re-assignment. 不過,這些規則可以支援兩個以上的範圍層級。The rules, however, can support more than two scope levels.

計算運算式之 安全回復 狀態的精確規則,以及管理運算式合法性的規則,請遵循。The precise rules for computing the safe-to-return status of an expression, and the rules governing the legality of expressions, follow.

ref-安全到 escaperef-safe-to-escape

Ref 安全對 escape是一個範圍,其中包含左值運算式,可安全地參考左值以進行換用。The ref-safe-to-escape is a scope, enclosing an lvalue expression, to which it is safe for a ref to the lvalue to escape to. 如果該範圍是整個方法,我們假設左值的 ref 可以 安全地 從方法傳回。If that scope is the entire method, we say that a ref to the lvalue is safe to return from the method.

安全到 escapesafe-to-escape

安全轉型是包含運算式的範圍,可讓您安全地將值換成。The safe-to-escape is a scope, enclosing an expression, to which it is safe for the value to escape to. 如果該範圍是整個方法,我們假設此值 可以安全地 從方法傳回。If that scope is the entire method, we say that the value is safe to return from the method.

型別不是型別的運算式可 ref struct 從整個封 入方法安全地 傳回。An expression whose type is not a ref struct type is safe-to-return from the entire enclosing method. 否則,我們會參考下列規則。Otherwise we refer to the rules below.

參數Parameters

指定型式參數的左值是參考 安全 的參考) (,如下所示:An lvalue designating a formal parameter is ref-safe-to-escape (by reference) as follows:

  • 如果參數是 refoutin 參數,則會從整個方法中 參考安全的參考 (例如藉由 return ref 語句) ,否則為。If the parameter is a ref, out, or in parameter, it is ref-safe-to-escape from the entire method (e.g. by a return ref statement); otherwise
  • 如果參數是 this 結構類型的參數,則它是方法 (的最上層範圍的 參考安全 ,但不是從整個方法本身) ; 範例If the parameter is the this parameter of a struct type, it is ref-safe-to-escape to the top-level scope of the method (but not from the entire method itself); Sample
  • 否則,參數是值參數,它是方法 (的最上層範圍的 參考安全 ,但不是從方法本身) 。Otherwise the parameter is a value parameter, and it is ref-safe-to-escape to the top-level scope of the method (but not from the method itself).

表示使用型式參數之右值的運算式,是從整個方法) 的值的 安全轉型 ( (例如 return) 語句。An expression that is an rvalue designating the use of a formal parameter is safe-to-escape (by value) from the entire method (e.g. by a return statement). 這也適用于 this 參數。This applies to the this parameter as well.

本機Locals

指定區域變數的左值是參考 安全 的參考 (,) 如下所示:An lvalue designating a local variable is ref-safe-to-escape (by reference) as follows:

  • 如果變數是 ref 變數,則會從其初始化運算式的ref 安全對 escape取得其ref 安全到escape; 否則為。If the variable is a ref variable, then its ref-safe-to-escape is taken from the ref-safe-to-escape of its initializing expression; otherwise
  • 變數是在其宣告所在的範圍中, 以 ref 安全的方式來進行 宣告。The variable is ref-safe-to-escape the scope in which it was declared.

運算式,其為指定區域變數使用方式的右值 , (依值) ,如下 所示:An expression that is an rvalue designating the use of a local variable is safe-to-escape (by value) as follows:

  • 但是上述的一般規則,型別不是型別的本機, ref struct 就是從整個封入方法中 安全地 傳回的。But the general rule above, a local whose type is not a ref struct type is safe-to-return from the entire enclosing method.
  • 如果變數是迴圈的反覆運算變數,則 foreach 變數的 安全至 escape 範圍會與迴圈的運算式的 安全對 escape 相同 foreachIf the variable is an iteration variable of a foreach loop, then the variable's safe-to-escape scope is the same as the safe-to-escape of the foreach loop's expression.
  • 型別的本機 ref struct 和在宣告點未初始化的區域,從整個封入方法都是 安全的A local of ref struct type and uninitialized at the point of declaration is safe-to-return from the entire enclosing method.
  • 否則,變數的類型是 ref struct 類型,而且變數的宣告需要初始化運算式。Otherwise the variable's type is a ref struct type, and the variable's declaration requires an initializer. 變數的 安全 轉型範圍與其初始化運算式的 安全對 escape 相同。The variable's safe-to-escape scope is the same as the safe-to-escape of its initializer.

欄位參考Field reference

指定欄位參考的左 e.F 值,是參考 安全 的參考) (,如下所示:An lvalue designating a reference to a field, e.F, is ref-safe-to-escape (by reference) as follows:

  • 如果 e 是參考型別,則會從整個方法中參考 安全的 參考,否則為。If e is of a reference type, it is ref-safe-to-escape from the entire method; otherwise
  • 如果 e 是實值型別,則會從的ref 安全對 escape取得其ref 安全轉型 eIf e is of a value type, its ref-safe-to-escape is taken from the ref-safe-to-escape of e.

指定欄位參考的右 e.F 值,具有 安全到 escape 的範圍,與的 安全對 escape 相同 eAn rvalue designating a reference to a field, e.F, has a safe-to-escape scope that is the same as the safe-to-escape of e.

運算子包括 ?:Operators including ?:

使用者定義運算子的應用會被視為方法調用。The application of a user-defined operator is treated as a method invocation.

若為產生右值的運算子(例如 e1 + e2 或),則 c ? e1 : e2 會將結果的安全轉型 為運算子的 安全到 escape 之間的最小範圍。For an operator that yields an rvalue, such as e1 + e2 or c ? e1 : e2, the safe-to-escape of the result is the narrowest scope among the safe-to-escape of the operands of the operator. 因此,若為產生右值的一元運算子(例如),則 +e 結果的 安全 轉型會是運算元的 安全對換 用。As a consequence, for a unary operator that yields an rvalue, such as +e, the safe-to-escape of the result is the safe-to-escape of the operand.

適用于產生左值的運算子,例如 c ? ref e1 : ref e2For an operator that yields an lvalue, such as c ? ref e1 : ref e2

  • 結果的 ref 安全對 escape 是運算子之運算元的 ref 安全到 escape 之間的最小範圍。the ref-safe-to-escape of the result is the narrowest scope among the ref-safe-to-escape of the operands of the operator.
  • 這些運算元的 安全對 escape 必須一致,而且這是所產生之左值的 安全對 escapethe safe-to-escape of the operands must agree, and that is the safe-to-escape of the resulting lvalue.

方法引動過程Method invocation

由 ref 傳回方法調用所產生的左 e1.M(e2, ...) 值,是 以 ref 安全 的方式,將下列範圍的最小值進行 escape:An lvalue resulting from a ref-returning method invocation e1.M(e2, ...) is ref-safe-to-escape the smallest of the following scopes:

  • 整個封入方法The entire enclosing method
  • 所有和引數運算式的 ref 安全對 escape ref out (排除接收者) the ref-safe-to-escape of all ref and out argument expressions (excluding the receiver)
  • 針對方法的每個 in 參數,如果有對應的運算式是左值,則為其 ref 安全的參考,否則為最接近的封閉範圍For each in parameter of the method, if there is a corresponding expression that is an lvalue, its ref-safe-to-escape, otherwise the nearest enclosing scope
  • 所有引數運算式的 安全對 escape (包括接收者) the safe-to-escape of all argument expressions (including the receiver)

注意:處理常式代碼時,需要最後一個專案符號,例如Note: the last bullet is necessary to handle code such as

var sp = new Span(...)
return ref sp[0];

or

return ref M(sp, 0);

從下列最小的範圍中,方法調用所產生的右 e1.M(e2, ...) 值是安全的- escapeAn rvalue resulting from a method invocation e1.M(e2, ...) is safe-to-escape from the smallest of the following scopes:

  • 整個封入方法The entire enclosing method
  • 所有引數運算式的 安全對 escape (包括接收者) the safe-to-escape of all argument expressions (including the receiver)

右值An Rvalue

右值是從最接近的封入範圍中的 ref 安全到 escapeAn rvalue is ref-safe-to-escape from the nearest enclosing scope. 這種情況發生在調用中,例如 M(ref d.Length) where d 的型別 dynamicThis occurs for example in an invocation such as M(ref d.Length) where d is of type dynamic. 此外,它也會與 (一致,而且可能會在處理與參數對應的引數時) subsumes inIt is also consistent with (and perhaps subsumes) our handling of arguments corresponding to in parameters.

屬性調用Property invocations

屬性調用 (getset) 由上述規則被視為基礎方法的方法調用。A property invocation (either get or set) it treated as a method invocation of the underlying method by the above rules.

stackalloc

Stackalloc 運算式是一個右值,可 安全地 在方法 (的最上層範圍內進行,但不會從整個方法本身) 。A stackalloc expression is an rvalue that is safe-to-escape to the top-level scope of the method (but not from the entire method itself).

函式呼叫Constructor invocations

叫用函式的 new 運算式會遵守與方法叫用相同的規則,該方法調用會被視為傳回所要建立的型別。A new expression that invokes a constructor obeys the same rules as a method invocation that is considered to return the type being constructed.

此外,如果有初始化運算式 ,則不 會超出物件初始化運算式運算式之所有引數/運算元的最小 安全到 escape 的最小值。In addition safe-to-escape is no wider than the smallest of the safe-to-escape of all arguments/operands of the object initializer expressions, recursively, if initializer is present.

Span 函數Span constructor

語言不依賴下列格式的函式 Span<T>The language relies on Span<T> not having a constructor of the following form:

void Example(ref int x)
{
    // Create a span of length one
    var span = new Span<int>(ref x); 
}

這類的函式會 Span<T> 將用來作為欄位的欄位與欄位不區分 refSuch a constructor makes Span<T> which are used as fields indistinguishable from a ref field. 本檔中所述的安全性規則,取決於 ref c # 或 .net 中不是有效結構的欄位。The safety rules described in this document depend on ref fields not being a valid construct in C# or .NET.

default 運算式default expressions

default運算式可從整個封入方法安全地進行 escapeA default expression is safe-to-escape from the entire enclosing method.

語言條件約束Language Constraints

我們想要確保沒有任何 ref 區域變數,而且沒有任何類型的變數 ref struct 參考的堆疊記憶體或變數無法再運作。We wish to ensure that no ref local variable, and no variable of ref struct type, refers to stack memory or variables that are no longer alive. 因此,我們有下列語言條件約束:We therefore have the following language constraints:

  • Ref 參數和 ref 區域變數,以及類型的參數或區域變數都不能 ref struct 放入 lambda 或區域函數中。Neither a ref parameter, nor a ref local, nor a parameter or local of a ref struct type can be lifted into a lambda or local function.

  • Ref 參數或類型的參數都不 ref struct 能是 iterator 方法或方法的引數 asyncNeither a ref parameter nor a parameter of a ref struct type may be an argument on an iterator method or an async method.

  • Ref 區域變數和類型的本機都不 ref struct 能在 yield return 語句或運算式的範圍內 awaitNeither a ref local, nor a local of a ref struct type may be in scope at the point of a yield return statement or an await expression.

  • ref struct型別不能當做型別引數或元組類型中的元素類型使用。A ref struct type may not be used as a type argument, or as an element type in a tuple type.

  • ref struct型別不能是欄位的宣告型別,不同之處在于它可能是另一個實例欄位的宣告型別 ref structA ref struct type may not be the declared type of a field, except that it may be the declared type of an instance field of another ref struct.

  • ref struct類型可能不是陣列的元素類型。A ref struct type may not be the element type of an array.

  • 類型的值 ref struct 可能不會被裝箱:A value of a ref struct type may not be boxed:

    • 沒有從 ref struct 類型轉換成類型或類型的轉換 object System.ValueTypeThere is no conversion from a ref struct type to the type object or the type System.ValueType.
    • ref struct類型不可宣告為可執行任何介面A ref struct type may not be declared to implement any interface
    • 在或中宣告的實例方法 object System.ValueType ,在類型中不會被覆寫,但不 ref struct 能以該類型的接收者來呼叫 ref structNo instance method declared in object or in System.ValueType but not overridden in a ref struct type may be called with a receiver of that ref struct type.
    • ref struct方法轉換不能將類型的實例方法轉換為委派類型。No instance method of a ref struct type may be captured by method conversion to a delegate type.
  • 針對 ref 重新指派 ref e1 = ref e2 ,的 ref 安全 to escape e2 必須至少為的範圍,與的 ref 安全對 escape 的範圍相同 e1For a ref reassignment ref e1 = ref e2, the ref-safe-to-escape of e2 must be at least as wide a scope as the ref-safe-to-escape of e1.

  • 若為 ref return 語句 return ref e1 ,從整個方法的 ref 安全到 escape e1 必須是 參考安全 的。For a ref return statement return ref e1, the ref-safe-to-escape of e1 must be ref-safe-to-escape from the entire method. (TODO:我們也需要 e1 從整個方法中 安全地 escape 的規則,還是多餘的? ) (TODO: Do we also need a rule that e1 must be safe-to-escape from the entire method, or is that redundant?)

  • 針對 return 語句 return e1 ,的 安全對 escape e1 必須是整個方法的 安全對 escape。For a return statement return e1, the safe-to-escape of e1 must be safe-to-escape from the entire method.

  • 針對指派 e1 = e2 ,如果的型別 e1 為型別 ref struct ,則的 安全對 escape e2 必須至少為的範圍與的 安全對轉義 e1For an assignment e1 = e2, if the type of e1 is a ref struct type, then the safe-to-escape of e2 must be at least as wide a scope as the safe-to-escape of e1.

  • 若為方法叫用,如果有 ref 類型的或 out 引數 ref struct (包括接收者) ,而且具有 安全的可 escape E1,則沒有包含接收者) 的引數 (可能會比 E1 更窄的 安全到 escapeFor a method invocation if there is a ref or out argument of a ref struct type (including the receiver), with safe-to-escape E1, then no argument (including the receiver) may have a narrower safe-to-escape than E1. 範例Sample

  • 區域函式或匿名函式可能不會參考 ref struct 在封閉範圍中宣告之類型的本機或參數。A local function or anonymous function may not refer to a local or parameter of ref struct type declared in an enclosing scope.

開啟問題: 我們需要一些規則,讓我們在需要 ref struct 在 await 運算式中溢出類型的堆疊值(例如,在程式碼中)時產生錯誤。Open Issue: We need some rule that permits us to produce an error when needing to spill a stack value of a ref struct type at an await expression, for example in the code

Foo(new Span<int>(...), await e2);

解釋Explanations

這些說明和範例可協助說明上述許多安全性規則為何存在These explanations and samples help explain why many of the safety rules above exist

方法引數必須相符Method Arguments Must Match

當您叫用的方法中有 out ref 一個包含接收者的參數,則 ref struct 所有的 ref struct 存留期都需要相同的存留期。When invoking a method where there is an out, ref parameter that is a ref struct including the receiver then all of the ref struct need to have the same lifetime. 這是必要的,因為 c # 必須根據方法簽章中可用的資訊和呼叫位置上的值存留期,來決定有關存留期安全的所有決策。This is necessary because C# must make all of its decisions around lifetime safety based on the information available in the signature of the method and the lifetime of the values at the call site.

當有 ref 參數之後,就 ref struct 會有這個可能性可以交換其內容。When there are ref parameters that are ref struct then there is the possiblity they could swap around their contents. 因此,在呼叫位置,我們必須確保所有這些 可能 的交換都相容。Hence at the call site we must ensure all of these potential swaps are compatible. 如果語言未強制執行,則會允許如下所示的錯誤程式碼。If the language didn't enforce that then it will allow for bad code like the following.

void M1(ref Span<int> s1)
{
    Span<int> s2 = stackalloc int[1];
    Swap(ref s1, ref s2);
}

void Swap(ref Span<int> x, ref Span<int> y)
{
    // This will effectively assign the stackalloc to the s1 parameter and allow it
    // to escape to the caller of M1
    ref x = ref y; 
}

接收者的限制是必要的,因為沒有任何內容是參考安全的參考,它可以儲存提供的值。The restriction on the receiver is necessary because while none of its contents are ref-safe-to-escape it can store the provided values. 這表示在不相符的存留期內,您可以透過下列方式建立型別安全漏洞:This means with mismatched lifetimes you could create a type safety hole in the following way:

ref struct S
{
    public Span<int> Span;

    public void Set(Span<int> span)
    {
        Span = span;
    }
}

void Broken(ref S s)
{
    Span<int> span = stackalloc int[1];

    // The result of a stackalloc is now stored in s.Span and escaped to the caller
    // of Broken
    s.Set(span); 
}

結構此 EscapeStruct This Escape

當涉及到安全規則時, this 實例成員中的值會模型化為成員的參數。When it comes to span safety rules, the this value in an instance member is modeled as a parameter to the member. 現在對的 struct 型別 this 而言,其實就是 ref S class S 針對名為的) 的成員 (class / structNow for a struct the type of this is actually ref S where in a class it's simply S (for members of a class / struct named S).

this 有不同于其他參數的轉義規則 refYet this has different escaping rules than other ref parameters. 具體而言,它並不是參考安全的,而其他參數則是:Specifically it is not ref-safe-to-escape while other parameters are:

ref struct S
{ 
    int Field;

    // Illegal because `this` isn't safe to escape as ref
    ref int Get() => ref Field;

    // Legal
    ref int GetParam(ref int p) => ref p;
}

這種限制的原因實際上與 struct 成員調用有關。The reason for this restriction actually has little to do with struct member invocation. struct 接收者為右值的成員上,有一些必須在成員調用方面使用的規則。There are some rules that need to be worked out with respect to member invocation on struct members where the receiver is an rvalue. 但這是很平易近人的。But that is very approachable.

這項限制的原因實際上是關於介面調用。The reason for this restriction is actually about interface invocation. 具體來說,它會說明下列範例應該或不應該進行編譯;Specifically it comes down to whether or not the following sample should or should not compile;

interface I1
{
    ref int Get();
}

ref int Use<T>(T p)
    where T : I1
{
    return ref p.Get();
}

請考慮將具 T 現化為的案例 structConsider the case where T is instantiated as a struct. 如果 this 參數是 ref safe 對 escape,則的傳回 p.Get 可能會指向堆疊 (具體而言,它可能是) 具現化類型內的欄位 TIf the this parameter is ref-safe-to-escape then the return of p.Get could point to the stack (specifically it could be a field inside of the instantiated type of T). 這表示語言無法允許此範例進行編譯,因為它可能會將傳回 ref 至堆疊位置。That means the language could not allow this sample to compile as it could be returning a ref to a stack location. 另一方面,如果不 this 是 ref safe 對 escape,則 p.Get 無法參考堆疊,因此可以安全地傳回。On the other hand if this is not ref-safe-to-escape then p.Get cannot refer to the stack and hence it's safe to return.

這就是為什麼中的 escapability this struct 實際上都是關於介面的原因。This is why the escapability of this in a struct is really all about interfaces. 它絕對可以正常運作,但卻有取捨。It can absolutely be made to work but it has a trade off. 這項設計的設計,是為了讓介面更具彈性。The design eventually came down in favor of making interfaces more flexible.

不過,我們可能會在未來將其放寬。There is potential for us to relax this in the future though.

未來的考量Future Considerations

長度一跨越 <T> ref 值Length one Span<T> over ref values

雖然今天並不合法,但在某些情況下,在某個值上建立一個實例的長度會 Span<T> 很有用:Though not legal today there are cases where creating a length one Span<T> instance over a value would be beneficial:

void RefExample()
{
    int x = ...;

    // Today creating a length one Span<int> requires a stackalloc and a new 
    // local
    Span<int> span1 = stackalloc [] { x };
    Use(span1);
    x = span1[0]; 

    // Simpler to just allow length one span
    var span2 = new Span<int>(ref x);
    Use(span2);
}

如果我們提高 固定大小緩衝區 的限制,這項功能會變得更吸引人,因為這會讓 Span<T> 實例的長度更高。This feature gets more compelling if we lift the restrictions on fixed sized buffers as it would allow for Span<T> instances of even greater length.

如果您需要關閉此路徑,則該語言可以藉由確保這類 Span<T> 實例僅向下切入,來滿足此需求。If there is ever a need to go down this path then the language could accommodate this by ensuring such Span<T> instances were downward facing only. 這是因為它們只是 安全地 在其建立所在的範圍內進行 escape。That is they were only ever safe-to-escape to the scope in which they were created. 這可確保語言永遠不需要考慮透過的傳回或欄位來對方法進行的 ref 值的 ref struct 還原 ref structThis ensure the language never had to consider a ref value escaping a method via a ref struct return or field of ref struct. 不過,這可能也需要進一步的變更,才能以 ref 這種方式來捕捉參數。This would likely also require further changes to recognize such constructors as capturing a ref parameter in this way though.