Parâmetros do 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 os tipos de referência (class), uma cópia da referência é passada para o método. Os modificadores de parâmetros permitem que você passe argumentos por referência. Os conceitos a seguir ajudam a entender essas distinções e como usar os modificadores de parâmetro:

  • Pass by value significa passar uma cópia da variável para o método.
  • Passar por referência significa passar 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 seus dados 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 by value para um método. O método não tem acesso ao struct original no método de chamada e, portanto, não pode alterá-lo de forma alguma. O método pode alterar apenas 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 para um método, o método recebe uma cópia da referência para a instância de classe. Ambas as variáveis se referem 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 da instância, o método de chamada também verá essas alterações, pois faz referência à mesma instância.

A saída do exemplo a seguir ilustra a diferença. O método ClassTaker altera o willIChange valor do campo porque o método usa o endereço no parâmetro para localizar o campo especificado da instância de classe. O willIChange campo 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 é perdida quando a chamada para StructTaker é 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 você passa um valor tipo por valor:
    • Se o método atribui o parâmetro para se referir a um objeto diferente, essas alterações não são visíveis do chamador.
    • Se o método modifica o estado do objeto referido pelo parâmetro, essas alterações não são visíveis do chamador.
  • Quando você passa um tipo de referência por valor:
    • Se o método atribui o parâmetro para se referir a um objeto diferente, essas alterações não são visíveis do chamador.
    • Se o método modifica o estado do objeto referido pelo parâmetro, essas alterações são visíveis do chamador.
  • Quando você passa um tipo de valor por referência:
    • Se o método atribui o parâmetro para se referir a um objeto diferente, essas alterações não são visíveis do chamador.
    • Se o método modifica o estado do objeto referido pelo parâmetro, essas alterações são visíveis do chamador.
  • Quando você passa um tipo de referência por referência:
    • Se o método atribui o parâmetro para se referir a um objeto diferente, essas alterações são visíveis do chamador.
    • Se o método modifica o estado do objeto referido pelo parâmetro, essas alterações são visíveis do chamador.

Passar um tipo de referência por referência permite que o método chamado substitua o objeto ao qual o parâmetro de referência se refere 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), também alterará o local de armazenamento ao qual o chamador se refere. O exemplo a seguir passa uma instância de um tipo de referência como um ref parâmetro.

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 dos 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. Passar parâmetros por referência com segurança requer 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 limita o acesso a uma expressão ou variável. O compilador usa dois escopos: safe-context e ref-safe-context.

  • O contexto seguro define o escopo onde qualquer expressão pode ser acessada com segurança.
  • O ref-safe-context define o escopo onde uma referência a qualquer expressão pode ser acessada ou modificada com segurança.

Informalmente, você pode pensar nesses escopos como o mecanismo para garantir que seu código nunca acesse ou modifique uma referência que não é mais válida. Uma referência é válida desde que se refira a um objeto ou struct válido. O contexto seguro define quando uma variável pode ser atribuída ou reatribuída. O ref-safe-context define quando uma variável pode ser atribuída ref ouref reatribuída. A atribuição atribui uma variável a um novo valor; A atribuição ref atribui a variável para 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 para fazê-lo.
  • 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.
  • readonly ref: 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 para in parâmetros.

Os membros de uma classe não podem ter assinaturas que diferem apenas por ref, ref readonly, in, ou out. Um erro de compilador ocorre se a única diferença entre dois membros de um tipo é que um deles tem um ref parâmetro e o outro tem um outref readonly, ou in parâmetro. No entanto, os métodos podem ser sobrecarregados quando um método tem um ref, ref readonly, in, ou out parâmetro e o outro tem um parâmetro que é passado por valor, como mostrado no exemplo a seguir. Em outras situações que exigem correspondência de assinatura, como ocultar ou substituir, in, , ref, ref readonlye 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 ref parâmetro deve incluir o ref modificador.
  • Um argumento para um out parâmetro deve incluir o out modificador.
  • Um argumento para um in parâmetro pode, opcionalmente, incluir o in modificador. Se o ref modificador for usado no argumento, o compilador emitirá um aviso.
  • Um argumento para um ref readonly parâmetro deve incluir os in modificadores ou ref modificadores, mas não ambos. Se nenhum dos modificadores estiver 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 escrever o valor do argumento. O argumento deve ser passado por referência.
  • in significa que o método lê, mas não pode escrever o valor do argumento. O argumento será passado por referência ou através de uma variável temporária.

As propriedades não são variáveis. São métodos e não podem ser passados para ref parâmetros. Não é possível usar os modificadores de parâmetro anteriores nos seguintes tipos de métodos:

  • Métodos assíncronos, que você define usando o modificador assíncrono.
  • Métodos iteradores, que incluem um retorno de rendimento ou yield break uma instrução.

Os métodos de extensão também têm restrições no uso dessas palavras-chave de argumento:

  • A out palavra-chave não pode ser usada no primeiro argumento de um método de extensão.
  • A ref palavra-chave não pode ser usada no primeiro argumento de um método de extensão quando o argumento não é um struct, ou um tipo genérico não restrito a ser um struct.
  • As ref readonly palavras-chave e in não podem ser usadas a menos que o primeiro argumento seja um struct.
  • As ref readonly palavras-chave e in não podem ser usadas em nenhum tipo genérico, mesmo quando restritas a ser uma estrutura.

ref modificador de parâmetros

Para usar um ref parâmetro, tanto a definição do método quanto o método de chamada devem usar explicitamente a ref palavra-chave, conforme mostrado no exemplo a seguir. (Só que o método de chamada pode ser omitido 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 ref parâmetro deve ser inicializado antes de ser passado.

out modificador de parâmetros

Para usar um out parâmetro, tanto a definição do método quanto o método de chamada devem usar explicitamente a out palavra-chave. Por exemplo:

int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod);     // value is now 44

void OutArgExample(out int number)
{
    number = 44;
}

As variáveis passadas como out argumentos 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 retorne.

Os métodos Deconstruct declaram seus parâmetros com o out modificador 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 passá-la como um out argumento. Você também pode declarar a out variável na lista de argumentos da chamada de método, em vez de em uma declaração de variável separada. out As declarações de variáveis produzem um 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 number variável 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 digitada implicitamente.

ref readonly modificador

O ref readonly modificador deve estar presente na declaração do método. Um modificador no site de chamada é opcional. O in modificador ou ref pode ser usado. O ref readonly modificador não é válido no site de chamada. O modificador usado no site de chamada pode ajudar a descrever as características do argumento. Você só pode 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. Não é possível adicionar nenhum modificador se o argumento não for uma variável, mas uma expressão. Os exemplos a seguir mostram essas condições. O método a seguir usa o ref readonly modificador para indicar que uma estrutura grande deve ser passada por referência por motivos de desempenho:

public static void ForceByRef(ref readonly OptionStruct thing)
{
    // elided
}

Você pode chamar o método usando o ref modificador 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 in modificadores ou ref , então 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 readonly variável, você deve usar o in modificador. O compilador emite um erro se você usar o ref modificador em vez disso.

O ref readonly modificador indica que o método espera que o argumento seja uma variável em vez de uma expressão que não é 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âmetros

O in modificador é necessário na declaração do método, mas desnecessário no local 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 in modificador 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 sua API exigir que o argumento seja passado por referência, escolha o ref readonly modificador em vez do in modificador.

Os métodos que são definidos usando in parâmetros potencialmente ganham otimização de desempenho. Alguns struct argumentos de tipo 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 in parâmetros 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. Passar esses argumentos por referência evita a cópia (potencialmente) cara. Você adiciona explicitamente o in modificador no site de chamada para garantir que o argumento seja passado por referência, não por valor. O uso in explícito tem os dois efeitos a seguir:

  • A especificação in no local de chamada força o compilador a selecionar um método definido com um parâmetro correspondente in . Caso contrário, quando dois métodos diferem apenas na presença de in, a sobrecarga por valor é uma melhor correspondência.
  • Ao especificar in, você declara sua intenção de passar um argumento por referência. O argumento usado com in deve representar um local que pode ser diretamente referido. Aplicam-se as mesmas regras out gerais e ref argumentos: não é possível usar constantes, propriedades ordinárias ou outras expressões que produzam valores. Caso contrário, omitir in no site de chamada informa ao compilador que não há problema em 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 in argumentos:
    • Uma variável temporária permite constantes de tempo de compilação como in parâmetros.
    • Uma variável temporária permite propriedades ou outras expressões para in parâmetros.
    • Uma variável temporária permite argumentos onde há uma conversão implícita do tipo de argumento para o tipo de parâmetro.

Em todas as instâncias anteriores, o compilador cria uma variável temporária que armazena o valor da constante, propriedade ou 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 mudam 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 final.

Nota

O código anterior usa int como o tipo de argumento para simplicidade. Como int não é maior do que uma referência na maioria das máquinas modernas, não há nenhum benefício em passar um único int como uma referência somente leitura.

params modificador

Nenhum outro parâmetro é permitido após a params palavra-chave em uma declaração de método, e apenas uma params palavra-chave é permitida em uma declaração de método.

Se o tipo declarado do parâmetro não for uma matriz unidimensional, ocorrerá o params erro do compilador CS0225 .

Quando você chama um método com um params parâmetro, você pode passar:

  • Uma lista separada por vírgulas de argumentos do tipo dos elementos da matriz.
  • Uma matriz de argumentos do tipo especificado.
  • Sem argumentos. Se você não enviar argumentos, o params tamanho da lista será zero.

O exemplo a seguir demonstra várias maneiras pelas quais os argumentos podem ser enviados para um params parâmetro.

public class MyClass
{
    public static void UseParams(params int[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    public static void UseParams2(params object[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        // You can send a comma-separated list of arguments of the
        // specified type.
        UseParams(1, 2, 3, 4);
        UseParams2(1, 'a', "test");

        // A params parameter accepts zero or more arguments.
        // The following calling statement displays only a blank line.
        UseParams2();

        // 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 };
        UseParams(myIntArray);

        object[] myObjArray = { 2, 'b', "test", "again" };
        UseParams2(myObjArray);

        // The following call causes a compiler error because the object
        // array cannot be converted into an integer array.
        //UseParams(myObjArray);

        // The following call does not cause an error, but the entire
        // integer array becomes the first element of the params array.
        UseParams2(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 da linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso do C#.