Odrzucanie — podstawy języka C#

Odrzucenia to zmienne zastępcze celowo nieużywane w kodzie aplikacji. Odrzucenia są równoważne nieprzypisanym zmiennym; nie mają wartości. Odrzucenie komunikuje intencję kompilatora i innych osób, które odczytują kod: zamierzasz zignorować wynik wyrażenia. Możesz zignorować wynik wyrażenia, co najmniej jeden element członkowczy wyrażenia krotki, out parametr metody lub element docelowy wyrażenia dopasowania wzorca.

Odrzucenia umożliwiają wyczyszczenie intencji kodu. Odrzucenie oznacza, że nasz kod nigdy nie używa zmiennej. Zwiększają czytelność i łatwość utrzymania.

Wskazujesz, że zmienna jest odrzuceniem, przypisując jej podkreślenie (_) jako nazwę. Na przykład następujące wywołanie metody zwraca krotkę, w której są odrzucane pierwsze i drugie wartości. area jest wcześniej zadeklarowaną zmienną ustawioną na trzeci składnik zwrócony przez GetCityInformation:

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

Można użyć odrzuć, aby określić nieużywane parametry wejściowe wyrażenia lambda. Aby uzyskać więcej informacji, zobacz sekcję Input parameters of a lambda expressions (Parametry wejściowe wyrażenia lambda) artykułu lambda expressions (Parametry wejściowe wyrażenia lambda).

Gdy _ jest prawidłowym odrzuceniem, próba pobrania jej wartości lub użycia jej w operacji przypisania generuje błąd kompilatora CS0103" "Nazwa '_' nie istnieje w bieżącym kontekście". Ten błąd jest spowodowany tym, że _ nie ma przypisanej wartości i może nawet nie zostać przypisana lokalizacja magazynu. Jeśli była to rzeczywista zmienna, nie można odrzucić więcej niż jednej wartości, jak w poprzednim przykładzie.

Krotka i dekonstrukcja obiektów

Odrzucenia są przydatne podczas pracy z krotkami, gdy kod aplikacji używa niektórych elementów krotki, ale ignoruje inne. Na przykład poniższa QueryCityDataForYears metoda zwraca krotkę o nazwie miasta, jego obszarze, roku, populacji miasta dla tego roku, drugiego roku i populacji miasta w tym drugim roku. W przykładzie pokazano zmianę populacji między tymi dwoma latami. Z danych dostępnych z krotki, jesteśmy niezkonsekwowani obszarem miasta i znamy nazwę miasta i dwie daty w czasie projektowania. W związku z tym interesuje nas tylko dwie wartości populacji przechowywane w krotki i mogą obsługiwać pozostałe wartości jako odrzucenia.

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

Aby uzyskać więcej informacji na temat dekonstrukcji krotki z odrzuceniami, zobacz Dekonstrukcja krotki i inne typy.

Deconstruct Metoda klasy, struktury lub interfejsu umożliwia również pobieranie i dekonstrukcję określonego zestawu danych z obiektu. Możesz użyć odrzuconych, gdy interesuje Cię praca tylko z podzbiorem zdekonstrukturowanych wartości. Poniższy przykład dekonstrukuje Person obiekt na cztery ciągi (imię i nazwisko, miasto i stan), ale odrzuca nazwisko i stan.

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!
        }
    }
}

Aby uzyskać więcej informacji na temat dekonstrukcji typów zdefiniowanych przez użytkownika za pomocą odrzucań, zobacz Dekonstrukcja krotki i inne typy.

Dopasowywanie wzorca za pomocą polecenia switch

Wzorzec odrzucania może być używany we wzorcu zgodnym z wyrażeniem przełącznika. Każde wyrażenie, w tym null, zawsze pasuje do wzorca odrzucenia.

W poniższym przykładzie zdefiniowano metodę ProvidesFormatInfo , która używa switch wyrażenia w celu określenia, czy obiekt udostępnia implementację IFormatProvider i sprawdza, czy obiekt to null. Używa również wzorca odrzucania do obsługi obiektów innych typów innych niż null.

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

Wywołania metod z parametrami out

Podczas wywoływania Deconstruct metody w celu dekonstrukcji typu zdefiniowanego przez użytkownika (wystąpienia klasy, struktury lub interfejsu) można odrzucić wartości poszczególnych out argumentów. Można jednak również odrzucić wartość argumentów out podczas wywoływania dowolnej metody z parametrem out .

Poniższy przykład wywołuje metodę DateTime.TryParse(String, out DateTime), aby określić, czy reprezentacja ciągu daty jest prawidłowa w bieżącej kulturze. Ponieważ przykład dotyczy tylko sprawdzania poprawności ciągu daty, a nie analizy w celu wyodrębnienia daty, out argument metody jest odrzucaniem.

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

Autonomiczne odrzucenie

Możesz użyć autonomicznego odrzucenia, aby wskazać dowolną zmienną, którą chcesz zignorować. Jednym z typowych zastosowań jest użycie przypisania, aby upewnić się, że argument nie ma wartości null. Poniższy kod używa odrzucenia, aby wymusić przypisanie. Po prawej stronie przypisania użyto operatora łączenia wartości null w celu wyrzucenia System.ArgumentNullException argumentu , gdy argument to null. Kod nie potrzebuje wyniku przypisania, więc zostanie odrzucony. Wyrażenie wymusza sprawdzenie wartości null. Odrzucenie wyjaśnia twoją intencję: wynik przypisania nie jest potrzebny ani używany.

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

    // Do work with arg.
}

W poniższym przykładzie użyto autonomicznego odrzucenia, aby zignorować Task obiekt zwrócony przez operację asynchroniczną. Przypisanie zadania ma wpływ na pomijanie wyjątku zgłaszanego przez operację w miarę jego ukończenia. To sprawia, że intencja jest jasna: chcesz odrzucić Taskelement i zignorować wszelkie błędy wygenerowane na podstawie tej operacji asynchronicznej.

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

Bez przypisywania zadania do odrzucenia następujący kod generuje ostrzeżenie kompilatora:

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

Uwaga

Jeśli uruchomisz jeden z powyższych dwóch przykładów przy użyciu debugera, debuger zatrzyma program po wystąpieniu wyjątku. Bez dołączonego debugera wyjątek jest dyskretnie ignorowany w obu przypadkach.

_ jest również prawidłowym identyfikatorem. W przypadku użycia poza obsługiwanym kontekstem nie jest traktowane jako odrzucenie, _ ale jako prawidłowa zmienna. Jeśli identyfikator o nazwie _ jest już w zakresie, użycie funkcji _ jako autonomicznego odrzucenia może spowodować:

  • Przypadkowe zmodyfikowanie wartości zmiennej w zakresie _ przez przypisanie jej wartości zamierzonego odrzucenia. Na przykład:
    private static void ShowValue(int _)
    {
       byte[] arr = [0, 0, 1, 2];
       _ = BitConverter.ToInt32(arr, 0);
       Console.WriteLine(_);
    }
     // The example displays the following output:
     //       33619968
    
  • Błąd kompilatora w przypadku naruszenia bezpieczeństwa typu. Na przykład:
    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'
    
  • Błąd kompilatora CS0136 "Nie można zadeklarować parametru lokalnego lub o nazwie "_", ponieważ ta nazwa jest używana w otaczającym zakresie lokalnym do zdefiniowania parametru lokalnego lub lokalnego". Na przykład:
     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
    

Zobacz też