Parâmetros de método
Por padrão, os argumentos em C# são passados para funções por valor. Isso significa que uma cópia da variável é passada para o método. Para tipos de valor (struct
), uma cópia do valor é passada para o método. Para tipos de referência (class
), uma cópia da referência é passada para o método. Modificadores de parâmetro permitem que você passe argumentos por referência. Os conceitos a seguir ajudam você a entender essas distinções e como usar os modificadores de parâmetro:
- Aprovação por valor significa aprovar uma cópia da variável para o método.
- Aprovação por referência significa aprovar o acesso à variável para o método.
- Uma variável de um tipo de referência contém uma referência aos seus dados.
- Uma variável de um tipo de valor contém seu valor diretamente.
Como um struct é um tipo de valor, o método recebe e opera em uma cópia do argumento struct quando você passa um struct por valor para um método. O método não tem acesso ao struct original no método de chamada e, portanto, não é possível alterá-lo de forma alguma. O método pode alterar somente a cópia.
Uma instância de classe é um tipo de referência e não um tipo de valor. Quando um tipo de referência é passado por valor a um método, esse método receberá uma cópia da referência para a instância da classe. Ambas as variáveis referem-se ao mesmo objeto. O parâmetro é uma cópia da referência. O método chamado não pode reatribuir a instância no método de chamada. No entanto, o método chamado pode usar a cópia da referência para acessar os membros da instância. Se o método chamado alterar um membro de instância, o método de chamada também verá essas alterações, pois faz referência à mesma instância.
O resultado do exemplo a seguir ilustra a diferença. O método ClassTaker
altera o valor do campo willIChange
porque o método usa o endereço no parâmetro para localizar o campo especificado da instância de classe. O campo willIChange
do struct no método de chamada não muda de chamada StructTaker
porque o valor do argumento é uma cópia do struct em si, não uma cópia de seu endereço. StructTaker
altera a cópia e a cópia será perdida quando a chamada para StructTaker
for concluída.
class TheClass
{
public string? willIChange;
}
struct TheStruct
{
public string willIChange;
}
class TestClassAndStruct
{
static void ClassTaker(TheClass c)
{
c.willIChange = "Changed";
}
static void StructTaker(TheStruct s)
{
s.willIChange = "Changed";
}
public static void Main()
{
TheClass testClass = new TheClass();
TheStruct testStruct = new TheStruct();
testClass.willIChange = "Not Changed";
testStruct.willIChange = "Not Changed";
ClassTaker(testClass);
StructTaker(testStruct);
Console.WriteLine("Class field = {0}", testClass.willIChange);
Console.WriteLine("Struct field = {0}", testStruct.willIChange);
}
}
/* Output:
Class field = Changed
Struct field = Not Changed
*/
Combinações de tipo de parâmetro e modo de argumento
Como um argumento é passado e se é um tipo de referência ou tipo de valor controla quais modificações feitas no argumento são visíveis do chamador:
- Quando se aprova um tipo de valor por valor:
- Se o método atribuir o parâmetro para se referir a um objeto diferente, essas alterações não serão visíveis pelo chamador.
- Se o método modificar o estado do objeto referenciado pelo parâmetro, essas alterações não serão visíveis pelo chamador.
- Quando se aprova um tipo de referência por valor:
- Se o método atribuir o parâmetro para se referir a um objeto diferente, essas alterações não serão visíveis pelo chamador.
- Se o método modificar o estado do objeto referenciado pelo parâmetro, essas alterações ficarão visíveis pelo chamador.
- Quando se aprova um tipo de valor por referência:
- Se o método atribuir o parâmetro para se referir a um objeto diferente usando
ref =
, essas alterações não serão visíveis no chamador. - Se o método modificar o estado do objeto referenciado pelo parâmetro, essas alterações ficarão visíveis pelo chamador.
- Se o método atribuir o parâmetro para se referir a um objeto diferente usando
- Quando se aprova um tipo de referência por referência:
- Se o método atribuir o parâmetro para se referir a um objeto diferente, essas alterações ficarão visíveis pelo chamador.
- Se o método modificar o estado do objeto referenciado pelo parâmetro, essas alterações ficarão visíveis pelo chamador.
Passar um tipo de referência por referência permite que o método chamado substitua o objeto ao qual se refere o parâmetro de referência no chamador. O local de armazenamento do objeto é passado para o método como o valor do parâmetro de referência. Se você alterar o valor no local de armazenamento do parâmetro (para apontar para um novo objeto), irá alterar também o local de armazenamento ao qual se refere o chamador. O exemplo a seguir passa uma instância de um tipo de referência como um parâmetro ref
.
class Product
{
public Product(string name, int newID)
{
ItemName = name;
ItemID = newID;
}
public string ItemName { get; set; }
public int ItemID { get; set; }
}
private static void ChangeByReference(ref Product itemRef)
{
// Change the address that is stored in the itemRef parameter.
itemRef = new Product("Stapler", 12345);
}
private static void ModifyProductsByReference()
{
// Declare an instance of Product and display its initial values.
Product item = new Product("Fasteners", 54321);
System.Console.WriteLine("Original values in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
// Pass the product instance to ChangeByReference.
ChangeByReference(ref item);
System.Console.WriteLine("Calling method. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
}
// This method displays the following output:
// Original values in Main. Name: Fasteners, ID: 54321
// Calling method. Name: Stapler, ID: 12345
Contexto seguro de referências e valores
Os métodos podem armazenar os valores de parâmetros em campos. Quando os parâmetros são passados por valor, isso geralmente é seguro. Os valores são copiados e os tipos de referência podem ser acessados quando armazenados em um campo. Para aprovar parâmetros por referência com segurança, é necessário que o compilador defina quando é seguro atribuir uma referência a uma nova variável. Para cada expressão, o compilador define um contexto seguro que vincula o acesso a uma expressão ou variável. O compilador usa dois escopos: safe-context e ref-safe-context.
- O safe-context seguro define o escopo em que qualquer expressão pode ser acessada com segurança.
- O ref-safe-context define o escopo em que uma referência a qualquer expressão pode ser acessada ou modificada com segurança.
Informalmente, você pode considerar esses escopos como o mecanismo para garantir que seu código nunca acesse ou modifique uma referência que não seja mais válida. Uma referência é válida desde que se refira a um objeto ou struct válido. O safe-context define quando uma variável pode ser atribuída ou reatribuída. O ref-safe-context define quando uma variável pode ser ref atribuída novamente ou ref reatribuída. A atribuição concede a uma variável um novo valor. A atribuição de referência indica para a variável que ela deve se referir a um local de armazenamento diferente.
Parâmetros de referência
Você aplica um dos seguintes modificadores a uma declaração de parâmetro para passar argumentos por referência em vez de por valor:
ref
: o argumento deve ser inicializado antes de chamar o método. O método pode atribuir um novo valor ao parâmetro, mas não é necessário fazer isso.out
: o método de chamada não é necessário para inicializar o argumento antes de chamar o método. O método deve atribuir um valor ao parâmetro.ref readonly
: o argumento deve ser inicializado antes de chamar o método. O método não pode atribuir um novo valor ao parâmetro.in
: o argumento deve ser inicializado antes de chamar o método. O método não pode atribuir um novo valor ao parâmetro. O compilador pode criar uma variável temporária para manter uma cópia do argumento em parâmetrosin
.
Os membros de uma classe não podem ter assinaturas que diferem apenas por ref
, ref readonly
,in
ou out
. Ocorrerá um erro do compilador se a única diferença entre dois membros de um tipo for que um deles tem um parâmetro ref
e o outro tem um parâmetro out
, ref readonly
ou in
. No entanto, os métodos podem ser sobrecarregados quando um método tem um parâmetro ref
, ref readonly
, in
ou out
e o outro tem um parâmetro que é passado por valor, conforme mostrado no exemplo a seguir. Em outras situações que exigem correspondência de assinatura, como ocultar ou substituir, in
, ref
ref readonly
e out
fazem parte da assinatura e não correspondem entre si.
Quando um parâmetro tem um dos modificadores anteriores, o argumento correspondente pode ter um modificador compatível:
- Um argumento para um parâmetro
ref
deve incluir o modificadorref
. - Um argumento para um parâmetro
out
deve incluir o modificadorout
. - Um argumento para um parâmetro
in
pode incluir opcionalmente o modificadorin
. Se o modificadorref
for usado no argumento, o compilador emitirá um aviso. - Um argumento para um parâmetro
ref readonly
deve incluir os modificadoresin
ouref
, mas não ambos. Se nenhum modificador for incluído, o compilador emitirá um aviso.
Quando você usa esses modificadores, eles descrevem como o argumento é usado:
ref
significa que o método pode ler ou gravar o valor do argumento.out
significa que o método define o valor do argumento.ref readonly
significa que o método lê, mas não pode gravar o valor do argumento. O argumento deve ser passado por referência.in
significa que o método lê, mas não pode gravar o valor do argumento. O argumento será passado por referência ou por meio de uma variável temporária.
Você não pode usar os modificadores de parâmetro anteriores nos seguintes tipos de métodos:
- Métodos assíncronos, que você define usando o modificador async.
- Métodos de iterador, que incluem uma instrução yield return ou
yield break
.
Os métodos de extensão também têm restrições sobre o uso dessas palavras-chave de argumento:
- A palavra-chave
out
não pode ser usada no primeiro argumento de um método de extensão. - A palavra-chave
ref
não pode ser usada no primeiro argumento de um método de extensão quando o argumento não éstruct
, ou um tipo genérico não restrito a ser um struct. - As palavras-chave
ref readonly
ein
não podem ser usadas, a menos que o primeiro argumento seja umstruct
. - As palavras-chave
ref readonly
ein
não podem ser usadas em nenhum tipo genérico, mesmo quando restritas a serem um struct.
As propriedades não são variáveis. São métodos. Propriedades não podem ser argumentos para os parâmetros ref
.
ref
modificador de parâmetro
Para usar um parâmetro ref
, a definição do método e o método de chamada devem usar explicitamente a palavra-chave ref
, como mostrado no exemplo a seguir. (Exceto que o método de chamada pode omitir ref
ao fazer uma chamada COM.)
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
Um argumento que é passado para um parâmetro ref
deve ser inicializado antes de ser passado.
out
modificador de parâmetro
Para usar um parâmetro out
, a definição do método e o método de chamada devem usar explicitamente a palavra-chave out
. Por exemplo:
int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod); // value is now 44
void OutArgExample(out int number)
{
number = 44;
}
Variáveis passadas como argumentos out
não precisam ser inicializadas antes de serem passadas em uma chamada de método. No entanto, o método chamado é necessário para atribuir um valor antes que o método seja retornado.
Os métodos de desconstrução declaram seus parâmetros com o modificador out
para retornar vários valores. Outros métodos podem retornar tuplas de valor para vários valores de retorno.
Você pode declarar uma variável em uma instrução separada antes de transmiti-la como um argumento out
. Você também pode declarar a variável out
na lista de argumentos da chamada de método em vez de declará-la em uma declaração de variável separada. As declarações de variável out
produzem código mais compacto e legível e também impedem que você atribua inadvertidamente um valor à variável antes da chamada do método. O exemplo a seguir define a variável number
na chamada para o método Int32.TryParse.
string numberAsString = "1640";
if (Int32.TryParse(numberAsString, out int number))
Console.WriteLine($"Converted '{numberAsString}' to {number}");
else
Console.WriteLine($"Unable to convert '{numberAsString}'");
// The example displays the following output:
// Converted '1640' to 1640
Você também pode declarar uma variável local tipada implicitamente.
Modificador ref readonly
O modificador ref readonly
deve estar presente na declaração do método. Um modificador no site de chamada é opcional. O modificador in
ou ref
podem ser usados. O modificador ref readonly
não é válido no site de chamada. Qual modificador você usa no site de chamada pode ajudar a descrever as características do argumento. Você só poderá usar ref
se o argumento for uma variável e for gravável. Você só pode usar in
quando o argumento é uma variável. Pode ser gravável ou somente leitura. Você não poderá adicionar nenhum modificador se o argumento não for uma variável, mas for uma expressão. Os exemplos a seguir mostram essas condições. O método a seguir usa o modificador ref readonly
para indicar que um struct grande deve ser passado por referência por motivos de desempenho:
public static void ForceByRef(ref readonly OptionStruct thing)
{
// elided
}
Você pode chamar o método usando o modificador ref
ou in
. Se você omitir o modificador, o compilador emitirá um aviso. Quando o argumento é uma expressão, não uma variável, você não pode adicionar os modificadores in
ou ref
, portanto, você deve suprimir o aviso:
ForceByRef(in options);
ForceByRef(ref options);
ForceByRef(options); // Warning! variable should be passed with `ref` or `in`
ForceByRef(new OptionStruct()); // Warning, but an expression, so no variable to reference
Se a variável for uma variável readonly
, você deverá usar o modificador in
. O compilador emitirá um erro se você usar o modificador ref
.
O modificador ref readonly
indica que o método espera que o argumento seja uma variável em vez de uma expressão que não seja uma variável. Exemplos de expressões que não são variáveis são constantes, valores de retorno de método e propriedades. Se o argumento não for uma variável, o compilador emitirá um aviso.
in
modificador de parâmetro
O modificador in
é necessário na declaração do método, mas desnecessário no site de chamada.
int readonlyArgument = 44;
InArgExample(readonlyArgument);
Console.WriteLine(readonlyArgument); // value is still 44
void InArgExample(in int number)
{
// Uncomment the following line to see error CS8331
//number = 19;
}
O modificador in
permite que o compilador crie uma variável temporária para o argumento e passe uma referência somente leitura para esse argumento. O compilador sempre cria uma variável temporária quando o argumento deve ser convertido, quando há uma conversão implícita do tipo de argumento ou quando o argumento é um valor que não é uma variável. Por exemplo, quando o argumento é um valor literal ou o valor retornado de um acessador de propriedade. Quando a API exigir que o argumento seja passado por referência, escolha o modificador ref readonly
em vez do modificador in
.
Métodos definidos usando parâmetros in
potencialmente ganham otimização de desempenho. Alguns argumentos de tipo struct
podem ser grandes em tamanho e, quando os métodos são chamados em loops apertados ou caminhos de código críticos, o custo de copiar essas estruturas é substancial. Os métodos declaram parâmetros in
para especificar que os argumentos podem ser passados por referência com segurança porque o método chamado não modifica o estado desse argumento. A passagem desses argumentos por referência evita a cópia (possivelmente) dispendiosa. Você adiciona explicitamente o modificador in
no site de chamada para garantir que o argumento seja passado por referência, e não por valor. O uso explícito de in
tem dois efeitos:
- Especificar
in
no site de chamada força o compilador a selecionar um método definido com um parâmetro correspondentein
. Caso contrário, quando dois métodos diferem apenas na presença dein
, a sobrecarga por valor é uma correspondência melhor. - Ao especificar
in
, você declara sua intenção de passar um argumento por referência. O argumento usado comin
deve representar um local ao qual se possa fazer referência diretamente. As mesmas regras geraisout
eref
argumentos se aplicam: você não pode usar constantes, propriedades comuns ou outras expressões que produzem valores. Caso contrário, omitirin
no site de chamada informa ao compilador que é bom criar uma variável temporária para passar por referência somente leitura para o método. O compilador cria uma variável temporária para superar várias restrições com argumentosin
:- Uma variável temporária permite constantes de tempo de compilação como parâmetros
in
. - Uma variável temporária permite propriedades ou outras expressões para parâmetros
in
. - Uma variável temporária permite argumentos em que há uma conversão implícita do tipo de argumento para o tipo de parâmetro.
- Uma variável temporária permite constantes de tempo de compilação como parâmetros
Em todas as instâncias anteriores, o compilador cria uma variável temporária que armazena o valor da constante, da propriedade ou de outra expressão.
O código a seguir ilustra essas regras:
static void Method(in int argument)
{
// implementation removed
}
Method(5); // OK, temporary variable created.
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // OK, temporary int created with the value 0
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // passed by readonly reference
Method(in i); // passed by readonly reference, explicitly using `in`
Agora, suponha que outro método usando argumentos por valor estava disponível. Os resultados são alterados conforme mostrado no código a seguir:
static void Method(int argument)
{
// implementation removed
}
static void Method(in int argument)
{
// implementation removed
}
Method(5); // Calls overload passed by value
Method(5L); // CS1503: no implicit conversion from long to int
short s = 0;
Method(s); // Calls overload passed by value.
Method(in s); // CS1503: cannot convert from in short to in int
int i = 42;
Method(i); // Calls overload passed by value
Method(in i); // passed by readonly reference, explicitly using `in`
A única chamada de método em que o argumento é passado por referência é a chamada final.
Observação
O código anterior usa int
como o tipo de argumento por questão de simplicidade. Como int
não é maior que uma referência na maioria dos computadores modernos, não há nenhum benefício em passar um único int
como uma referência readonly.
Modificador params
Nenhum outro parâmetro é permitido após a palavra-chave params
em uma declaração de método e apenas uma palavra-chave params
é permitida em uma declaração de método.
O tipo declarado do parâmetro params
deve ser um tipo de coleção. Os tipos de coleção reconhecidos são:
- Um tipo de matriz
T[]
unidimensional, em cujo caso o tipo de elemento éT
. - Um tipo de intervalo:
System.Span<T>
System.ReadOnlySpan<T>
Aqui, o tipo de elemento éT
.
- Um tipo com um método de criação acessível com um tipo de elemento correspondente. O método de criação é identificado usando o mesmo atributo usado para expressões de coleção.
- Um struct ou tipo de classe que implementa System.Collections.Generic.IEnumerable<T> em que:
- O tipo tem um construtor que pode ser invocado sem argumentos e o construtor é pelo menos tão acessível quanto o membro de declaração.
- O tipo tem um método de instância (não uma extensão)
Add
em que:- O método pode ser invocado com um argumento de valor único.
- Se o método for genérico, os argumentos de tipo poderão ser inferidos do argumento.
- O método é pelo menos tão acessível quanto o membro que declara. Aqui, o tipo de elemento é o tipo de iteração do tipo.
- Um tipo de Interface:
Antes do C# 13, o parâmetro deve ser uma matriz unidimensional.
Ao chamar um método com um parâmetro params
, você pode passar:
- Uma lista separada por vírgulas de argumentos do tipo dos elementos da matriz.
- Uma coleção de argumentos do tipo especificado.
- Sem argumentos. Se você não enviar nenhum argumento, o comprimento da lista
params
será zero.
O exemplo a seguir demonstra várias maneiras em que os argumentos podem ser enviados para um parâmetro params
.
public static void ParamsModifierExample(params int[] list)
{
for (int i = 0; i < list.Length; i++)
{
System.Console.Write(list[i] + " ");
}
System.Console.WriteLine();
}
public static void ParamsModifierObjectExample(params object[] list)
{
for (int i = 0; i < list.Length; i++)
{
System.Console.Write(list[i] + " ");
}
System.Console.WriteLine();
}
public static void TryParamsCalls()
{
// You can send a comma-separated list of arguments of the
// specified type.
ParamsModifierExample(1, 2, 3, 4);
ParamsModifierObjectExample(1, 'a', "test");
// A params parameter accepts zero or more arguments.
// The following calling statement displays only a blank line.
ParamsModifierObjectExample();
// An array argument can be passed, as long as the array
// type matches the parameter type of the method being called.
int[] myIntArray = { 5, 6, 7, 8, 9 };
ParamsModifierExample(myIntArray);
object[] myObjArray = { 2, 'b', "test", "again" };
ParamsModifierObjectExample(myObjArray);
// The following call causes a compiler error because the object
// array cannot be converted into an integer array.
//ParamsModifierExample(myObjArray);
// The following call does not cause an error, but the entire
// integer array becomes the first element of the params array.
ParamsModifierObjectExample(myIntArray);
}
/*
Output:
1 2 3 4
1 a test
5 6 7 8 9
2 b test again
System.Int32[]
*/
- Listas de argumentos na Especificação de Linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.