Descartes - Guía de C#Discards - C# Guide

A partir de C# 7.0, C# admite descartes, que son variables de marcador de posición que no se usan deliberadamente en el código de la aplicación.Starting with C# 7.0, C# supports discards, which are placeholder variables that are intentionally unused in application code. Los descartes son equivalentes a variables sin asignar, ya que no tienen un valor.Discards are equivalent to unassigned variables; they don't have a value. Un descarte comunica la intención al compilador y otros usuarios que leen el código: Pretendía ignorar el resultado de una expresión.A discard communicates intent to the compiler and others that read your code: You intended to ignore the result of an expression. Es posible que desee ignorar el resultado de una expresión, uno o varios miembros de una expresión de tupla, un parámetro out de un método o el destino de una expresión de coincidencia de patrones.You may want to ignore the result of an expression, one or more members of a tuple expression, an out parameter to a method, or the target of a pattern matching expression.

Puesto que solo hay una variable de descarte única, es posible que a dicha variable no se le haya asignado almacenamiento.Because there's only a single discard variable, that variable may not even be allocated storage. Los descartes pueden reducir las asignaciones de memoria.Discards can reduce memory allocations. Los descartes aclaran la intención del código.Discards make the intent of your code clear. Mejoran la legibilidad y el mantenimiento.They enhance its readability and maintainability.

Para indicar que una variable es un descarte, se le asigna como nombre el carácter de subrayado (_).You indicate that a variable is a discard by assigning it the underscore (_) as its name. Por ejemplo, la siguiente llamada de método devuelve una tupla en la que el primer y el segundo valor son descartes.For example, the following method call returns a tuple in which the first and second values are discards. area es una variable declarada previamente establecida en el tercer componente devuelto por GetCityInformation:area is a previously declared variable set to the third component returned by GetCityInformation:

(_, _, area) = city.GetCityInformation(cityName);

A partir de C# 9.0, se pueden usar descartes para especificar los parámetros de entrada de una expresión lambda que no se utilizan.Beginning with C# 9.0, you can use discards to specify unused input parameters of a lambda expression. Para más información, consulte sección sobre parámetros de entrada de una expresión lambda en el artículo sobre expresiones lambda.For more information, see the Input parameters of a lambda expression section of the Lambda expressions article.

Cuando _ es un descarte válido, si se intenta recuperar su valor o usarlo en una operación de asignación, se genera el error del compilador CS0301: "El nombre '_' no existe en el contexto actual".When _ is a valid discard, attempting to retrieve its value or use it in an assignment operation generates compiler error CS0301, "The name '_' doesn't exist in the current context". Este error se debe a que no se le ha asignado un valor a _, y es posible que tampoco se le haya asignado una ubicación de almacenamiento.This error is because _ isn't assigned a value, and may not even be assigned a storage location. Si fuese una variable real no se podría descartar más de un valor, como en el ejemplo anterior.If it were an actual variable, you couldn't discard more than one value, as the previous example did.

Deconstrucción de tuplas y objetosTuple and object deconstruction

Los descartes son útiles en el trabajo con tuplas, cuando el código de la aplicación usa algunos elementos de tupla pero omite otros.Discards are useful in working with tuples when your application code uses some tuple elements but ignores others. Por ejemplo, el siguiente método QueryCityDataForYears devuelve una tupla con el nombre de una ciudad, su superficie, un año, la población de la ciudad en ese año, un segundo año y la población de la ciudad en ese segundo año.For example, the following QueryCityDataForYears method returns a tuple with the name of a city, its area, a year, the city's population for that year, a second year, and the city's population for that second year. En el ejemplo se muestra la evolución de la población entre esos dos años.The example shows the change in population between those two years. De los datos disponibles en la tupla, no nos interesa la superficie de la ciudad, y conocemos el nombre de la ciudad y las dos fechas en tiempo de diseño.Of the data available from the tuple, we're unconcerned with the city area, and we know the city name and the two dates at design-time. Como resultado, solo nos interesan los dos valores de población almacenados en la tupla, y podemos controlar los valores restantes como descartes.As a result, we're only interested in the two population values stored in the tuple, and can handle its remaining values as discards.

var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");

static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
    int population1 = 0, population2 = 0;
    double area = 0;

    if (name == "New York City")
    {
        area = 468.48;
        if (year1 == 1960)
        {
            population1 = 7781984;
        }
        if (year2 == 2010)
        {
            population2 = 8175133;
        }
        return (name, area, year1, population1, year2, population2);
    }

    return ("", 0, 0, 0, 0, 0);
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

Para obtener más información sobre la deconstrucción de tuplas con descartes, vea Deconstructing tuples and other types (Deconstruir tuplas y otros tipos).For more information on deconstructing tuples with discards, see Deconstructing tuples and other types.

El método Deconstruct de una clase, estructura o interfaz también permite recuperar y deconstruir un conjunto de datos específico de un objeto.The Deconstruct method of a class, structure, or interface also allows you to retrieve and deconstruct a specific set of data from an object. Puede usar descartes cuando le interese trabajar con un solo subconjunto de los valores deconstruidos.You can use discards when you're interested in working with only a subset of the deconstructed values. En el siguiente ejemplo se deconstruye un objeto Person en cuatro cadenas (el nombre propio, los apellidos, la ciudad y el estado), pero se descartan los apellidos y el estado.The following example deconstructs a Person object into four strings (the first and last names, the city, and the state), but discards the last name and the state.

using System;

namespace Discards
{
    public class Person
    {
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string City { get; set; }
        public string State { get; set; }

        public Person(string fname, string mname, string lname,
                      string cityName, string stateName)
        {
            FirstName = fname;
            MiddleName = mname;
            LastName = lname;
            City = cityName;
            State = stateName;
        }

        // Return the first and last name.
        public void Deconstruct(out string fname, out string lname)
        {
            fname = FirstName;
            lname = LastName;
        }

        public void Deconstruct(out string fname, out string mname, out string lname)
        {
            fname = FirstName;
            mname = MiddleName;
            lname = LastName;
        }

        public void Deconstruct(out string fname, out string lname,
                                out string city, out string state)
        {
            fname = FirstName;
            lname = LastName;
            city = City;
            state = State;
        }
    }
    class Example
    {
        public static void Main()
        {
            var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

            // Deconstruct the person object.
            var (fName, _, city, _) = p;
            Console.WriteLine($"Hello {fName} of {city}!");
            // The example displays the following output:
            //      Hello John of Boston!
        }
    }
}

Para obtener más información sobre la deconstrucción de tipos definidos por el usuario con descartes, vea Deconstructing tuples and other types (Deconstruir tuplas y otros tipos).For more information on deconstructing user-defined types with discards, see Deconstructing tuples and other types.

Coincidencia de patrones con switchPattern matching with switch

El patrón de descarte se puede usar en la coincidencia de patrones con la palabra clave switch.The discard pattern can be used in pattern matching with the switch keyword. Todas las expresiones siempre coinciden con el patrón de descarte.Every expression always matches the discard pattern. (Se puede utilizar con expresiones is.(It can be used with is expressions. Sin embargo, ese uso es poco frecuente porque el descarte se puede quitar sin cambiar su significado).However, that use is rare because the discard can be removed without changing its meaning).

En el ejemplo siguiente se define un método ProvidesFormatInfo que usa instrucciones is para determinar si un objeto proporciona una implementación de IFormatProvider y probar si el objeto es null.The following example defines a ProvidesFormatInfo method that uses is statements to determine whether an object provides an IFormatProvider implementation and tests whether the object is null. También se usa el patrón de descarte para controlar los objetos que no son NULL de cualquier otro tipo.It also uses the discard pattern to handle non-null objects of any other type.

object[] objects = { CultureInfo.CurrentCulture,
                   CultureInfo.CurrentCulture.DateTimeFormat,
                   CultureInfo.CurrentCulture.NumberFormat,
                   new ArgumentException(), null };
foreach (var obj in objects)
    ProvidesFormatInfo(obj);

static void ProvidesFormatInfo(object obj) =>
    Console.WriteLine(obj switch
    {
        IFormatProvider fmt => $"{fmt.GetType()} object",
        null => "A null object reference: Its use could result in a NullReferenceException",
        _ => "Some object type without format information"
    });
// The example displays the following output:
//    System.Globalization.CultureInfo object
//    System.Globalization.DateTimeFormatInfo object
//    System.Globalization.NumberFormatInfo object
//    Some object type without format information
//    A null object reference: Its use could result in a NullReferenceException

Llamadas a métodos con parámetros outCalls to methods with out parameters

Cuando se llama al método Deconstruct para deconstruir un tipo definido por el usuario (una instancia de una clase, estructura o interfaz), puede descartar los valores de argumentos out individuales.When calling the Deconstruct method to deconstruct a user-defined type (an instance of a class, structure, or interface), you can discard the values of individual out arguments. Pero también puede descartar el valor de argumentos out al llamar a cualquier método con un parámetro out.But you can also discard the value of out arguments when calling any method with an out parameter.

En el ejemplo siguiente se llama al método DateTime.TryParse(String, out DateTime) para determinar si la representación de cadena de una fecha es válida en la referencia cultural actual.The following example calls the DateTime.TryParse(String, out DateTime) method to determine whether the string representation of a date is valid in the current culture. Dado que al ejemplo solo le interesa validar la cadena de fecha, y no analizarla para extraer la fecha, el argumento out para el método es un descarte.Because the example is concerned only with validating the date string and not with parsing it to extract the date, the out argument to the method is a discard.

string[] dateStrings = {"05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",
                      "2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
                      "5/01/2018 14:57:32.80 -07:00",
                      "1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
                      "Fri, 15 May 2018 20:10:57 GMT" };
foreach (string dateString in dateStrings)
{
    if (DateTime.TryParse(dateString, out _))
        Console.WriteLine($"'{dateString}': valid");
    else
        Console.WriteLine($"'{dateString}': invalid");
}
// The example displays output like the following:
//       '05/01/2018 14:57:32.8': valid
//       '2018-05-01 14:57:32.8': valid
//       '2018-05-01T14:57:32.8375298-04:00': valid
//       '5/01/2018': valid
//       '5/01/2018 14:57:32.80 -07:00': valid
//       '1 May 2018 2:57:32.8 PM': valid
//       '16-05-2018 1:00:32 PM': invalid
//       'Fri, 15 May 2018 20:10:57 GMT': invalid

Descarte independienteA standalone discard

Puede usar un descarte independiente para indicar cualquier variable que decida omitir.You can use a standalone discard to indicate any variable that you choose to ignore. Un uso típico es usar una asignación para asegurarse de que un argumento no sea NULL.One typical use is to use an assignment to ensure that an argument isn't null. En el código siguiente se usa un descarte para forzar una asignación.The following code uses a discard to force an assignment. El lado derecho de la asignación utiliza el operador de uso combinado de NULL para producir System.ArgumentNullException cuando el argumento es null.The right side of the assignment uses the null coalescing operator to throw an System.ArgumentNullException when the argument is null. El código no necesita el resultado de la asignación, por lo que se descarta.The code doesn't need the result of the assignment, so it's discarded. La expresión fuerza una comprobación nula.The expression forces a null check. El descarte aclara su intención: el resultado de la asignación no es necesario ni se usa.The discard clarifies your intent: the result of the assignment isn't needed or used.

public static void Method(string arg)
{
    _ = arg ?? throw new ArgumentNullException(paramName: nameof(arg), message: "arg can't be null");

    // Do work with arg.
}

En el ejemplo siguiente se usa un descarte independiente para omitir el objeto Task devuelto por una operación asincrónica.The following example uses a standalone discard to ignore the Task object returned by an asynchronous operation. La asignación de la tarea tiene el efecto de suprimir la excepción que se produce en la operación cuando está a punto de completarse.Assigning the task has the effect of suppressing the exception that the operation throws as it is about to complete. Hace que su intención sea clara: Quiere descartar Task y omitir los errores generados a partir de esa operación asincrónica.It makes your intent clear: You want to discard the Task, and ignore any errors generated from that asynchronous operation.

private static async Task ExecuteAsyncMethods()
{
    Console.WriteLine("About to launch a task...");
    _ = Task.Run(() =>
    {
        var iterations = 0;
        for (int ctr = 0; ctr < int.MaxValue; ctr++)
            iterations++;
        Console.WriteLine("Completed looping operation...");
        throw new InvalidOperationException();
    });
    await Task.Delay(5000);
    Console.WriteLine("Exiting after 5 second delay");
}
// The example displays output like the following:
//       About to launch a task...
//       Completed looping operation...
//       Exiting after 5 second delay

Sin asignar la tarea a un descarte, el código siguiente genera una advertencia del compilador:Without assigning the task to a discard, the following code generates a compiler warning:

private static async Task ExecuteAsyncMethods()
{
    Console.WriteLine("About to launch a task...");
    // CS4014: Because this call is not awaited, execution of the current method continues before the call is completed.
    // Consider applying the 'await' operator to the result of the call.
    Task.Run(() =>
    {
        var iterations = 0;
        for (int ctr = 0; ctr < int.MaxValue; ctr++)
            iterations++;
        Console.WriteLine("Completed looping operation...");
        throw new InvalidOperationException();
    });
    await Task.Delay(5000);
    Console.WriteLine("Exiting after 5 second delay");

Nota

Si ejecuta cualquiera de los dos ejemplos anteriores mediante un depurador, este detendrá el programa cuando se produzca la excepción.If you run either of the preceding two samples using a debugger, the debugger will stop the program when the exception is thrown. Sin un depurador asociado, la excepción se omite en ambos casos en modo silencioso.Without a debugger attached, the exception is silently ignored in both cases.

_ también es un identificador válido._ is also a valid identifier. Cuando se usa fuera de un contexto compatible, _ no se trata como un descarte, sino como una variable válida.When used outside of a supported context, _ is treated not as a discard but as a valid variable. Si un identificador denominado _ ya está en el ámbito, el uso de _ como descarte independiente puede producir lo siguiente:If an identifier named _ is already in scope, the use of _ as a standalone discard can result in:

  • La modificación accidental del valor de la variable _ en el ámbito, al asignarle el valor del descarte previsto.Accidental modification of the value of the in-scope _ variable by assigning it the value of the intended discard. Por ejemplo:For example:
    private static void ShowValue(int _)
    {
       byte[] arr = { 0, 0, 1, 2 };
       _ = BitConverter.ToInt32(arr, 0);
       Console.WriteLine(_);
    }
     // The example displays the following output:
     //       33619968
    
  • Un error del compilador por infringir la seguridad de tipos.A compiler error for violating type safety. Por ejemplo:For example:
    private static bool RoundTrips(int _)
    {
       string value = _.ToString();
       int newValue = 0;
       _ = Int32.TryParse(value, out newValue);
       return _ == newValue;
    }
    // The example displays the following compiler error:
    //      error CS0029: Cannot implicitly convert type 'bool' to 'int'
    
  • Error del compilador CS0136: "Una variable local o un parámetro denominados '_' no se pueden declarar en este ámbito porque ese nombre se está usando en un ámbito local envolvente para definir una variable local o un parámetro".Compiler error CS0136, "A local or parameter named '_' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter." Por ejemplo:For example:
     public void DoSomething(int _)
    {
     var _ = GetValue(); // Error: cannot declare local _ when one is already in scope
    }
    // The example displays the following compiler error:
    // error CS0136:
    //       A local or parameter named '_' cannot be declared in this scope
    //       because that name is used in an enclosing local scope
    //       to define a local or parameter
    

Consulte tambiénSee also