Compartilhar via


Parâmetros e modificadores de método

Por padrão, o C# passa argumentos para funções por valor. Essa abordagem passa uma cópia da variável para o método. Para tipos de valor (struct), o método obtém uma cópia do valor. Para tipos de referência (class), o método obtém uma cópia da referência. Você pode usar modificadores de parâmetro para passar argumentos por referência.

Como um struct é um tipo de valor, passar um struct por valor para um método envia uma cópia do argumento para o método. O método funciona com essa cópia. O método não pode acessar o struct original no método de chamada e não pode alterá-lo. O método pode alterar somente a cópia.

Uma instância de classe é um tipo de referência, não um tipo de valor. Quando você passa um tipo de referência por valor para um método, o método obtém uma cópia da referência para a instância. 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.

A linguagem C# faz referência a documentos da versão mais recentemente lançada da linguagem C#. Ele também contém a documentação inicial para funcionalidades em pré-visualizações públicas para o próximo lançamento do idioma.

A documentação identifica qualquer recurso introduzido pela primeira vez nas três últimas versões do idioma ou nas versões prévias públicas atuais.

Dica

Para descobrir quando um recurso foi introduzido pela primeira vez em C#, consulte o artigo sobre o histórico de versão da linguagem C#.

Passar por valor e passar por referência

Todos os exemplos nesta seção usam os dois record tipos a seguir para ilustrar as diferenças entre class tipos e struct tipos:

public record struct Point(int X, int Y);
// This doesn't use a primary constructor because the properties implemented for `record` types are 
// readonly in record class types. That would prevent the mutations necessary for this example.
public record class Point3D
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }
}

A saída do exemplo a seguir ilustra a diferença entre passar um tipo de struct por valor e passar um tipo de classe por valor. Ambos os Mutate métodos alteram os valores de propriedade de seu argumento. Quando o parâmetro é um struct tipo, essas alterações afetam uma cópia dos dados do argumento. Quando o parâmetro é um class tipo, essas alterações afetam a instância referenciada pelo argumento:

public class PassTypesByValue
{
    public static void Mutate(Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }
    public static void Mutate(Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;
        pt.Z = 42;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }

    public static void TestPassTypesByValue()
    {
        Console.WriteLine("===== Value Types =====");

        var ptStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{ptStruct}");

        Mutate(ptStruct);

        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{ptStruct}");

        Console.WriteLine("===== Reference Types =====");

        var ptClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{ptClass}");

        Mutate(ptClass);
        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{ptClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Mutate:           Point { X = 1, Y = 2 }
        //         Exit Mutate:            Point { X = 19, Y = 23 }
        // After called Mutate:            Point { X = 1, Y = 2 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Mutate:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
        // After called Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
    }
}

O ref modificador é uma maneira de passar argumentos por referência aos métodos. O código a seguir replica o exemplo anterior, mas passa parâmetros por referência. As modificações feitas no struct tipo ficam visíveis no método de chamada quando o struct é passado por referência. Não há nenhuma alteração semântica quando um tipo de referência é passado por referência:

public class PassTypesByReference
{
    public static void Mutate(ref Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }
    public static void Mutate(ref Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Mutate)}:\t\t{pt}");
        pt.X = 19;
        pt.Y = 23;
        pt.Z = 42;

        Console.WriteLine($"\tExit {nameof(Mutate)}:\t\t{pt}");
    }

    public static void TestPassTypesByReference()
    {
        Console.WriteLine("===== Value Types =====");

        var pStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{pStruct}");

        Mutate(ref pStruct);

        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{pStruct}");

        Console.WriteLine("===== Reference Types =====");

        var pClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{pClass}");

        Mutate(ref pClass);
        Console.WriteLine($"After called {nameof(Mutate)}:\t\t{pClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Mutate:           Point { X = 1, Y = 2 }
        //         Exit Mutate:            Point { X = 19, Y = 23 }
        // After called Mutate:            Point { X = 19, Y = 23 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Mutate:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
        // After called Mutate:            Point3D { X = 19, Y = 23, Z = 42 }
    }
}

Os exemplos anteriores modificaram as propriedades de um parâmetro. Um método também pode reatribuir um parâmetro a um novo valor. A reatribuição se comporta de forma diferente para struct e tipos de classe quando passados por valor ou por referência. O exemplo a seguir mostra como os tipos de struct e os tipos de classe se comportam quando os parâmetros passados por valor são reatribuídos:

public class PassByValueReassignment
{
    public static void Reassign(Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point { X = 13, Y = 29 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void Reassign(Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point3D { X = 13, Y = 29, Z = -42 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void TestPassByValueReassignment()
    {
        Console.WriteLine("===== Value Types =====");

        var ptStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{ptStruct}");

        Reassign(ptStruct);

        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptStruct}");

        Console.WriteLine("===== Reference Types =====");

        var ptClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{ptClass}");

        Reassign(ptClass);
        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Reassign:         Point { X = 1, Y = 2 }
        //         Exit Reassign:          Point { X = 13, Y = 29 }
        // After called Reassign:          Point { X = 1, Y = 2 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Reassign:         Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Reassign:          Point3D { X = 13, Y = 29, Z = -42 }
        // After called Reassign:          Point3D { X = 1, Y = 2, Z = 3 }
    }
}

O exemplo anterior mostra que, quando você reatribui um parâmetro a um novo valor, essa alteração não é visível do método de chamada, independentemente de o tipo ser um tipo de valor ou um tipo de referência. O exemplo a seguir mostra o comportamento ao reatribuir um parâmetro que o método recebeu por referência:

public class PassByReferenceReassignment
{
    public static void Reassign(ref Point pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point { X = 13, Y = 29 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void Reassign(ref Point3D pt)
    {
        Console.WriteLine($"\tEnter {nameof(Reassign)}:\t\t{pt}");
        pt = new Point3D { X = 13, Y = 29, Z = -42 };

        Console.WriteLine($"\tExit {nameof(Reassign)}:\t\t{pt}");
    }

    public static void TestPassByReferenceReassignment()
    {
        Console.WriteLine("===== Value Types =====");

        var ptStruct = new Point { X = 1, Y = 2 };
        Console.WriteLine($"After initialization:\t\t{ptStruct}");

        Reassign(ref ptStruct);

        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptStruct}");

        Console.WriteLine("===== Reference Types =====");

        var ptClass = new Point3D { X = 1, Y = 2, Z = 3 };

        Console.WriteLine($"After initialization:\t\t{ptClass}");

        Reassign(ref ptClass);
        Console.WriteLine($"After called {nameof(Reassign)}:\t\t{ptClass}");

        // Output:
        // ===== Value Types =====
        // After initialization:           Point { X = 1, Y = 2 }
        //         Enter Reassign:         Point { X = 1, Y = 2 }
        //         Exit Reassign:          Point { X = 13, Y = 29 }
        // After called Reassign:          Point { X = 13, Y = 29 }
        // ===== Reference Types =====
        // After initialization:           Point3D { X = 1, Y = 2, Z = 3 }
        //         Enter Reassign:         Point3D { X = 1, Y = 2, Z = 3 }
        //         Exit Reassign:          Point3D { X = 13, Y = 29, Z = -42 }
        // After called Reassign:          Point3D { X = 13, Y = 29, Z = -42 }
    }
}

O exemplo anterior mostra como reatribuir o valor de um parâmetro passado por referência é visível no contexto de chamada.

Contexto seguro de referências e valores

Os métodos podem armazenar os valores de parâmetros em campos. Quando você passa parâmetros por valor, geralmente é seguro. O método copia valores e os tipos de referência podem ser acessados quando o método os armazena 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

Para passar argumentos por referência em vez de por valor, use um dos seguintes modificadores em uma declaração de parâmetro:

  • ref: inicialize o argumento antes de chamar o método. O método pode atribuir um novo valor ao parâmetro, mas não é necessário.
  • out: o método de chamada não precisa inicializar o argumento antes de chamar o método. O método deve atribuir um valor ao parâmetro.
  • ref readonly: inicialize o argumento antes de chamar o método. O método não pode atribuir um novo valor ao parâmetro.
  • in: inicialize o argumento 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âmetros in.

Um parâmetro passado por referência é uma variável de referência. Ele não tem seu próprio valor. Em vez disso, ele se refere a uma variável diferente chamada seu referencial. Você pode reatribuir variáveis de referência, o que altera o referencial.

Os membros de uma classe não podem ter assinaturas que diferem apenas por ref, ref readonly,in ou out. Ocorre um erro do compilador se a única diferença entre dois membros de um tipo é que um membro tem um ref parâmetro e o outro membro tem um outparâmetro ou in umref readonly. No entanto, você pode sobrecarregar métodos quando um método tem um refparâmetro, ref readonlyinum parâmetro 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, refref 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 modificador ref.
  • Um argumento para um parâmetro out deve incluir o modificador out.
  • Um argumento para um parâmetro in pode incluir opcionalmente o modificador in. Se o modificador ref for usado no argumento, o compilador emitirá um aviso.
  • Um argumento para um parâmetro ref readonly deve incluir os modificadores in ou ref, 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 é 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 membros da 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 e in não podem ser usadas, a menos que o primeiro argumento seja um struct.
  • As palavras-chave ref readonly e in 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. Você não pode usar propriedades como argumentos para ref parâmetros.

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

Você deve inicializar um argumento antes de passá-lo para um ref parâmetro.

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;
}

Você não precisa inicializar variáveis passadas como out argumentos antes da chamada do método. No entanto, o método chamado deve atribuir um valor antes de retornar.

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

A declaração do método requer o ref readonly modificador. Um modificador no site de chamada é opcional. Você pode usar o modificador ou o in modificador ref . O modificador ref readonly 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ó 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. A 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 ou in o ref modificador. 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.

var largeStruct = new LargeStruct { Value1 = 42, Value2 = 3.14, Value3 = "Hello" };

// Using 'in' avoids copying the large struct and prevents modification
ProcessLargeStruct(in largeStruct);
Console.WriteLine($"Original value unchanged: {largeStruct.Value1}");

// Without 'in', the struct would be copied (less efficient for large structs)
ProcessLargeStructByValue(largeStruct);
Console.WriteLine($"Original value still unchanged: {largeStruct.Value1}");

void ProcessLargeStruct(in LargeStruct data)
{
    // Can read the values
    Console.WriteLine($"Processing: {data.Value1}, {data.Value2}, {data.Value3}");
    
    // Uncomment the following line to see error CS8331
    // data.Value1 = 99; // Compilation error: cannot assign to 'in' parameter
}

void ProcessLargeStructByValue(LargeStruct data)
{
    // This method receives a copy of the struct
    Console.WriteLine($"Processing copy: {data.Value1}, {data.Value2}, {data.Value3}");
    
    // Modifying the copy doesn't affect the original
    data.Value1 = 99;
}

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 a API exigir que o argumento seja passado por referência, escolha o modificador ref readonly em vez do modificador in.

Você pode obter otimização de desempenho definindo métodos com in parâmetros. Alguns struct argumentos de tipo podem ser grandes em tamanho e, quando você chama métodos em loops apertados ou caminhos de código críticos, o custo de copiar essas estruturas é substancial. Declare in parâmetros para especificar que você pode passar argumentos com segurança por referência 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 correspondente in. Caso contrário, quando dois métodos diferem apenas na presença de in, 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 com in deve representar um local ao qual se possa fazer referência diretamente. As mesmas regras gerais out e ref argumentos se aplicam: você não pode usar constantes, propriedades comuns ou outras expressões que produzem valores. Caso contrário, omitir in 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 argumentos in:
    • 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.

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 somente leitura.

Modificador params

O parâmetro com a params palavra-chave deve ser o último parâmetro na declaração de método. Você só pode usar uma params palavra-chave em uma declaração de método.

Você deve declarar o params parâmetro como um tipo de coleção. Os tipos de coleção reconhecidos incluem:

Antes do C# 13, você deve usar uma matriz unidimensional para o parâmetro.

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 de enviar argumentos para um params parâmetro.

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[]
*/

A resolução de sobrecarga pode causar ambiguidade quando o argumento de um params parâmetro é um tipo de coleção. O tipo de coleção do argumento deve ser conversível para o tipo de coleção do parâmetro. Quando diferentes sobrecargas fornecem conversões melhores para esse parâmetro, esse método pode ser melhor. No entanto, se o argumento para o params parâmetro for elementos discretos ou ausentes, todas as sobrecargas com diferentes params tipos de parâmetro serão iguais para esse parâmetro.

Para obter mais informações, consulte a seção em Listas de argumentos na Especificação da Linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.