método c # com parâmetros ilimitados ou método com uma matriz ou lista?

21

Eu aprendi recentemente que você pode criar algum método com parâmetros ilimitados, por exemplo:

SomeMethod(params int[] numbers);

mas minha pergunta é: qual é a diferença entre isso e apenas criar um método que recebe uma lista ou uma matriz?

SomeMethod(int[] numbers);
SomeMethod(List<int> numbers);

talvez tenha algum impacto no desempenho? Não entendo completamente nem vejo de que maneira você preferiria aquele com parâmetros ilimitados.

Uma pesquisa rápida no google não ajudou, espero que você possa me ajudar.

Ricardo Sanchez Santos
fonte
Além da minha resposta abaixo, paramstambém requer que o tipo do argumento seja uma matriz. Se você precisar consumir uma coleção que não seja uma matriz, pode fazer mais sentido fornecer um não paramsargumento.
digitlworld 22/01
11
@digitlworld: se este assunto lhe interessa, consulte github.com/dotnet/csharplang/issues/179 para discussão.
Eric Lippert
Veja a assinatura e / ou implementação de Console.Write.
3Dave

Respostas:

27

qual é a diferença entre isso e apenas criar um método que recebe uma lista ou uma matriz?

A diferença entre

void M(params int[] x)

e

void N(int[] x)

é que M pode ser chamado assim:

M(1, 2, 3)

ou assim:

M(new int[] { 1, 2, 3 });

mas N só pode ser chamado da segunda maneira, não da primeira .

talvez tenha algum impacto no desempenho?

O impacto no desempenho é que, se você liga Mpela primeira ou pela segunda maneira, de qualquer maneira você cria uma matriz. A criação de uma matriz afeta o desempenho, pois leva tempo e memória. Lembre-se de que os impactos no desempenho devem ser medidos em relação às metas de desempenho; é improvável que o custo de criação de uma matriz extra seja o fator determinante que é a diferença entre sucesso e fracasso no mercado.

Não entendo completamente nem vejo de que maneira você preferiria aquele com parâmetros ilimitados.

É pura e inteiramente uma conveniência para o autor do código que está chamando o método; é simplesmente mais curto e fácil de escrever

M(1, 2, 3);

em vez de escrever

M(new int[] { 1, 2, 3 });

Apenas salva algumas teclas pressionadas no lado do chamador. Isso é tudo.

Algumas perguntas que você não fez, mas talvez queira saber a resposta para:

Como é chamado esse recurso?

Os métodos que permitem que um número variável de argumentos sejam passados ​​no lado do chamador são chamados variadic . Os métodos de parâmetros são como o C # implementa métodos variados.

Como a resolução de sobrecarga funciona com um método variável?

Quando confrontado com um problema de resolução de sobrecarga, o C # considerará os formulários "normal" e "expandido", e o formulário "normal" sempre vence se ambos forem aplicáveis. Por exemplo, considere isso:

void P(params object[] x){}

e nós temos uma ligação

P(null);

Existem duas possibilidades aplicáveis. No formato "normal", chamamos Pe passamos uma referência nula para a matriz. Na forma "expandida", chamamos P(new object[] { null }). Nesse caso, a forma normal vence. Se recebemos uma chamada P(null, null), o formulário normal é inaplicável e o formulário expandido vence por padrão.

Desafio : suponha que tenhamos var s = new[] { "hello" };uma ligação P(s);. Descreva o que acontece no site da chamada e por quê. Você pode se surpreender!

Desafio : Suponha que temos ambos void P(object x){}e void P(params object[] x){}. O que P(null)faz e por quê?

Desafio : Suponha que temos ambos void M(string x){}e void M(params string[] x){}. O que M(null)faz e por quê? Como isso difere do caso anterior?

Eric Lippert
fonte
Ch2: P(null)vs. P((object)null)vs. P((object[])null)- onde posso encontrar uma explicação para essa diferença? Parece que nullteve algum tipo especial , que se converte em matriz em vez de objeto ( P(null)), mas considera a conversão em sequência ou matriz de seqüências ambíguas ( M(null)) ... que parece muito estranha, seria de esperar ambígua nos dois casos ou escolher a versão de argumento único (o que não existe). Mas acho que é mais sobre paramsser de alguma forma genérico , como referência universal ( &&) nos modelos C ++ e, portanto, uma melhor correspondência (no Ch2, não no Ch3).
firda 28/01
@firda: Você pode encontrar a explicação na especificação C #. O motivo da estranheza é: null é convertível em objeto, string, objeto [] e string []. Portanto, a questão colocada para sobrecarregar a resolução é: qual conversão é a melhor ? A regra de controle neste caso é específica é melhor que geral . Como podemos dizer que tipo é o mais específico? A regra é: todos os girafas são animais, mas nem todos os animais são girafas; portanto, a girafa é mais específica que o animal. Todos object[]são object, mas nem todos objectsão object[], portanto, object[]é mais específico.
Eric Lippert
@firda: Agora você sabe por que as cordas são ambíguas. NÃO é verdade que "todos string[]são string". De fato, NO string[]são stringe NÃO stringsão string[], portanto, stringnão é mais específico ou mais geral que string[], e obtemos um erro de ambiguidade quando solicitados a escolher.
Eric Lippert 28/01
Todos object[]são object... object[]é mais específico, então P(null)vale para a matriz mais específica, thx, era isso que eu estava perdendo. Encontrei algumas regras aqui: docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
firda em 28/01
5

Só fiz um pequeno protótipo. A resposta parece ser paramssimplesmente açúcar sintático para passar em uma matriz. Isso não é realmente uma surpresa. Criei duas versões do mesmo método, onde a única diferença é a palavra-chave "params". A IL gerada para ambos era idêntica, exceto que a System.ParamArrayAttributefoi aplicada à paramsversão.

Além disso, a IL gerada no site da chamada também foi a mesma entre eu chamar o método com um declarado manualmente new int[]e chamar o método apenas usando os paramsargumentos.

Então, a resposta parece ser "conveniência". Não parece haver nenhuma diferença no desempenho. Você também pode chamar uma paramsfunção com uma matriz, o que também não é muito surpreendente. Tudo se resume a se é mais fácil para o consumidor do seu método chamá-lo com qualquer número de parâmetros (por exemplo someMethod(1, 2, 3)) do que sempre ter que criar uma coleção primeiro (por exemplo someMethod(new List<int>() { 1, 2, 3 } )).

digitlworld
fonte
4

O recurso de parâmetros ilimitados oferece os seguintes benefícios em muitos cenários:

  1. Acoplamento solto
  2. Reutilização aprimorada
  3. Melhor desempenho geral do aplicativo

Aqui está um exemplo em que a opção de parâmetros ilimitados é uma ótima opção

Considere que um aplicativo para enviar e-mails precisa ser criado.

A função que envia o email deve poder manipular valores únicos ou múltiplos para os campos 'Para', 'CC' e 'BCC'.

Se os tipos de parâmetros forem fixos em matrizes ou listas para todos os campos (Para, CC, BCC), a função de chamada será forçada a lidar com toda a complexidade da definição de 3 matrizes ou listas para chamar a função de remetente de email .

Mesmo que o chamador deseje enviar um email para apenas um endereço, a função remetente do email forçará o chamador a definir e enviar 3 matrizes diferentes como parâmetros.

Se a função de remetente de email adotar a abordagem de parâmetros ilimitados, a função de chamada não precisará lidar com toda a complexidade.

A abordagem de parâmetros ilimitados contribui para melhorar o desempenho do aplicativo em tempo de execução, evitando a criação de matrizes ou listas sempre que desnecessário.

Gopinath
fonte
2

De uma perspectiva de estilo sem desempenho , a paramspalavra-chave é realmente boa quando você deseja enviar uma lista opcional de parâmetros.

Pessoalmente, eu usaria paramsquando meu código fosse algo como

SomeMethod('Option1', 'Option17');

void SomeMethod(params string[] options)
{
    foreach(var option in options)
    {
        switch(option): ...
    }
}

A parte boa disso é que eu posso usar esse método em todo o lugar sem precisar criar uma matriz ou lista todas as vezes.

Eu usaria arrayou listquando sei que sempre transmitirei a essa função um conjunto de dados que já estão reunidos como

List<User> users = GetUsersFromDB();
SomeMethod(users);

Eu vejo o benefício da paramsflexibilidade que ele adiciona. Pode ser um impacto relativamente pequeno no seu código, mas ainda é uma boa ferramenta.

OliveJuice
fonte
1

A convenção de chamada é diferente. Por exemplo ...

public class Test
{
    public void Method1( params int[] nums )
    {
        nums.ToList().ForEach( n => Console.WriteLine(n) );
    }

    public void Method2( List<int> nums )
    {
        nums.ForEach( n  => Console.WriteLine(n) );
    }   
}

void Main()
{   
    var t = new Test();
    t.Method1( 1, 2, 3, 4 );
    t.Method2( new List<int>() { 1, 2, 3, 4 } );
}

No primeiro caso, você pode passar tantas entradas quanto parâmetros separados para o método. No segundo, você precisaria instanciar uma lista e passá-la.

JP Alioto
fonte