Verwijderingen - C#-basisprincipes

Verwijderingen zijn tijdelijke aanduidingen voor variabelen die opzettelijk niet worden gebruikt in toepassingscode. Verwijderingen zijn gelijk aan niet-toegewezen variabelen; ze hebben geen waarde. Een verwijdering communiceert met de compiler en anderen die uw code lezen: u wilt het resultaat van een expressie negeren. U kunt het resultaat van een expressie, een of meer leden van een tuple-expressie, een out parameter naar een methode of het doel van een expressie voor patroonkoppeling negeren.

Als u deze verwijdert, wordt de intentie van uw code duidelijk. Een verwijdering geeft aan dat onze code nooit gebruikmaakt van de variabele. Ze verbeteren de leesbaarheid en onderhoudbaarheid.

U geeft aan dat een variabele een verwijdering is door deze als naam het onderstrepingsteken (_) toe te wijzen. Met de volgende methode-aanroep wordt bijvoorbeeld een tuple geretourneerd waarin de eerste en tweede waarden worden verwijderd. area is een eerder gedeclareerde variabele die is ingesteld op het derde onderdeel dat wordt geretourneerd door GetCityInformation:

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

U kunt verwijderingen gebruiken om ongebruikte invoerparameters van een lambda-expressie op te geven. Zie de invoerparameters van een lambda-expressiesectie van het artikel Lambda-expressies voor meer informatie.

Wanneer _ een geldige verwijdering is, probeert de waarde ervan op te halen of te gebruiken in een toewijzingsbewerking, genereert compilerfout CS0103, 'De naam '_' bestaat niet in de huidige context'. Deze fout komt doordat _ er geen waarde is toegewezen en mogelijk niet eens een opslaglocatie wordt toegewezen. Als het een werkelijke variabele was, kunt u niet meer dan één waarde verwijderen, zoals in het vorige voorbeeld is gedaan.

Tuple en objectontstructie

Verwijderingen zijn handig bij het werken met tuples wanneer in uw toepassingscode enkele tuple-elementen worden gebruikt, maar andere worden genegeerd. De volgende QueryCityDataForYears methode retourneert bijvoorbeeld een tuple met de naam van een stad, het gebied, een jaar, de bevolking van de stad voor dat jaar, een tweede jaar en de bevolking van de stad voor dat tweede jaar. In het voorbeeld ziet u de verandering in de populatie tussen die twee jaar. Van de gegevens die beschikbaar zijn in de tuple, hebben we geen betrekking op het gebied van de stad, en we kennen de naam van de stad en de twee datums in ontwerptijd. Als gevolg hiervan zijn we alleen geïnteresseerd in de twee populatiewaarden die zijn opgeslagen in de tuple en kunnen we de resterende waarden verwerken als verwijderingen.

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

Zie Deconstructing tuples en andere typen voor meer informatie over het deconstrueren van tuples met verwijderingen.

Met de Deconstruct methode van een klasse, structuur of interface kunt u ook een specifieke set gegevens ophalen en deconstrueren van een object. U kunt verwijderingen gebruiken als u alleen wilt werken met een subset van de gedeconstrueerde waarden. In het volgende voorbeeld wordt een Person object gedeconstrueerd in vier tekenreeksen (de voor- en achternamen, de plaats en de staat), maar wordt de achternaam en de staat verwijderd.

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

Zie Deconstructing tuples en andere typen voor meer informatie over het deconstrueren van door de gebruiker gedefinieerde typen met verwijderingen.

Patroonkoppeling met switch

Het verwijderingspatroon kan worden gebruikt in patroonkoppeling met de switchexpressie. Elke expressie, inclusief null, komt altijd overeen met het verwijderingspatroon.

In het volgende voorbeeld wordt een ProvidesFormatInfo methode gedefinieerd die gebruikmaakt van een switch expressie om te bepalen of een object een IFormatProvider implementatie biedt en test of het object is null. Ook wordt het verwijderingspatroon gebruikt om niet-null-objecten van elk ander type te verwerken.

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

Aanroepen naar methoden met out parameters

Wanneer u de methode aanroept om een door de Deconstruct gebruiker gedefinieerd type (een exemplaar van een klasse, structuur of interface) te deconstrueren, kunt u de waarden van afzonderlijke out argumenten negeren. Maar u kunt ook de waarde van argumenten negeren bij het aanroepen van out een methode met een out parameter.

In het volgende voorbeeld wordt de methode DateTime.TryParse(String, out DateTime) aangeroepen om te bepalen of de tekenreeksweergave van een datum geldig is in de huidige cultuur. Omdat het voorbeeld alleen betrekking heeft op het valideren van de datumtekenreeks en niet met parseren om de datum te extraheren, is het out argument voor de methode een verwijdering.

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

Een zelfstandige verwijdering

U kunt een zelfstandige verwijdering gebruiken om een variabele aan te geven die u wilt negeren. Een typisch gebruik is het gebruik van een toewijzing om ervoor te zorgen dat een argument niet null is. In de volgende code wordt een verwijdering gebruikt om een toewijzing af te dwingen. Aan de rechterkant van de toewijzing wordt de operator null-samenskoling gebruikt om een System.ArgumentNullException waarde te genereren wanneer het argument is null. De code heeft het resultaat van de toewijzing niet nodig, dus wordt deze verwijderd. De expressie dwingt een null-controle af. De verwijdering verduidelijkt uw intentie: het resultaat van de toewijzing is niet nodig of gebruikt.

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

    // Do work with arg.
}

In het volgende voorbeeld wordt een zelfstandige verwijdering gebruikt om het Task object te negeren dat wordt geretourneerd door een asynchrone bewerking. Het toewijzen van de taak heeft het effect van het onderdrukken van de uitzondering die door de bewerking wordt gegenereerd, omdat deze bijna is voltooid. Het maakt uw intentie duidelijk: u wilt het Taskverwijderen en eventuele fouten negeren die zijn gegenereerd op basis van die asynchrone bewerking.

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

Zonder de taak toe te wijzen aan een verwijdering, genereert de volgende code een compilerwaarschuwing:

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

Notitie

Als u een van de voorgaande twee voorbeelden uitvoert met behulp van een foutopsporingsprogramma, stopt het foutopsporingsprogramma het programma wanneer de uitzondering wordt gegenereerd. Zonder gekoppeld foutopsporingsprogramma wordt de uitzondering in beide gevallen op de achtergrond genegeerd.

_ is ook een geldige id. Wanneer deze buiten een ondersteunde context wordt gebruikt, _ wordt deze niet beschouwd als een verwijdering, maar als een geldige variabele. Als een id met de naam _ al binnen het bereik valt, kan het gebruik als _ zelfstandige verwijdering leiden tot:

  • Onbedoelde wijziging van de waarde van de variabele binnen het bereik _ door deze toe te wijzen aan de waarde van de beoogde verwijdering. Voorbeeld:
    private static void ShowValue(int _)
    {
       byte[] arr = [0, 0, 1, 2];
       _ = BitConverter.ToInt32(arr, 0);
       Console.WriteLine(_);
    }
     // The example displays the following output:
     //       33619968
    
  • Een compilerfout voor het schenden van typeveiligheid. Voorbeeld:
    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'
    
  • Compilerfout CS0136: 'Een lokale of parameter met de naam '_' kan niet in dit bereik worden gedeclareerd omdat die naam wordt gebruikt in een lokaal bereik om een lokale of parameter te definiëren. Bijvoorbeeld:
     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
    

Zie ook