UpToDate C#: Funciones

C#12

(Actualizado a 10 de Agosto de 2024)

Cualquier novedad interesante para tratar con funciones.

Información de contexto

Es posible obtener información de contexto sobre la llamada a una función.

public void WriteMessage(string messageBefore,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
{// C# 5
    Console.WriteLine("parameter before: " + messageBefore);
    Console.WriteLine("caller: " + memberName);
    Console.WriteLine("source file path: " + sourceFilePath);
    Console.WriteLine("source line number: " + sourceLineNumber);
    Console.WriteLine("parameter after: " + messageAfter);
}

/* El resultado es:
parameter: Example
member name: Main
source file path: C:\repos\UpToDate\src\UpToDate\Program.cs
source line number: 28
*/

Existe también la opción de indicar un parámetro que el compilador reemplaza con la representación de texto de otro argumento, en este caso si no se cumple la validación (condition = false) en el mensaje se pondrá como texto el nombre de la variable que se usó como parámetro validateSomethingInteresting

public void Validate(bool condition, [CallerArgumentExpression("condition")] string? message = null)
{
    if (!condition)
    {
        throw new InvalidOperationException($"Argument failed validation: <{message}>");
    }
}

bool validateSomethingInteresting = false;
try
{
    functions.Validate(validateSomethingInteresting);
}
catch(Exception e)
{
    Console.WriteLine(e.Message); // Argument failed validation: <validateSomethingInteresting>
}

Propiedades

Las propiedades aceptan inicializadores, código que asigna un valor inicial, este código tiene que ser estático (una constante, una función estática,…)

// Llamando a una función estática
public string Surname { get; set; } = ReturnDiscard(5).ToString();
// O a una constante
public string Surname { get; set; } = "Surname".ToLower();

Declaración y uso de parámetros out

Es posible devolver valores por referencia

public ref string ReturnByReference(int number, string[] names)
{
    return ref names[number]; // return the storage location, not the value
}

// Uso
string[] dummies = new string[] { "John", "Doe" };
ref var refString = ref functions.ReturnByReference(1, dummies);
refString = "surname"; // Esto modifica el elemento 1 del parámetro dummies
System.Console.WriteLine(dummies[1]);

/* El resultado es:
surname
*/

También es posible que este no sea el comportamiento deseado y no se quiera poder modificar el valor retornado, se puede usar readonly para esto:

public ref readonly string ReturnByReferenceReadonly(int number, string[] names) // 'readonly' es la palabra clave
{
    return ref names[number];
}

// Uso
string[] dummies = new string[] { "John", "Doe" };
var refString = functions.ReturnByReferenceReadonly(1, dummies);
refString = "surname"; //Esto no modificará el elemento 1
System.Console.WriteLine(dummies[1]);

/* El resultado es:
Doe
*/

Funciones locales

Es posible crear funciones locales, es decir funciones que existen dentro de otras.

// Función local auto-implementada
public int LocalFunctionsAuto(int a, int b)
{
    int Sum(int a1, int b1) => a1 + b1;
    return Sum(a, b);
}
// Función local
public int LocalFunctionsRegular(int a, int b)
{
    int Sum(int a1, int b1) {
        return a1 + b1;
    }
    return Sum(a, b);
}

Parámetro in

Un parámetro in no se puede modificar por la función que lo usa, por lo tanto tiene que ser inicializado antes de ser enviado a la función, digamos que es un readonly para la función que lo usa.

public void InParameter(in int o)// Because of 'in' o can not be modified
{
    //o = 3; Esto provocaría un error CS8331
    Console.WriteLine(o);
}
// Uso
functions.InParameter(66);

/* El resultado es:
66
*/

Devolviendo múltiples valores

Se pueden devolver varios valores desde una función, la función llamadora recibirá un objeto de la clase Tuple.

public (int, double) Average(int[] values)
{
    return (values.Length, values.Average()); 
}

// Uso
var items = Average(new int[] { 1, 2 });
Console.WriteLine($"For {items.Item1} elements the average is {items.Item2}");

/* El resultado es:
For 2 elements the average is 1.5
*/

Deconstructores

Se puede deconstruir un objeto y devolver una tupla de elementos, para esto hace falta una función especial llamada Deconstruct si se quiere por ejemplo extraer dos valores esta función aceptará dos parámetros.

// Esta función establecerá los valores a devolver
public void Deconstruct(out string firstName, out string lastName)
{// It can be also an extension method
    firstName = FirstName.ToLower();
    lastName = LastName.ToLower();
}

// Uso
DataStructures dataStructures2 = new DataStructures("John", "Doe");
var (f, l) = dataStructures2;
System.Console.WriteLine(f); // => FirstName.ToLower()
System.Console.WriteLine(l); // => LastName.ToLower()

/* El resultado es:
john
doe
*/

De igual forma se pueden crear métodos Deconstruct con más parámetros que asignarán más valores a variables.

[C#10] No hace falta que se asignen todos los parámetros, por ejemplo:

int x = 0;
(x, int y) = point;

Private protected

Nuevo modificador de accesibilidad que permite que un método sea llamado tanto desde la propia clase como desde una clase que herede de esta siempre y cuando estén en el mismo ensamblado.

private protected string Example()
{// Este método solo puede ser llamado por un padre en el mismo ensamblado
    return "Private protected";
}

Funciones locales estáticas

Ahora es posible crear funciones locales estáticas, esto impide hacer uso de las variables del ámbito padre al contrario que las funciones no estáticas, de lo contrario se generaría un error.

public int Father()
{
	int a = 1;
	int b = 2;
	return Son(a, b);

	static int Son(int c, int d) => c + d;
}

Inicializador de módulos (C# 9)

Ahora se pueden inicializar módulos a través de funciones marcadas con el atributo [ModuleInitializer] estas funciones se ejecutarán antes que cualquier otro método en el módulo, por ejemplo este código será el primero que se ejecute antes que cualquier otro.

[ModuleInitializer]
public static void Start()
{
    System.Console.WriteLine("I'm the first one");
}

UpToDate C#: Funciones