Ponteiros de funçãoFunction Pointers
ResumoSummary
Esta proposta fornece construções de linguagem que expõem opcodes IL que atualmente não podem ser acessados com eficiência ou, em C#, hoje: ldftn
e calli
.This proposal provides language constructs that expose IL opcodes that cannot currently be accessed efficiently, or at all, in C# today: ldftn
and calli
. Esses opcodes de IL podem ser importantes em código de alto desempenho e os desenvolvedores precisam de uma maneira eficiente para acessá-los.These IL opcodes can be important in high performance code and developers need an efficient way to access them.
MotivaçãoMotivation
As motivações e o plano de fundo desse recurso são descritos no seguinte problema (como é uma implementação potencial do recurso):The motivations and background for this feature are described in the following issue (as is a potential implementation of the feature):
https://github.com/dotnet/csharplang/issues/191
Esta é uma proposta de design alternativa para intrínsecos do compiladorThis is an alternate design proposal to compiler intrinsics
Design detalhadoDetailed Design
Ponteiros de funçãoFunction pointers
O idioma permitirá a declaração de ponteiros de função usando a delegate*
sintaxe.The language will allow for the declaration of function pointers using the delegate*
syntax. A sintaxe completa é descrita detalhadamente na próxima seção, mas ela se destina a se assemelhar à sintaxe usada Func
pelas Action
declarações de tipo e.The full syntax is described in detail in the next section but it is meant to resemble the syntax used by Func
and Action
type declarations.
unsafe class Example {
void Example(Action<int> a, delegate*<int, void> f) {
a(42);
f(42);
}
}
Esses tipos são representados usando o tipo de ponteiro de função, conforme descrito em ECMA-335.These types are represented using the function pointer type as outlined in ECMA-335. Isso significa que a invocação de um delegate*
usará calli
onde a invocação de um delegate
será usada callvirt
no Invoke
método.This means invocation of a delegate*
will use calli
where invocation of a delegate
will use callvirt
on the Invoke
method.
Sintaticamente, embora a invocação seja idêntica para ambas as construções.Syntactically though invocation is identical for both constructs.
A definição ECMA-335 dos ponteiros de método inclui a Convenção de chamada como parte da assinatura de tipo (seção 7,1).The ECMA-335 definition of method pointers includes the calling convention as part of the type signature (section 7.1).
A Convenção de chamada padrão será managed
.The default calling convention will be managed
. As convenções de chamada não gerenciadas podem ser especificadas por meio da inserção de uma unmanaged
palavra-chave após a delegate*
sintaxe, que usará o padrão da plataforma de tempo de execução.Unmanaged calling conventions can by specified by putting an unmanaged
keyword afer the delegate*
syntax, which will use the runtime platform default. Convenções não gerenciadas específicas podem então ser especificadas entre colchetes para a unmanaged
palavra-chave, especificando qualquer tipo começando com CallConv
no System.Runtime.CompilerServices
namespace, deixando o CallConv
prefixo.Specific unmanaged conventions can then be specified in brackets to the unmanaged
keyword by specifying any type starting with CallConv
in the System.Runtime.CompilerServices
namespace, leaving off the CallConv
prefix. Esses tipos devem vir da biblioteca principal do programa, e o conjunto de combinações válidas é dependente da plataforma.These types must come from the program's core library, and the set of valid combinations is platform-dependent.
//This method has a managed calling convention. This is the same as leaving the managed keyword off.
delegate* managed<int, int>;
// This method will be invoked using whatever the default unmanaged calling convention on the runtime
// platform is. This is platform and architecture dependent and is determined by the CLR at runtime.
delegate* unmanaged<int, int>;
// This method will be invoked using the cdecl calling convention
// Cdecl maps to System.Runtime.CompilerServices.CallConvCdecl
delegate* unmanaged[Cdecl] <int, int>;
// This method will be invoked using the stdcall calling convention, and suppresses GC transition
// Stdcall maps to System.Runtime.CompilerServices.CallConvStdcall
// SuppressGCTransition maps to System.Runtime.CompilerServices.CallConvSuppressGCTransition
delegate* unmanaged[Stdcall, SuppressGCTransition] <int, int>;
As conversões entre delegate*
os tipos são feitas com base em sua assinatura, incluindo a Convenção de chamada.Conversions between delegate*
types is done based on their signature including the calling convention.
unsafe class Example {
void Conversions() {
delegate*<int, int, int> p1 = ...;
delegate* managed<int, int, int> p2 = ...;
delegate* unmanaged<int, int, int> p3 = ...;
p1 = p2; // okay p1 and p2 have compatible signatures
Console.WriteLine(p2 == p1); // True
p2 = p3; // error: calling conventions are incompatible
}
}
Um delegate*
tipo é um tipo de ponteiro que significa que ele tem todos os recursos e restrições de um tipo de ponteiro padrão:A delegate*
type is a pointer type which means it has all of the capabilities and restrictions of a standard pointer type:
- Válido somente em um
unsafe
contexto.Only valid in anunsafe
context. - Os métodos que contêm um
delegate*
parâmetro ou tipo de retorno só podem ser chamados de umunsafe
contexto.Methods which contain adelegate*
parameter or return type can only be called from anunsafe
context. - Não pode ser convertido em
object
.Cannot be converted toobject
. - Não pode ser usado como um argumento genérico.Cannot be used as a generic argument.
- Pode converter implicitamente
delegate*
emvoid*
.Can implicitly convertdelegate*
tovoid*
. - Pode converter explicitamente de
void*
paradelegate*
.Can explicitly convert fromvoid*
todelegate*
.
Restrições:Restrictions:
- Atributos personalizados não podem ser aplicados a um
delegate*
ou a qualquer um de seus elementos.Custom attributes cannot be applied to adelegate*
or any of its elements. - Um
delegate*
parâmetro não pode ser marcado comoparams
Adelegate*
parameter cannot be marked asparams
- Um
delegate*
tipo tem todas as restrições de um tipo de ponteiro normal.Adelegate*
type has all of the restrictions of a normal pointer type. - A aritmética de ponteiro não pode ser executada diretamente em tipos de ponteiro de função.Pointer arithmetic cannot be performed directly on function pointer types.
Sintaxe de ponteiro de funçãoFunction pointer syntax
A sintaxe de ponteiro de função completa é representada pela seguinte gramática:The full function pointer syntax is represented by the following grammar:
pointer_type
: ...
| funcptr_type
;
funcptr_type
: 'delegate' '*' calling_convention_specifier? '<' funcptr_parameter_list funcptr_return_type '>'
;
calling_convention_specifier
: 'managed'
| 'unmanaged' ('[' unmanaged_calling_convention ']')?
;
unmanaged_calling_convention
: 'Cdecl'
| 'Stdcall'
| 'Thiscall'
| 'Fastcall'
| identifier (',' identifier)*
;
funptr_parameter_list
: (funcptr_parameter ',')*
;
funcptr_parameter
: funcptr_parameter_modifier? type
;
funcptr_return_type
: funcptr_return_modifier? return_type
;
funcptr_parameter_modifier
: 'ref'
| 'out'
| 'in'
;
funcptr_return_modifier
: 'ref'
| 'ref readonly'
;
Se não calling_convention_specifier
for fornecido, o padrão será managed
.If no calling_convention_specifier
is provided, the default is managed
. A codificação de metadados precisa dos calling_convention_specifier
e quais identifier
s são válidos no unmanaged_calling_convention
é abordada na representação de metadados das convenções de chamada.The precise metadata encoding of the calling_convention_specifier
and what identifier
s are valid in the unmanaged_calling_convention
is covered in Metadata Representation of Calling Conventions.
delegate int Func1(string s);
delegate Func1 Func2(Func1 f);
// Function pointer equivalent without calling convention
delegate*<string, int>;
delegate*<delegate*<string, int>, delegate*<string, int>>;
// Function pointer equivalent with calling convention
delegate* managed<string, int>;
delegate*<delegate* managed<string, int>, delegate*<string, int>>;
Conversões de ponteiro de funçãoFunction pointer conversions
Em um contexto sem segurança, o conjunto de conversões implícitas disponíveis (conversões implícitas) é estendido para incluir as seguintes conversões de ponteiro implícitas:In an unsafe context, the set of available implicit conversions (Implicit conversions) is extended to include the following implicit pointer conversions:
- Conversões existentesExisting conversions
- De _ tipo funcptr
F0
para outro _ tipo de funcptrF1
, desde que todos os seguintes itens sejam verdadeiros:From funcptr_typeF0
to another funcptr_typeF1
, provided all of the following are true:F0
eF1
têm o mesmo número de parâmetros, e cada parâmetroD0n
noF0
tem os mesmosref
out
modificadores,, ouin
como o parâmetro correspondenteD1n
noF1
.F0
andF1
have the same number of parameters, and each parameterD0n
inF0
has the sameref
,out
, orin
modifiers as the corresponding parameterD1n
inF1
.- Para cada parâmetro de valor (um parâmetro sem nenhum
ref
,out
ouin
Modificador), uma conversão de identidade, conversão de referência implícita ou conversão de ponteiro implícita existe do tipo de parâmetro emF0
para o tipo de parâmetro correspondente noF1
.For each value parameter (a parameter with noref
,out
, orin
modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type inF0
to the corresponding parameter type inF1
. - Para cada
ref
out
parâmetro,, ouin
, o tipo de parâmetro noF0
é o mesmo que o tipo de parâmetro correspondente noF1
.For eachref
,out
, orin
parameter, the parameter type inF0
is the same as the corresponding parameter type inF1
. - Se o tipo de retorno for por valor (não
ref
ouref readonly
), uma identidade, referência implícita ou conversão implícita do ponteiro existirá do tipo de retorno deF1
para o tipo de retorno deF0
.If the return type is by value (noref
orref readonly
), an identity, implicit reference, or implicit pointer conversion exists from the return type ofF1
to the return type ofF0
. - Se o tipo de retorno for por referência (
ref
ouref readonly
), o tipo de retorno e osref
modificadores deF1
serão iguais aos do tipo de retorno eref
dos modificadores deF0
.If the return type is by reference (ref
orref readonly
), the return type andref
modifiers ofF1
are the same as the return type andref
modifiers ofF0
. - A Convenção de chamada do
F0
é igual à Convenção de chamada doF1
.The calling convention ofF0
is the same as the calling convention ofF1
.
Permitir endereço para métodos de destinoAllow address-of to target methods
Os grupos de métodos agora serão permitidos como argumentos para uma expressão de endereço.Method groups will now be allowed as arguments to an address-of expression. O tipo de tal expressão será um delegate*
que tem a assinatura equivalente do método de destino e uma Convenção de chamada gerenciada:The type of such an expression will be a delegate*
which has the equivalent signature of the target method and a managed calling convention:
unsafe class Util {
public static void Log() { }
void Use() {
delegate*<void> ptr1 = &Util.Log;
// Error: type "delegate*<void>" not compatible with "delegate*<int>";
delegate*<int> ptr2 = &Util.Log;
}
}
Em um contexto sem segurança, um método M
é compatível com um tipo de ponteiro F
de função se todas as seguintes opções forem verdadeiras:In an unsafe context, a method M
is compatible with a function pointer type F
if all of the following are true:
M
eF
têm o mesmo número de parâmetros, e cada parâmetro noM
tem os mesmosref
out
modificadores,, ouin
como o parâmetro correspondente noF
.M
andF
have the same number of parameters, and each parameter inM
has the sameref
,out
, orin
modifiers as the corresponding parameter inF
.- Para cada parâmetro de valor (um parâmetro sem nenhum
ref
,out
ouin
Modificador), uma conversão de identidade, conversão de referência implícita ou conversão de ponteiro implícita existe do tipo de parâmetro emM
para o tipo de parâmetro correspondente noF
.For each value parameter (a parameter with noref
,out
, orin
modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type inM
to the corresponding parameter type inF
. - Para cada
ref
out
parâmetro,, ouin
, o tipo de parâmetro noM
é o mesmo que o tipo de parâmetro correspondente noF
.For eachref
,out
, orin
parameter, the parameter type inM
is the same as the corresponding parameter type inF
. - Se o tipo de retorno for por valor (não
ref
ouref readonly
), uma identidade, referência implícita ou conversão implícita do ponteiro existirá do tipo de retorno deF
para o tipo de retorno deM
.If the return type is by value (noref
orref readonly
), an identity, implicit reference, or implicit pointer conversion exists from the return type ofF
to the return type ofM
. - Se o tipo de retorno for por referência (
ref
ouref readonly
), o tipo de retorno e osref
modificadores deF
serão iguais aos do tipo de retorno eref
dos modificadores deM
.If the return type is by reference (ref
orref readonly
), the return type andref
modifiers ofF
are the same as the return type andref
modifiers ofM
. - A Convenção de chamada do
M
é igual à Convenção de chamada doF
.The calling convention ofM
is the same as the calling convention ofF
. Isso inclui o bit da Convenção de chamada, bem como quaisquer sinalizadores de Convenção de chamada especificados no identificador não gerenciado.This includes both the calling convention bit, as well as any calling convention flags specified in the unmanaged identifier. M
é um método estático.M
is a static method.
Em um contexto não seguro, existe uma conversão implícita de uma expressão de endereço cujo destino é um grupo de métodos E
para um tipo de ponteiro de função compatível F
se E
contiver pelo menos um método que seja aplicável em seu formato normal a uma lista de argumentos construída pelo uso dos tipos de parâmetro e modificadores de F
, conforme descrito no seguinte.In an unsafe context, an implicit conversion exists from an address-of expression whose target is a method group E
to a compatible function pointer type F
if E
contains at least one method that is applicable in its normal form to an argument list constructed by use of the parameter types and modifiers of F
, as described in the following.
- Um único método
M
é selecionado, correspondendo a uma invocação de método do formulárioE(A)
com as seguintes modificações:A single methodM
is selected corresponding to a method invocation of the formE(A)
with the following modifications:- A lista de argumentos
A
é uma lista de expressões, cada uma classificada como variável e com o tipo e o modificador (ref
,out
ouin
) da _ _ lista de parâmetros funcptr correspondente deF
.The arguments listA
is a list of expressions, each classified as a variable and with the type and modifier (ref
,out
, orin
) of the corresponding funcptr_parameter_list ofF
. - Os métodos candidatos são apenas os métodos que são aplicáveis em seu formato normal, não aqueles aplicáveis em sua forma expandida.The candidate methods are only those methods that are applicable in their normal form, not those applicable in their expanded form.
- Os métodos candidatos são apenas os métodos que são estáticos.The candidate methods are only those methods that are static.
- A lista de argumentos
- Se o algoritmo de resolução de sobrecarga produzir um erro, ocorrerá um erro de tempo de compilação.If the algorithm of overload resolution produces an error, then a compile-time error occurs. Caso contrário, o algoritmo produz um único método melhor
M
com o mesmo número de parâmetros queF
e a conversão é considerada como existente.Otherwise, the algorithm produces a single best methodM
having the same number of parameters asF
and the conversion is considered to exist. - O método selecionado
M
deve ser compatível (conforme definido acima) com o tipo de ponteiro de funçãoF
.The selected methodM
must be compatible (as defined above) with the function pointer typeF
. Caso contrário, ocorrerá um erro de tempo de compilação.Otherwise, a compile-time error occurs. - O resultado da conversão é um ponteiro de função do tipo
F
.The result of the conversion is a function pointer of typeF
.
Isso significa que os desenvolvedores podem depender das regras de resolução de sobrecarga para trabalhar em conjunto com o operador address-of:This means developers can depend on overload resolution rules to work in conjunction with the address-of operator:
unsafe class Util {
public static void Log() { }
public static void Log(string p1) { }
public static void Log(int i) { };
void Use() {
delegate*<void> a1 = &Log; // Log()
delegate*<int, void> a2 = &Log; // Log(int i)
// Error: ambiguous conversion from method group Log to "void*"
void* v = &Log;
}
O operador address-of será implementado usando a ldftn
instrução.The address-of operator will be implemented using the ldftn
instruction.
Restrições deste recurso:Restrictions of this feature:
- Aplica-se somente a métodos marcados como
static
.Only applies to methods marked asstatic
. - Funções não
static
locais não podem ser usadas no&
.Non-static
local functions cannot be used in&
. Os detalhes de implementação desses métodos são deliberadamente não especificados pelo idioma.The implementation details of these methods are deliberately not specified by the language. Isso inclui se eles são estáticos versus instância ou exatamente em qual assinatura eles são emitidos.This includes whether they are static vs. instance or exactly what signature they are emitted with.
Operadores em tipos de ponteiro de funçãoOperators on Function Pointer Types
A seção em código não seguro em operadores é modificada da seguinte maneira:The section in unsafe code on operators is modified as such:
Em um contexto não seguro, várias construções estão disponíveis para operar em todos os type_s de _pointer _ que não são _funcptr _ type_s:In an unsafe context, several constructs are available for operating on all _pointer_type_s that are not _funcptr_type_s:
- O
*
operador pode ser usado para executar o direcionamento de ponteiro (indireção de ponteiro).The*
operator may be used to perform pointer indirection (Pointer indirection).- O
->
operador pode ser usado para acessar um membro de uma struct por meio de um ponteiro (acesso de membro de ponteiro).The->
operator may be used to access a member of a struct through a pointer (Pointer member access).- O
[]
operador pode ser usado para indexar um ponteiro (acesso de elemento de ponteiro).The[]
operator may be used to index a pointer (Pointer element access).- O
&
operador pode ser usado para obter o endereço de uma variável (o operador address-of).The&
operator may be used to obtain the address of a variable (The address-of operator).- Os
++
--
operadores e podem ser usados para incrementar e decrementar ponteiros (incremento de ponteiro e decréscimo).The++
and--
operators may be used to increment and decrement pointers (Pointer increment and decrement).- Os
+
-
operadores e podem ser usados para executar aritmética de ponteiro (aritmética de ponteiro).The+
and-
operators may be used to perform pointer arithmetic (Pointer arithmetic).- Os
==
operadores,,!=
<
,,>
<=
e=>
podem ser usados para comparar ponteiros (comparação de ponteiros).The==
,!=
,<
,>
,<=
, and=>
operators may be used to compare pointers (Pointer comparison).- O
stackalloc
operador pode ser usado para alocar memória da pilha de chamadas (buffers de tamanho fixo).Thestackalloc
operator may be used to allocate memory from the call stack (Fixed size buffers).- A
fixed
instrução pode ser usada para corrigir temporariamente uma variável para que seu endereço possa ser obtido (a instrução Fixed).Thefixed
statement may be used to temporarily fix a variable so its address can be obtained (The fixed statement).Em um contexto não seguro, várias construções estão disponíveis para operar em todos os _funcptr _ type_s:In an unsafe context, several constructs are available for operating on all _funcptr_type_s:
- O
&
operador pode ser usado para obter o endereço de métodos estáticos (permitir endereço-de para métodos de destino)The&
operator may be used to obtain the address of static methods (Allow address-of to target methods)- Os
==
operadores,,!=
<
,,>
<=
e=>
podem ser usados para comparar ponteiros (comparação de ponteiros).The==
,!=
,<
,>
,<=
, and=>
operators may be used to compare pointers (Pointer comparison).
Além disso, modificamos todas as seções em Pointers in expressions
para proibir tipos de ponteiro de função, exceto Pointer comparison
e The sizeof operator
.Additionally, we modify all the sections in Pointers in expressions
to forbid function pointer types, except Pointer comparison
and The sizeof operator
.
Melhor membro da funçãoBetter function member
A melhor especificação de membro de função será alterada para incluir a seguinte linha:The better function member specification will be changed to include the following line:
Um
delegate*
é mais específico do quevoid*
Adelegate*
is more specific thanvoid*
Isso significa que é possível sobrecarregar on void*
e a delegate*
e ainda facilmente usar o operador address-of.This means that it is possible to overload on void*
and a delegate*
and still sensibly use the address-of operator.
Inferência de tiposType Inference
Em código não seguro, as seguintes alterações são feitas nos algoritmos de inferência de tipos:In unsafe code, the following changes are made to the type inference algorithms:
Tipos de entradaInput types
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#input-types
O seguinte é adicionado:The following is added:
Se
E
for um grupo de métodos de endereço eT
for um tipo de ponteiro de função, todos os tipos de parâmetro deT
são tipos de entrada deE
com o tipoT
.IfE
is an address-of method group andT
is a function pointer type then all the parameter types ofT
are input types ofE
with typeT
.
Tipos de saídaOutput types
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#output-types
O seguinte é adicionado:The following is added:
Se
E
for um grupo de métodos de endereço eT
for um tipo de ponteiro de função, o tipo de retorno deT
será um tipo de saídaE
com o tipoT
.IfE
is an address-of method group andT
is a function pointer type then the return type ofT
is an output type ofE
with typeT
.
Inferências de tipo de saídaOutput type inferences
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#output-type-inferences
O marcador a seguir é adicionado entre os marcadores 2 e 3:The following bullet is added between bullets 2 and 3:
- Se
E
é um grupo de métodos de endereço eT
é um tipo de ponteiro de função com tipos de parâmetroT1...Tk
e tipo de retornoTb
, e a resolução de sobrecarga deE
com os tiposT1..Tk
gera um único método com tipo de retornoU
, uma inferência de limite inferior é feita deU
paraTb
.IfE
is an address-of method group andT
is a function pointer type with parameter typesT1...Tk
and return typeTb
, and overload resolution ofE
with the typesT1..Tk
yields a single method with return typeU
, then a lower-bound inference is made fromU
toTb
.
Inferências exatasExact inferences
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#exact-inferences
O submarcador a seguir é adicionado como um caso no marcador 2:The following sub-bullet is added as a case to bullet 2:
V
é um tipo de ponteiro de funçãodelegate*<V2..Vk, V1>
eU
é um tipo de ponteiro de funçãodelegate*<U2..Uk, U1>
, e a Convenção de chamada deV
é idêntica aU
e o refness deVi
é idêntico aUi
.V
is a function pointer typedelegate*<V2..Vk, V1>
andU
is a function pointer typedelegate*<U2..Uk, U1>
, and the calling convention ofV
is identical toU
, and the refness ofVi
is identical toUi
.
Inferências de limite inferiorLower-bound inferences
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#lower-bound-inferences
O seguinte caso é adicionado ao marcador 3:The following case is added to bullet 3:
V
é um tipo de ponteiro de funçãodelegate*<V2..Vk, V1>
e há um tipo de ponteiro de funçãodelegate*<U2..Uk, U1>
queU
é idêntico adelegate*<U2..Uk, U1>
, e a Convenção de chamada deV
é idêntica aU
e o refness deVi
é idêntico aUi
.V
is a function pointer typedelegate*<V2..Vk, V1>
and there is a function pointer typedelegate*<U2..Uk, U1>
such thatU
is identical todelegate*<U2..Uk, U1>
, and the calling convention ofV
is identical toU
, and the refness ofVi
is identical toUi
.
O primeiro marcador de inferência de Ui
para Vi
é modificado para:The first bullet of inference from Ui
to Vi
is modified to:
- Se
U
não for um tipo de ponteiro de função eUi
não for conhecido como um tipo de referência, ou seU
for um tipo de ponteiro de função eUi
não for conhecido como um tipo de ponteiro de função ou um tipo de referência, uma inferência exata será feitaIfU
is not a function pointer type andUi
is not known to be a reference type, or ifU
is a function pointer type andUi
is not known to be a function pointer type or a reference type, then an exact inference is made
Depois, adicionado após o submarcador de inferência de Ui
para Vi
:Then, added after the 3rd bullet of inference from Ui
to Vi
:
- Caso contrário, se
V
fordelegate*<V2..Vk, V1>
, a inferência dependerá do parâmetro i-th dedelegate*<V2..Vk, V1>
:Otherwise, ifV
isdelegate*<V2..Vk, V1>
then inference depends on the i-th parameter ofdelegate*<V2..Vk, V1>
:
- Se V1:If V1:
- Se o retorno for por valor, será feita uma inferência de limite inferior .If the return is by value, then a lower-bound inference is made.
- Se o retorno for por referência, uma inferência exata será feita.If the return is by reference, then an exact inference is made.
- Se v2.. Vk:If V2..Vk:
- Se o parâmetro for por valor, uma inferência de limite superior será feita.If the parameter is by value, then an upper-bound inference is made.
- Se o parâmetro for por referência, uma inferência exata será feita.If the parameter is by reference, then an exact inference is made.
Inferências de limite superiorUpper-bound inferences
https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#upper-bound-inferences
O seguinte caso é adicionado ao marcador 2:The following case is added to bullet 2:
U
é um tipo de ponteiro de funçãodelegate*<U2..Uk, U1>
eV
é um tipo de ponteiro de função que é idêntico adelegate*<V2..Vk, V1>
, e a Convenção de chamada deU
é idêntica aV
e o refness deUi
é idêntico aVi
.U
is a function pointer typedelegate*<U2..Uk, U1>
andV
is a function pointer type which is identical todelegate*<V2..Vk, V1>
, and the calling convention ofU
is identical toV
, and the refness ofUi
is identical toVi
.
O primeiro marcador de inferência de Ui
para Vi
é modificado para:The first bullet of inference from Ui
to Vi
is modified to:
- Se
U
não for um tipo de ponteiro de função eUi
não for conhecido como um tipo de referência, ou seU
for um tipo de ponteiro de função eUi
não for conhecido como um tipo de ponteiro de função ou um tipo de referência, uma inferência exata será feitaIfU
is not a function pointer type andUi
is not known to be a reference type, or ifU
is a function pointer type andUi
is not known to be a function pointer type or a reference type, then an exact inference is made
Depois, adicionado após o submarcador de inferência de Ui
para Vi
:Then added after the 3rd bullet of inference from Ui
to Vi
:
- Caso contrário, se
U
fordelegate*<U2..Uk, U1>
, a inferência dependerá do parâmetro i-th dedelegate*<U2..Uk, U1>
:Otherwise, ifU
isdelegate*<U2..Uk, U1>
then inference depends on the i-th parameter ofdelegate*<U2..Uk, U1>
:
- Se U1:If U1:
- Se o retorno for por valor, uma inferência de limite superior será feita.If the return is by value, then an upper-bound inference is made.
- Se o retorno for por referência, uma inferência exata será feita.If the return is by reference, then an exact inference is made.
- Se U2.. BritânicoIf U2..Uk:
- Se o parâmetro for por valor, será feita uma inferência de limite inferior .If the parameter is by value, then a lower-bound inference is made.
- Se o parâmetro for por referência, uma inferência exata será feita.If the parameter is by reference, then an exact inference is made.
Representação de metadados in
de out
parâmetros,, e ref readonly
tipos de retornoMetadata representation of in
, out
, and ref readonly
parameters and return types
As assinaturas de ponteiro de função não têm nenhum local de sinalizadores de parâmetro, portanto, devemos codificar se os parâmetros e o tipo de retorno são in
, out
ou ref readonly
usando modreqs.Function pointer signatures have no parameter flags location, so we must encode whether parameters and the return type are in
, out
, or ref readonly
by using modreqs.
in
Reutilizamos System.Runtime.InteropServices.InAttribute
, aplicada como um modreq
para o especificador de referência em um parâmetro ou tipo de retorno, para significar o seguinte:We reuse System.Runtime.InteropServices.InAttribute
, applied as a modreq
to the ref specifier on a parameter or return type, to mean the following:
- Se aplicado a um especificador de referência de parâmetro, esse parâmetro será tratado como
in
.If applied to a parameter ref specifier, this parameter is treated asin
. - Se aplicado ao especificador de referência de tipo de retorno, o tipo de retorno será tratado como
ref readonly
.If applied to the return type ref specifier, the return type is treated asref readonly
.
out
Usamos System.Runtime.InteropServices.OutAttribute
, aplicados como um modreq
para o especificador de referência em um tipo de parâmetro, para significar que o parâmetro é um out
parâmetro.We use System.Runtime.InteropServices.OutAttribute
, applied as a modreq
to the ref specifier on a parameter type, to mean that the parameter is an out
parameter.
ErrorsErrors
- É um erro a ser aplicado
OutAttribute
como um modreq a um tipo de retorno.It is an error to applyOutAttribute
as a modreq to a return type. - É um erro aplicar
InAttribute
eOutAttribute
como um modreq a um tipo de parâmetro.It is an error to apply bothInAttribute
andOutAttribute
as a modreq to a parameter type. - Se ambos forem especificados via modopt, eles serão ignorados.If either are specified via modopt, they are ignored.
Representação de metadados de convenções de chamadaMetadata Representation of Calling Conventions
As convenções de chamada são codificadas em uma assinatura de método nos metadados por uma combinação do CallKind
sinalizador na assinatura e zero ou mais modopt
s no início da assinatura.Calling conventions are encoded in a method signature in metadata by a combination of the CallKind
flag in the signature and zero or more modopt
s at the start of the signature. O ECMA-335 declara atualmente os seguintes elementos no CallKind
sinalizador:ECMA-335 currently declares the following elements in the CallKind
flag:
CallKind
: default
| unmanaged cdecl
| unmanaged fastcall
| unmanaged thiscall
| unmanaged stdcall
| varargs
;
Desses, os ponteiros de função no C# oferecerão suporte a todos, exceto varargs
.Of these, function pointers in C# will support all but varargs
.
Além disso, o tempo de execução (e, eventualmente, 335) será atualizado para incluir um novo CallKind
nas novas plataformas.In addition, the runtime (and eventually 335) will be updated to include a new CallKind
on new platforms. Isso não tem um nome formal no momento, mas este documento será usado unmanaged ext
como um espaço reservado para destacar o novo formato de Convenção de chamada extensível.This does not have a formal name currently, but this document will use unmanaged ext
as a placeholder to stand for the new extensible calling convention format. Sem modopt
s, unmanaged ext
é a Convenção de chamada padrão da plataforma, com unmanaged
colchetes.With no modopt
s, unmanaged ext
is the platform default calling convention, unmanaged
without the square brackets.
Mapeando o calling_convention_specifier
para um CallKind
Mapping the calling_convention_specifier
to a CallKind
Um calling_convention_specifier
que é omitido ou especificado como managed
, é mapeado para o default
CallKind
.A calling_convention_specifier
that is omitted, or specified as managed
, maps to the default
CallKind
. Isso é o padrão CallKind
de qualquer método não atribuído com UnmanagedCallersOnly
.This is default CallKind
of any method not attributed with UnmanagedCallersOnly
.
O C# reconhece 4 identificadores especiais que são mapeados para s não gerenciados específicos existentes CallKind
do ECMA 335.C# recognizes 4 special identifiers that map to specific existing unmanaged CallKind
s from ECMA 335. Para que esse mapeamento ocorra, esses identificadores devem ser especificados por conta própria, sem nenhum outro identificador, e esse requisito é codificado na especificação para unmanaged_calling_convention
s.In order for this mapping to occur, these identifiers must be specified on their own, with no other identifiers, and this requirement is encoded into the spec for unmanaged_calling_convention
s. Esses identificadores são Cdecl
, Thiscall
, Stdcall
e Fastcall
, que correspondem a unmanaged cdecl
, unmanaged thiscall
, unmanaged stdcall
e unmanaged fastcall
, respectivamente.These identifiers are Cdecl
, Thiscall
, Stdcall
, and Fastcall
, which correspond to unmanaged cdecl
, unmanaged thiscall
, unmanaged stdcall
, and unmanaged fastcall
, respectively. Se mais de um identifer
for especificado ou o único identifier
não for dos identificadores reconhecidos especialmente, executaremos uma pesquisa de nome especial no identificador com as seguintes regras:If more than one identifer
is specified, or the single identifier
is not of the specially recognized identifiers, we perform special name lookup on the identifier with the following rules:
- Precedemos o
identifier
com a cadeia de caracteresCallConv
We prepend theidentifier
with the stringCallConv
- Examinamos apenas os tipos definidos no
System.Runtime.CompilerServices
namespace.We look only at types defined in theSystem.Runtime.CompilerServices
namespace. - Examinamos apenas os tipos definidos na biblioteca principal do aplicativo, que é a biblioteca que define
System.Object
e não tem dependências.We look only at types defined in the core library of the application, which is the library that definesSystem.Object
and has no dependencies. - Estamos procurando apenas em tipos públicos.We look only at public types.
Se a pesquisa for realizada com sucesso em todos os identifier
s especificados em um unmanaged_calling_convention
, codificaremos o as CallKind
unmanaged ext
e codificaremos cada um dos tipos resolvidos no conjunto de modopt
s no início da assinatura do ponteiro de função.If lookup succeeds on all of the identifier
s specified in an unmanaged_calling_convention
, we encode the CallKind
as unmanaged ext
, and encode each of the resolved types in the set of modopt
s at the beginning of the function pointer signature. Como uma observação, essas regras significam que os usuários não podem prefixar esses identifier
s com CallConv
, pois isso resultará na pesquisa CallConvCallConvVectorCall
.As a note, these rules mean that users cannot prefix these identifier
s with CallConv
, as that will result in looking up CallConvCallConvVectorCall
.
Ao interpretar metadados, primeiro vamos examinar o CallKind
.When interpreting metadata, we first look at the CallKind
. Se for algo diferente de unmanaged ext
, ignoramos todos os modopt
s no tipo de retorno para fins de determinação da Convenção de chamada e usam apenas o CallKind
.If it is anything other than unmanaged ext
, we ignore all modopt
s on the return type for the purposes of determining the calling convention, and use only the CallKind
. Se CallKind
for, veremos unmanaged ext
o modopts no início do tipo de ponteiro de função, assumindo a União de todos os tipos que atendem aos seguintes requisitos:If the CallKind
is unmanaged ext
, we look at the modopts at the start of the function pointer type, taking the union of all types that meet the following requirements:
- O é definido na biblioteca principal, que é a biblioteca que faz referência a nenhuma outra biblioteca e define
System.Object
.The is defined in the core library, which is the library that references no other libraries and definesSystem.Object
. - O tipo é definido no
System.Runtime.CompilerServices
namespace.The type is defined in theSystem.Runtime.CompilerServices
namespace. - O tipo começa com o prefixo
CallConv
.The type starts with the prefixCallConv
. - O tipo é público.The type is public.
Eles representam os tipos que devem ser encontrados ao executar a pesquisa nos identifier
s em um unmanaged_calling_convention
ao definir um tipo de ponteiro de função na origem.These represent the types that must be found when performing lookup on the identifier
s in an unmanaged_calling_convention
when defining a function pointer type in source.
É um erro tentar usar um ponteiro de função com um CallKind
de unmanaged ext
se o tempo de execução de destino não oferecer suporte ao recurso.It is an error to attempt to use a function pointer with a CallKind
of unmanaged ext
if the target runtime does not support the feature. Isso será determinado procurando a presença da System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind
constante.This will be determined by looking for the presence of the System.Runtime.CompilerServices.RuntimeFeature.UnmanagedCallKind
constant. Se essa constante estiver presente, o tempo de execução será considerado para dar suporte ao recurso.If this constant is present, the runtime is considered to support the feature.
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
é um atributo usado pelo CLR para indicar que um método deve ser chamado com uma Convenção de chamada específica.System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute
is an attribute used by the CLR to indicate that a method should be called with a specific calling convention. Por isso, apresentamos o seguinte suporte para trabalhar com o atributo:Because of this, we introduce the following support for working with the attribute:
- É um erro chamar diretamente um método anotado com esse atributo em C#.It is an error to directly call a method annotated with this attribute from C#. Os usuários devem obter um ponteiro de função para o método e, em seguida, invocar esse ponteiro.Users must obtain a function pointer to the method and then invoke that pointer.
- É um erro aplicar o atributo a qualquer coisa que não seja um método estático comum ou uma função local estática comum.It is an error to apply the attribute to anything other than an ordinary static method or ordinary static local function. O compilador C# marcará quaisquer métodos não estáticos ou estáticos não-comuns importados de metadados com esse atributo como sem suporte no idioma.The C# compiler will mark any non-static or static non-ordinary methods imported from metadata with this attribute as unsupported by the language.
- É um erro para um método marcado com o atributo para ter um parâmetro ou tipo de retorno que não seja um
unmanaged_type
.It is an error for a method marked with the attribute to have a parameter or return type that is not anunmanaged_type
. - É um erro para um método marcado com o atributo para ter parâmetros de tipo, mesmo se esses parâmetros de tipo forem restritos a
unmanaged
.It is an error for a method marked with the attribute to have type parameters, even if those type parameters are constrained tounmanaged
. - É um erro para um método em um tipo genérico ser marcado com o atributo.It is an error for a method in a generic type to be marked with the attribute.
- É um erro converter um método marcado com o atributo para um tipo delegado.It is an error to convert a method marked with the attribute to a delegate type.
- É um erro especificar quaisquer tipos para
UnmanagedCallersOnly.CallConvs
os quais não atendem aos requisitos para chamar a Convençãomodopt
s nos metadados.It is an error to specify any types forUnmanagedCallersOnly.CallConvs
that do not meet the requirements for calling conventionmodopt
s in metadata.
Ao determinar a Convenção de chamada de um método marcado com um UnmanagedCallersOnly
atributo válido, o compilador executa as seguintes verificações nos tipos especificados na CallConvs
propriedade para determinar o efetivo CallKind
e os modopt
s que devem ser usados para determinar a Convenção de chamada:When determining the calling convention of a method marked with a valid UnmanagedCallersOnly
attribute, the compiler performs the following checks on the types specified in the CallConvs
property to determine the effective CallKind
and modopt
s that should be used to determine the calling convention:
- Se nenhum tipo for especificado, o
CallKind
será tratado comounmanaged ext
, sem Convenção de chamadamodopt
s no início do tipo de ponteiro de função.If no types are specified, theCallKind
is treated asunmanaged ext
, with no calling conventionmodopt
s at the start of the function pointer type. - Se houver um tipo especificado, e esse tipo for chamado
CallConvCdecl
,CallConvThiscall
,CallConvStdcall
ouCallConvFastcall
, oCallKind
será tratado comounmanaged cdecl
,unmanaged thiscall
,unmanaged stdcall
ouunmanaged fastcall
, respectivamente, sem nenhuma convençãomodopt
de chamada s no início do tipo de ponteiro de função.If there is one type specified, and that type is namedCallConvCdecl
,CallConvThiscall
,CallConvStdcall
, orCallConvFastcall
, theCallKind
is treated asunmanaged cdecl
,unmanaged thiscall
,unmanaged stdcall
, orunmanaged fastcall
, respectively, with no calling conventionmodopt
s at the start of the function pointer type. - Se vários tipos forem especificados ou o tipo único não for nomeado um dos tipos especialmente chamados acima, o
CallKind
será tratado comounmanaged ext
, com a União dos tipos especificados tratados comomodopt
s no início do tipo de ponteiro de função.If multiple types are specified or the single type is not named one of the specially called out types above, theCallKind
is treated asunmanaged ext
, with the union of the types specified treated asmodopt
s at the start of the function pointer type.
O compilador então examina esse efetivo CallKind
e a modopt
coleção e usa as regras de metadados normais para determinar a Convenção de chamada final do tipo de ponteiro de função.The compiler then looks at this effective CallKind
and modopt
collection and uses normal metadata rules to determine the final calling convention of the function pointer type.
Perguntas em abertoOpen Questions
Detectando suporte de tempo de execução para unmanaged ext
Detecting runtime support for unmanaged ext
https://github.com/dotnet/runtime/issues/38135 controla a adição deste sinalizador.https://github.com/dotnet/runtime/issues/38135 tracks adding this flag. Dependendo dos comentários da revisão, usaremos a propriedade especificada no problema ou usamos a presença de UnmanagedCallersOnlyAttribute
como o sinalizador que determina se os tempos de execução são compatíveis com o unmanaged ext
.Depending on the feedback from review, we will either use the property specified in the issue, or use the presence of UnmanagedCallersOnlyAttribute
as the flag that determines whether the runtimes supports unmanaged ext
.
ConsideraçõesConsiderations
Permitir métodos de instânciaAllow instance methods
A proposta pode ser estendida para dar suporte a métodos de instância aproveitando a EXPLICITTHIS
Convenção de chamada da CLI (chamada instance
em código C#).The proposal could be extended to support instance methods by taking advantage of the EXPLICITTHIS
CLI calling convention (named instance
in C# code). Essa forma de ponteiros de função da CLI coloca o this
parâmetro como um primeiro parâmetro explícito da sintaxe do ponteiro de função.This form of CLI function pointers puts the this
parameter as an explicit first parameter of the function pointer syntax.
unsafe class Instance {
void Use() {
delegate* instance<Instance, string> f = &ToString;
f(this);
}
}
Isso é bom, mas adiciona certa complicação à proposta.This is sound but adds some complication to the proposal. Particularmente porque os ponteiros de função que diferem pela Convenção de chamada instance
e managed
seriam incompatíveis mesmo que ambos os casos sejam usados para invocar métodos gerenciados com a mesma assinatura C#.Particularly because function pointers which differed by the calling convention instance
and managed
would be incompatible even though both cases are used to invoke managed methods with the same C# signature. Além disso, em todos os casos, consideramos que isso seria valioso ter uma solução simples: usar uma static
função local.Also in every case considered where this would be valuable to have there was a simple work around: use a static
local function.
unsafe class Instance {
void Use() {
static string toString(Instance i) => i.ToString();
delegate*<Instance, string> f = &toString;
f(this);
}
}
Não requer segurança na declaraçãoDon't require unsafe at declaration
Em vez de exigir unsafe
em cada uso de um delegate*
, só é necessário no ponto em que um grupo de métodos é convertido em um delegate*
.Instead of requiring unsafe
at every use of a delegate*
, only require it at the point where a method group is converted to a delegate*
. É aí que os problemas de segurança principal entram em cena (sabendo que o assembly recipiente não pode ser descarregado enquanto o valor estiver ativo).This is where the core safety issues come into play (knowing that the containing assembly cannot be unloaded while the value is alive). Exigir unsafe
em outros locais pode ser visto como excessivo.Requiring unsafe
on the other locations can be seen as excessive.
É assim que o design foi originalmente planejado.This is how the design was originally intended. Mas as regras de linguagem resultantes pareciam muito estranhas.But the resulting language rules felt very awkward. É impossível ocultar o fato de que esse é um valor de ponteiro e que ele continua exibindo mesmo sem a unsafe
palavra-chave.It's impossible to hide the fact that this is a pointer value and it kept peeking through even without the unsafe
keyword. Por exemplo, a conversão para object
não pode ser permitida, não pode ser membro de um class
, etc... O design do C# é exigir unsafe
para todos os usos de ponteiro e, portanto, esse design é o seguinte.For example the conversion to object
can't be allowed, it can't be a member of a class
, etc ... The C# design is to require unsafe
for all pointer uses and hence this design follows that.
Os desenvolvedores ainda poderão apresentar um wrapper seguro sobre delegate*
os valores da mesma maneira que fazem para tipos de ponteiros normais hoje em dia.Developers will still be capable of presenting a safe wrapper on top of delegate*
values the same way that they do for normal pointer types today. Considere:Consider:
unsafe struct Action {
delegate*<void> _ptr;
Action(delegate*<void> ptr) => _ptr = ptr;
public void Invoke() => _ptr();
}
Usando delegadosUsing delegates
Em vez de usar um novo elemento Syntax, delegate*
basta usar delegate
tipos existentes com *
o seguinte tipo:Instead of using a new syntax element, delegate*
, simply use existing delegate
types with a *
following the type:
Func<object, object, bool>* ptr = &object.ReferenceEquals;
A manipulação de Convenção de chamada pode ser feita anotando os delegate
tipos com um atributo que especifica um CallingConvention
valor.Handling calling convention can be done by annotating the delegate
types with an attribute that specifies a CallingConvention
value. A falta de um atributo significaria a Convenção de chamada gerenciada.The lack of an attribute would signify the managed calling convention.
Codificar isso no IL é problemático.Encoding this in IL is problematic. O valor subjacente precisa ser representado como um ponteiro, ainda que ele também deva:The underlying value needs to be represented as a pointer yet it also must:
- Ter um tipo exclusivo para permitir sobrecargas com tipos diferentes de ponteiro de função.Have a unique type to allow for overloads with different function pointer types.
- Ser equivalente para fins de OHI em limites de assembly.Be equivalent for OHI purposes across assembly boundaries.
O último ponto é particularmente problemático.The last point is particularly problematic. Isso significa que cada assembly que usa Func<int>*
deve codificar um tipo equivalente em metadados, embora Func<int>*
esteja definido em um assembly, embora não controle.This mean that every assembly which uses Func<int>*
must encode an equivalent type in metadata even though Func<int>*
is defined in an assembly though don't control.
Além disso, qualquer outro tipo que é definido com o nome System.Func<T>
em um assembly que não seja mscorlib deve ser diferente da versão definida em mscorlib.Additionally any other type which is defined with the name System.Func<T>
in an assembly that is not mscorlib must be different than the version defined in mscorlib.
Uma opção que foi explorada estava emitindo tal ponteiro como mod_req(Func<int>) void*
.One option that was explored was emitting such a pointer as mod_req(Func<int>) void*
. Isso não funciona, embora mod_req
não seja possível associar a a TypeSpec
e, portanto, não pode direcionar instanciações genéricas.This doesn't work though as a mod_req
cannot bind to a TypeSpec
and hence cannot target generic instantiations.
Ponteiros de função nomeadosNamed function pointers
A sintaxe do ponteiro de função pode ser complicada, particularmente em casos complexos, como ponteiros de funções aninhadas.The function pointer syntax can be cumbersome, particularly in complex cases like nested function pointers. Em vez de os desenvolvedores digitarem a assinatura sempre que o idioma puder permitir declarações nomeadas de ponteiros de função como é feito com delegate
.Rather than have developers type out the signature every time the language could allow for named declarations of function pointers as is done with delegate
.
func* void Action();
unsafe class NamedExample {
void M(Action a) {
a();
}
}
Parte do problema aqui é que o primitivo de CLI subjacente não tem nomes, portanto, essa seria apenas uma invenção de C# e requer um pouco de trabalho de metadados para habilitar.Part of the problem here is the underlying CLI primitive doesn't have names hence this would be purely a C# invention and require a bit of metadata work to enable. Isso é factível, mas é um grande respeito do trabalho.That is doable but is a significant about of work. Basicamente, ele requer que o C# tenha um complemento para a tabela de tipo def puramente para esses nomes.It essentially requires C# to have a companion to the type def table purely for these names.
Além disso, quando os argumentos dos ponteiros de função nomeados foram examinados, eles poderiam ser aplicados igualmente bem a vários outros cenários.Also when the arguments for named function pointers were examined we found they could apply equally well to a number of other scenarios. Por exemplo, seria conveniente declarar tuplas nomeadas para reduzir a necessidade de digitar a assinatura completa em todos os casos.For example it would be just as convenient to declare named tuples to reduce the need to type out the full signature in all cases.
(int x, int y) Point;
class NamedTupleExample {
void M(Point p) {
Console.WriteLine(p.x);
}
}
Após a discussão, decidimos não permitir a declaração nomeada de delegate*
tipos.After discussion we decided to not allow named declaration of delegate*
types. Se encontrarmos uma necessidade significativa para isso com base nos comentários de uso do cliente, investigaremos uma solução de nomenclatura que funciona para ponteiros de função, tuplas, genéricos, etc... Isso provavelmente será semelhante em forma de outras sugestões, como suporte completo typedef
no idioma.If we find there is significant need for this based on customer usage feedback then we will investigate a naming solution that works for function pointers, tuples, generics, etc ... This is likely to be similar in form to other suggestions like full typedef
support in the language.
Considerações futurasFuture Considerations
delegados estáticosstatic delegates
Isso se refere à proposta para permitir a declaração de delegate
tipos que só podem se referir a static
Membros.This refers to the proposal to allow for the declaration of delegate
types which can only refer to static
members. A vantagem é que essas delegate
instâncias podem ser de alocação gratuita e melhor em cenários de desempenho.The advantage being that such delegate
instances can be allocation free and better in performance sensitive scenarios.
Se o recurso de ponteiro de função for implementado static delegate
, a proposta provavelmente será fechada. A vantagem proposta desse recurso é a natureza livre de alocação.If the function pointer feature is implemented the static delegate
proposal will likely be closed out. The proposed advantage of that feature is the allocation free nature. No entanto, foram encontradas investigações recentes que não podem ser obtidas devido ao descarregamento do assembly.However recent investigations have found that is not possible to achieve due to assembly unloading. Deve haver um identificador forte do static delegate
para o método ao qual se refere para impedir que o assembly seja descarregado de dentro dele.There must be a strong handle from the static delegate
to the method it refers to in order to keep the assembly from being unloaded out from under it.
Para manter cada static delegate
instância, seria necessário alocar um novo identificador que executa um contador para os objetivos da proposta.To maintain every static delegate
instance would be required to allocate a new handle which runs counter to the goals of the proposal. Havia alguns designs em que a alocação poderia ser amortizada para uma única alocação por site de chamada, mas isso era um pouco complexo e não parece que vale a pena compensar.There were some designs where the allocation could be amortized to a single allocation per call-site but that was a bit complex and didn't seem worth the trade off.
Isso significa que os desenvolvedores têm, essencialmente, decidir entre as seguintes compensações:That means developers essentially have to decide between the following trade offs:
- Segurança diante do descarregamento do assembly: isso requer alocações e, portanto,
delegate
já é uma opção suficiente.Safety in the face of assembly unloading: this requires allocations and hencedelegate
is already a sufficient option. - Não há segurança em face de descarregamento de assembly: Use um
delegate*
.No safety in face of assembly unloading: use adelegate*
. Isso pode ser encapsulado em umstruct
para permitir o uso fora de umunsafe
contexto no restante do código.This can be wrapped in astruct
to allow usage outside anunsafe
context in the rest of the code.