¿Método c # con parámetros ilimitados o método con una matriz o lista?

21

Hace poco aprendí que puedes crear algún método con parámetros ilimitados, por ejemplo:

SomeMethod(params int[] numbers);

pero mi pregunta es, ¿cuál es la diferencia entre eso y simplemente crear un método que recibe una lista o una matriz?

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

tal vez tiene algún impacto en el rendimiento? No entiendo completamente ni veo de qué manera preferiría el que tiene parámetros ilimitados.

Una búsqueda rápida en google no ayudó, espero que me puedan ayudar.

Ricardo Sanchez Santos
fuente
Además de mi respuesta a continuación, paramstambién requiere que el tipo de argumento sea una matriz. Si necesita consumir una colección que no sea una matriz, puede tener más sentido proporcionar un no paramsargumento en su lugar.
digitlworld
1
@digitlworld: si este tema le interesa, visite github.com/dotnet/csharplang/issues/179 para discutirlo.
Eric Lippert
Mire la firma y / o implementación de Console.Write.
3Dave

Respuestas:

27

¿Cuál es la diferencia entre eso y simplemente crear un método que reciba una lista o una matriz?

La diferencia entre

void M(params int[] x)

y

void N(int[] x)

es que M puede llamarse así:

M(1, 2, 3)

o así:

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

pero N solo puede llamarse de la segunda manera, no de la primera .

tal vez tiene algún impacto en el rendimiento?

El impacto en el rendimiento es que, ya sea que llame Mde la primera o de la segunda forma, de cualquier forma obtendrá una matriz creada. La creación de una matriz tiene un impacto en el rendimiento porque requiere tiempo y memoria. Recuerde que los impactos en el desempeño deben medirse contra los objetivos de desempeño; es poco probable que el costo de crear una matriz adicional sea el factor determinante que es la diferencia entre el éxito y el fracaso en el mercado.

No entiendo completamente ni veo de qué manera preferiría el que tiene parámetros ilimitados.

Es pura y enteramente una conveniencia para el autor del código que llama al método; es simplemente más corto y fácil de escribir

M(1, 2, 3);

en lugar de escribir

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

Simplemente guarda algunas pulsaciones de teclas en el lado de la persona que llama. Eso es todo.

Algunas preguntas que no hizo pero que tal vez le gustaría saber la respuesta a:

¿Cómo se llama esta característica?

Los métodos que permiten pasar un número variable de argumentos en el lado llamante se denominan variables . Los métodos de parámetros son cómo C # implementa métodos variables.

¿Cómo funciona la resolución de sobrecarga con un método variadic?

Cuando se enfrenta a un problema de resolución de sobrecarga, C # considerará las formas "normal" y "expandida", y la forma "normal" siempre gana si ambas son aplicables. Por ejemplo, considere esto:

void P(params object[] x){}

y tenemos una llamada

P(null);

Hay dos posibilidades aplicables. En forma "normal", llamamos Py pasamos una referencia nula para la matriz. En forma "expandida", llamamos P(new object[] { null }). En este caso, la forma normal gana. Si recibimos una llamada, P(null, null)entonces la forma normal no es aplicable y la forma expandida gana por defecto.

Desafío : supongamos que tenemos var s = new[] { "hello" };y una llamada P(s);. Describa lo que sucede en el sitio de la llamada y por qué. ¡Te sorprenderías!

Reto : supongamos que tenemos ambos void P(object x){}y void P(params object[] x){}. ¿Qué hace P(null)y por qué?

Reto : supongamos que tenemos ambos void M(string x){}y void M(params string[] x){}. ¿Qué hace M(null)y por qué? ¿Cómo difiere esto del caso anterior?

Eric Lippert
fuente
Ch2: P(null)vs. P((object)null)vs. P((object[])null)- ¿Dónde puedo encontrar una explicación para esta diferencia? Parece que nulltenía algún tipo especial , que se convierte en matriz en lugar de objeto ( P(null)), pero encuentra la conversión a cadena o matriz de cadenas ambigua ( M(null)) ... lo que se siente muy extraño, esperaría que sea ambigua en ambos casos o seleccione la versión de un solo argumento (que no lo hace). Pero supongo que se trata más de paramsser de alguna manera genérico , como la referencia universal ( &&) en las plantillas de C ++ y, por lo tanto, una mejor coincidencia (en Ch2, no en Ch3).
Firda
@firda: puede encontrar la explicación en la especificación de C #. La razón de la extrañeza es: nulo es convertible en objeto, cadena, objeto [] y cadena []. Entonces, la pregunta que se plantea para sobrecargar la resolución es: ¿qué conversión es la mejor ? La regla de control en este caso es específica, es mejor que general . ¿Cómo sabemos qué tipo es el más específico? La regla es: todas las jirafas son animales pero no todos los animales son jirafas, por lo tanto, la jirafa es más específica que el animal. Todos object[]son object, pero no todos objectson object[], por object[]lo tanto, es más específico.
Eric Lippert
@firda: Ahora sabes por qué las cadenas son ambiguas. NO es cierto que "todos lo string[]son string". De hecho, NO string[]son stringy NO stringson string[], por stringlo que no es más específico o más general que string[], y obtenemos un error de ambigüedad cuando se nos pide elegir.
Eric Lippert
Todos object[]son object... object[]es más específico, por lo que P(null)va para la matriz más específica, thx, eso es lo que me faltaba. Encontré algunas reglas aquí: docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
firda
5

Acabo de hacer un pequeño prototipo. La respuesta parece ser que paramses simplemente azúcar sintáctica para pasar en una matriz. Eso no es realmente una sorpresa. Creé dos versiones del mismo método, donde la única diferencia es la palabra clave "params". La IL generada para ambos fue idéntica, excepto que System.ParamArrayAttributese aplicó a la paramsversión.

Además, la IL generada en el sitio de la llamada también fue la misma entre que llamé al método con una declaración manual new int[]y llamé al método solo usando los paramsargumentos.

Entonces, la respuesta parece ser "conveniencia". No parece haber ninguna diferencia en el rendimiento. También puede llamar a una paramsfunción con una matriz, por lo que tampoco es terriblemente sorprendente. Todo se reduce a si es más fácil para el consumidor de su método llamarlo con cualquier número de parámetros (p someMethod(1, 2, 3). Ej. ) Que tener que crear siempre una colección primero (p someMethod(new List<int>() { 1, 2, 3 } ). Ej .).

digitlworld
fuente
4

La función de parámetros ilimitados ofrece los siguientes beneficios en muchos escenarios:

  1. Bajo acoplamiento
  2. Reutilización mejorada
  3. Mejor rendimiento general de la aplicación.

Aquí hay un ejemplo donde la opción de parámetros ilimitados es una gran opción

Tenga en cuenta que debe crearse una aplicación para enviar correos electrónicos.

La función que envía el correo electrónico debe poder manejar valores únicos o múltiples para los campos 'Para', 'CC' y 'BCC'.

Si los tipos de parámetros se arreglan para ser matrices o listas para todos los campos (Para, CC, BCC), entonces la función de llamada se verá obligada a lidiar con toda la complejidad de definir 3 matrices o listas para llamar a la función del remitente del correo electrónico .

Incluso si la persona que llama desea enviar un correo electrónico a una sola dirección, la función del remitente del correo electrónico obligará a la persona que llama a definir y enviar 3 matrices diferentes como parámetros.

Si la función del remitente del correo electrónico adopta el enfoque de parámetros ilimitados, entonces la función de la persona que llama no necesita ocuparse de toda la complejidad.

El enfoque de parámetros ilimitados contribuye a mejorar el rendimiento de la aplicación en tiempo de ejecución al evitar la creación de matrices o listas donde sea innecesario.

Gopinath
fuente
2

Desde una perspectiva de estilo sin rendimiento , la paramspalabra clave es realmente agradable cuando se desea enviar una lista opcional de parámetros.

Personalmente, usaría paramscuando mi código fuera algo así

SomeMethod('Option1', 'Option17');

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

Lo bueno de esto es que puedo usar este método por todas partes sin tener que crear una matriz o lista cada vez.

Yo usaría arrayo listcuando sé que siempre pasaré esta función a un conjunto de datos que ya están juntos como

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

Veo el beneficio de paramsla flexibilidad que agrega. Puede ser un impacto relativamente menor en su código, pero sigue siendo una buena herramienta para tener.

Zumo de oliva
fuente
1

La convención de convocatoria es diferente. Por ejemplo ...

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

En el primer caso, puede pasar tantas entradas como parámetros separados al método. En el segundo, necesitaría crear una instancia de una lista y pasarla.

JP Alioto
fuente