Implementación de métodos y parámetros de clase
Un método es un bloque de código que contiene una serie de instrucciones. Un programa hace que las instrucciones se ejecuten llamando al método y especificando los argumentos de método necesarios. En C#, todas las instrucciones ejecutadas se realizan en el contexto de un método .
Firmas de método
Los métodos se declaran en una clase, registro o estructura especificando:
- Un nivel de acceso opcional, como
publicoprivate. El valor predeterminado esprivate. - Modificadores opcionales como
abstractosealed. - Valor devuelto o
voidsi el método no tiene ninguno. - Nombre del método.
- Cualquier parámetro de método. Los parámetros de método se incluyen entre paréntesis y están separados por comas. Los paréntesis vacíos indican que el método no requiere parámetros.
Estas partes funcionan juntas para formar la firma del método.
Importante
Un tipo de valor devuelto de un método no forma parte de la firma del método con el fin de sobrecargar el método. Sin embargo, forma parte de la firma del método al determinar la compatibilidad entre un delegado y el método al que apunta.
En el ejemplo siguiente se define una clase denominada Motorcycle que contiene cinco métodos:
namespace MotorCycleExample
{
abstract class Motorcycle
{
// Anyone can call this.
public void StartEngine() {/* Method statements here */ }
// Only derived classes can call this.
protected void AddGas(int gallons) { /* Method statements here */ }
// Derived classes can override the base class implementation.
public virtual int Drive(int miles, int speed) { /* Method statements here */ return 1; }
// Derived classes can override the base class implementation.
public virtual int Drive(TimeSpan time, int speed) { /* Method statements here */ return 0; }
// Derived classes must implement this.
public abstract double GetTopSpeed();
}
}
La clase Motorcycle incluye un método sobrecargado, Drive. Los dos métodos Drive tienen el mismo nombre, pero tipos de parámetros diferentes.
Invocación de método
Los métodos pueden ser de instancia o estáticos . Debe crear una instancia de un objeto para invocar un método de instancia en esa instancia; un método de instancia funciona en esa instancia y sus datos. Para invocar un método estático, haga referencia al nombre del tipo al que pertenece el método; Los métodos estáticos no funcionan en datos de instancia. Si se intenta llamar a un método estático a través de una instancia de objeto, se genera un error del compilador.
Llamar a un método es como acceder a un campo. Después del nombre del objeto (si llama a un método de instancia) o el nombre de tipo (si llama a un método estático), agregue un punto, el nombre del método y paréntesis. Los argumentos se enumeran entre paréntesis y están separados por comas.
La definición del método especifica los nombres y tipos de cualquier parámetro que sea necesario. Cuando un llamador invoca el método , proporciona valores concretos, denominados argumentos, para cada parámetro. Los argumentos deben ser compatibles con el tipo de parámetro, pero el nombre del argumento, si se usa uno en el código de llamada, no tiene que ser el mismo que el parámetro denominado definido en el método . En el ejemplo siguiente, el método Square incluye un único parámetro de tipo int denominado i. La primera llamada al método pasa el método Square una variable de tipo int denominada num; el segundo, una constante numérica; y la tercera expresión.
public static class SquareExample
{
public static void Main()
{
// Call with an int variable.
int num = 4;
int productA = Square(num);
// Call with an integer literal.
int productB = Square(12);
// Call with an expression that evaluates to int.
int productC = Square(productA * 3);
}
static int Square(int i)
{
// Store input argument in a local variable.
int input = i;
return input * input;
}
}
Parámetros de valor y referencia
Los tipos de C# son tipos de valor o tipos de referencia. De forma predeterminada, los tipos de valor y los tipos de referencia se pasan por valor a un método .
Parámetros de tipo de valor
Cuando se pasa un tipo de valor a un método por valor, se pasa una copia del objeto en lugar del propio objeto al método . Por lo tanto, los cambios realizados en el objeto del método llamado no tienen ningún efecto en el objeto original cuando el control vuelve al autor de la llamada.
En el ejemplo siguiente se pasa un tipo de valor a un método por valor y el método llamado intenta cambiar el valor del tipo de valor. Define una variable de tipo int, que es un tipo de valor, inicializa su valor en 20 y lo pasa a un método denominado ModifyValue que cambia el valor de la variable a 30. Sin embargo, cuando el método devuelve , el valor de la variable permanece sin cambios.
public static class ByValueExample
{
public static void Main()
{
var value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(value);
Console.WriteLine("Back in Main, value = {0}", value);
}
static void ModifyValue(int i)
{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 20
Cuando un objeto de un tipo de referencia se pasa a un método por valor, se pasa una referencia al objeto por valor. Es decir, el método recibe no el propio objeto, sino un argumento que indica la ubicación del objeto. Si cambia un miembro del objeto mediante esta referencia, el cambio se refleja en el objeto cuando el control vuelve al método que realiza la llamada. Sin embargo, reemplazar el objeto pasado al método no tiene ningún efecto en el objeto original cuando el control vuelve al autor de la llamada.
En el ejemplo siguiente se define una clase (que es un tipo de referencia) denominada SampleRefType. Crea una instancia de un objeto SampleRefType, asigna 44 a su campo value y pasa el objeto al método ModifyObject. Este ejemplo hace esencialmente lo mismo que el ejemplo anterior (pasa un argumento por valor a un método). Sin embargo, el resultado es diferente porque se usa un tipo de referencia en lugar de un tipo de valor. La modificación realizada en ModifyObject al campo obj.value también cambia el campo value del argumento, rt. Cuando el método Main muestra el valor de rt vemos que se ha actualizado a 33, como se muestra en la salida del ejemplo.
public class SampleRefType
{
public int value;
}
public static class ByRefTypeExample
{
public static void Main()
{
var rt = new SampleRefType { value = 44 };
Console.WriteLine("In Main, rt.value = {0}", rt.value);
ModifyObject(rt);
Console.WriteLine("Back in Main, rt.value = {0}", rt.value);
}
static void ModifyObject(SampleRefType obj)
{
obj.value = 33;
Console.WriteLine("In ModifyObject, obj.value = {0}", obj.value);
}
}
// The example displays the following output:
// In Main, rt.value = 44
// In ModifyObject, obj.value = 33
// Back in Main, rt.value = 33
Parámetros de tipo de referencia
Se pasa un parámetro por referencia cuando desea cambiar el valor de un argumento en un método y desea reflejar ese cambio cuando el control vuelve al método de llamada. Para pasar un parámetro por referencia, use la palabra clave ref o out. También puede pasar un valor por referencia para evitar la copia, pero evitar modificaciones mediante la palabra clave in.
El ejemplo siguiente es idéntico al anterior, excepto que el valor se pasa por referencia al método ModifyValue. Cuando el valor del parámetro se modifica en el método ModifyValue, el cambio en el valor se refleja cuando el control vuelve al autor de la llamada.
public static class ByRefExample
{
public static void Main()
{
var value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(ref value);
Console.WriteLine("Back in Main, value = {0}", value);
}
private static void ModifyValue(ref int i)
{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 30
Un patrón común que usa los parámetros ref implica intercambiar los valores de las variables. Se pasan dos variables a un método por referencia y el método intercambia su contenido. En el ejemplo siguiente se intercambian valores enteros.
public static class RefSwapExample
{
static void Main()
{
int i = 2, j = 3;
Console.WriteLine("i = {0} j = {1}", i, j);
Swap(ref i, ref j);
Console.WriteLine("i = {0} j = {1}", i, j);
}
static void Swap(ref int x, ref int y) =>
(y, x) = (x, y);
}
// The example displays the following output:
// i = 2 j = 3
// i = 3 j = 2
Pasar un parámetro de tipo de referencia permite cambiar el valor de la propia referencia, en lugar del valor de sus elementos o campos individuales.
Colecciones de parámetros
A veces, el requisito de especificar el número exacto de argumentos para el método es restrictivo. Mediante el uso de la palabra clave params para indicar que un parámetro es una colección de parámetros, se permite llamar al método con un número variable de argumentos. El parámetro etiquetado con la palabra clave params debe ser un tipo de colección y debe ser el último parámetro de la lista de parámetros del método.
A continuación, un autor de llamada puede invocar el método de cualquiera de las cuatro maneras para el parámetro params:
- Al pasar una colección del tipo adecuado que contiene el número deseado de elementos. En el ejemplo se usa una expresión de colección para que el compilador cree un tipo de colección adecuado.
- Al pasar una lista separada por comas de argumentos individuales del tipo adecuado al método . El compilador crea el tipo de colección adecuado.
- Pasando
null. - Al no proporcionar un argumento a la colección de parámetros.
En el ejemplo siguiente se define un método denominado GetVowels que devuelve todas las vocales de una colección de parámetros. El método Main ilustra las cuatro formas de invocar el método. Los llamadores no son necesarios para proporcionar ningún argumento para los parámetros que incluyan el modificador params. En ese caso, el parámetro es una colección vacía.
static class ParamsExample
{
static void Main()
{
string fromArray = GetVowels(["apple", "banana", "pear"]);
Console.WriteLine($"Vowels from collection expression: '{fromArray}'");
string fromMultipleArguments = GetVowels("apple", "banana", "pear");
Console.WriteLine($"Vowels from multiple arguments: '{fromMultipleArguments}'");
string fromNull = GetVowels(null);
Console.WriteLine($"Vowels from null: '{fromNull}'");
string fromNoValue = GetVowels();
Console.WriteLine($"Vowels from no value: '{fromNoValue}'");
}
static string GetVowels(params IEnumerable<string>? input)
{
if (input == null || !input.Any())
{
return string.Empty;
}
char[] vowels = ['A', 'E', 'I', 'O', 'U'];
return string.Concat(
input.SelectMany(
word => word.Where(letter => vowels.Contains(char.ToUpper(letter)))));
}
}
// The example displays the following output:
// Vowels from array: 'aeaaaea'
// Vowels from multiple arguments: 'aeaaaea'
// Vowels from null: ''
// Vowels from no value: ''
Nota
Antes de C# 13, el modificador params solo se puede usar con una matriz unidimensional.
Valores devueltos de métodos
Los métodos pueden devolver un valor al autor de la llamada. Si el tipo devuelto (el tipo enumerado antes del nombre del método) no es void, el método puede devolver el valor mediante la palabra clave return. Una instrucción con la palabra clave return seguida de una variable, constante o expresión que coincide con el tipo de valor devuelto devuelve ese valor al llamador del método. Se requieren métodos con un tipo de valor devuelto novoid para usar la palabra clave return para devolver un valor. La palabra clave return también detiene la ejecución del método .
Si el tipo de valor devuelto es void, una instrucción return sin un valor sigue siendo útil para detener la ejecución del método. Sin la palabra clave return, el método deja de ejecutarse cuando llega al final del bloque de código.
Por ejemplo, estos dos métodos usan la palabra clave return para devolver enteros:
class SimpleMath
{
public int AddTwoNumbers(int number1, int number2) =>
number1 + number2;
public int SquareANumber(int number) =>
number * number;
}
En este ejemplo se usan miembros con forma de expresión para asignar el valor devuelto de los métodos. Esta sintaxis es una abreviatura de los métodos que usan una sola instrucción (expresión) para calcular el valor devuelto.
También puede elegir definir los métodos con un cuerpo de instrucción y una instrucción return:
class SimpleMathExtnsion
{
public int DivideTwoNumbers(int number1, int number2)
{
return number1 / number2;
}
}
Para usar un valor devuelto desde un método, el método de llamada puede usar la propia llamada al método en cualquier lugar donde un valor del mismo tipo sería suficiente. También puede asignar el valor devuelto a una variable. Por ejemplo, los tres ejemplos de código siguientes logran el mismo objetivo:
int result = obj.AddTwoNumbers(1, 2);
result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);
result = obj.SquareANumber(obj.AddTwoNumbers(1, 2));
// The result is 9.
Console.WriteLine(result);
result = obj2.DivideTwoNumbers(6,2);
// The result is 3.
Console.WriteLine(result);
A veces, quiere que el método devuelva más de un solo valor. Use tipos de tupla y literales de tupla para devolver varios valores. El tipo de tupla define los tipos de datos de los elementos de la tupla. Los literales de tupla proporcionan los valores reales de la tupla devuelta. En el ejemplo siguiente, (string, string, string, int) define el tipo de tupla devuelto por el método GetPersonalInfo. La expresión (per.FirstName, per.MiddleName, per.LastName, per.Age) es el literal de tupla; El método devuelve el primer, medio y nombre de familia, junto con la edad, de un objeto PersonInfo.
public (string, string, string, int) GetPersonalInfo(string id)
{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}
A continuación, el autor de la llamada puede consumir la tupla devuelta mediante el código siguiente:
var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.Item1} {person.Item3}: age = {person.Item4}");
Los nombres también se pueden asignar a los elementos de tupla en la definición de tipo de tupla. En el ejemplo siguiente se muestra una versión alternativa del método GetPersonalInfo que usa elementos con nombre:
public (string FName, string MName, string LName, int Age) GetPersonalInfo(string id)
{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}
La llamada anterior al método GetPersonalInfo se puede modificar de la siguiente manera:
var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.FName} {person.LName}: age = {person.Age}");
Si un método toma una matriz como parámetro y modifica el valor de los elementos individuales, no es necesario que el método devuelva la matriz. C# pasa todos los tipos de referencia por valor y el valor de una referencia de matriz es el puntero a la matriz.
Miembros con forma de expresión
Es habitual tener definiciones de método que devuelvan inmediatamente con el resultado de una expresión o que tengan una sola instrucción como cuerpo del método. Hay un acceso directo de sintaxis para definir estos métodos mediante =>:
public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public void Print() => Console.WriteLine(First + " " + Last);
// Works with operators, properties, and indexers too.
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);
Si el método devuelve void o es un método asincrónico, el cuerpo del método debe ser una expresión de instrucción (igual que con lambdas). En el caso de las propiedades y los indexadores, deben ser de solo lectura y no se usa la palabra clave de descriptor de acceso get.