Share via


Zelfstudie: C# 11-functie verkennen - statische virtuele leden in interfaces

C# 11 en .NET 7 bevatten statische virtuele leden in interfaces. Met deze functie kunt u interfaces definiëren die overbelaste operators of andere statische leden bevatten. Nadat u interfaces met statische leden hebt gedefinieerd, kunt u deze interfaces gebruiken als beperkingen om algemene typen te maken die gebruikmaken van operators of andere statische methoden. Zelfs als u geen interfaces met overbelaste operators maakt, profiteert u waarschijnlijk van deze functie en de algemene wiskundige klassen die door de taalupdate zijn ingeschakeld.

In deze zelfstudie leert u het volgende:

  • Definieer interfaces met statische leden.
  • Gebruik interfaces om klassen te definiëren die interfaces implementeren met gedefinieerde operators.
  • Algemene algoritmen maken die afhankelijk zijn van statische interfacemethoden.

Vereisten

U moet uw computer instellen voor het uitvoeren van .NET 7, dat C# 11 ondersteunt. De C# 11-compiler is beschikbaar vanaf Visual Studio 2022, versie 17.3 of de .NET 7 SDK.

Statische abstracte interfacemethoden

Laten we beginnen met een voorbeeld. De volgende methode retourneert het middelpunt van twee double getallen:

public static double MidPoint(double left, double right) =>
    (left + right) / (2.0);

Dezelfde logica werkt voor elk numeriek type: int, short, long, floatdecimalof elk type dat een getal vertegenwoordigt. U moet een manier hebben om de + en / operators te gebruiken en een waarde voor 2te definiëren. U kunt de System.Numerics.INumber<TSelf> interface gebruiken om de voorgaande methode te schrijven als de volgende algemene methode:

public static T MidPoint<T>(T left, T right)
    where T : INumber<T> => (left + right) / T.CreateChecked(2);  // note: the addition of left and right may overflow here; it's just for demonstration purposes

Elk type dat de INumber<TSelf> interface implementeert, moet een definitie voor operator +en voor operator /bevatten. De noemer wordt gedefinieerd door T.CreateChecked(2) de waarde 2 voor elk numeriek type te maken, waardoor de noemer hetzelfde type moet zijn als de twee parameters. INumberBase<TSelf>.CreateChecked<TOther>(TOther) maakt een exemplaar van het type van de opgegeven waarde en genereert een OverflowException als de waarde buiten het vertegenwoordigbare bereik valt. (Deze implementatie heeft het potentieel voor overloop als left en right beide groot genoeg waarden zijn. Er zijn alternatieve algoritmen die dit potentiële probleem kunnen voorkomen.)

U definieert statische abstracte leden in een interface met behulp van vertrouwde syntaxis: u voegt de static en abstract modifiers toe aan een statisch lid dat geen implementatie biedt. In het volgende voorbeeld wordt een IGetNext<T> interface gedefinieerd die kan worden toegepast op elk type dat wordt overschreven operator ++:

public interface IGetNext<T> where T : IGetNext<T>
{
    static abstract T operator ++(T other);
}

De beperking die door het typeargument wordt geïmplementeerdIGetNext<T>, Tzorgt ervoor dat de handtekening voor de operator het type bevat of het bijbehorende typeargument. Veel operators dwingen af dat de parameters overeenkomen met het type of de typeparameter zijn die is beperkt om het betreffende type te implementeren. Zonder deze beperking kan de ++ operator niet worden gedefinieerd in de IGetNext<T> interface.

U kunt een structuur maken waarmee een tekenreeks met A-tekens wordt gemaakt, waarbij elke increment een ander teken aan de tekenreeks toevoegt met behulp van de volgende code:

public struct RepeatSequence : IGetNext<RepeatSequence>
{
    private const char Ch = 'A';
    public string Text = new string(Ch, 1);

    public RepeatSequence() {}

    public static RepeatSequence operator ++(RepeatSequence other)
        => other with { Text = other.Text + Ch };

    public override string ToString() => Text;
}

Over het algemeen kunt u elk algoritme bouwen waar u misschien wilt definiëren ++ dat 'de volgende waarde van dit type produceert'. Met deze interface worden duidelijke code en resultaten geproduceerd:

var str = new RepeatSequence();

for (int i = 0; i < 10; i++)
    Console.WriteLine(str++);

In het voorgaande voorbeeld wordt de volgende uitvoer gegenereerd:

A
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA

In dit kleine voorbeeld ziet u de motivatie voor deze functie. U kunt natuurlijke syntaxis gebruiken voor operators, constante waarden en andere statische bewerkingen. U kunt deze technieken verkennen wanneer u meerdere typen maakt die afhankelijk zijn van statische leden, waaronder overbelaste operators. Definieer de interfaces die overeenkomen met de mogelijkheden van uw typen en declareer vervolgens de ondersteuning van deze typen voor de nieuwe interface.

Algemene wiskunde

Het motiverende scenario voor het toestaan van statische methoden, inclusief operators, in interfaces is het ondersteunen van algemene wiskundige algoritmen. De .NET 7-basisklassebibliotheek bevat interfacedefinities voor veel rekenkundige operatoren en afgeleide interfaces die veel rekenkundige operatoren in een INumber<T> interface combineren. Laten we deze typen toepassen om een Point<T> record te maken waarvoor elk numeriek type Tkan worden gebruikt. Het punt kan door sommigen XOffset worden verplaatst en YOffset de + operator gebruiken.

Begin met het maken van een nieuwe consoletoepassing, hetzij met behulp van dotnet new Visual Studio.

De openbare interface voor de Translation<T> en Point<T> moet eruitzien als de volgende code:

// Note: Not complete. This won't compile yet.
public record Translation<T>(T XOffset, T YOffset);

public record Point<T>(T X, T Y)
{
    public static Point<T> operator +(Point<T> left, Translation<T> right);
}

U gebruikt het record type voor zowel de als Point<T> de Translation<T> typen: beide slaan twee waarden op en ze vertegenwoordigen gegevensopslag in plaats van geavanceerd gedrag. De implementatie ziet operator + eruit als de volgende code:

public static Point<T> operator +(Point<T> left, Translation<T> right) =>
    left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };

Als u de vorige code wilt compileren, moet u declareren dat T ondersteuning biedt voor de IAdditionOperators<TSelf, TOther, TResult> interface. Deze interface bevat de operator + statische methode. Het declareert drie typeparameters: één voor de linkeroperand, één voor de rechteroperand en één voor het resultaat. Sommige typen implementeren + voor verschillende operanden en resultaattypen. Voeg een declaratie toe die het typeargument T implementeert IAdditionOperators<T, T, T>:

public record Point<T>(T X, T Y) where T : IAdditionOperators<T, T, T>

Nadat u die beperking hebt toegevoegd, kan uw Point<T> klasse de + operator voor optellen gebruiken. Voeg dezelfde beperking toe aan de Translation<T> declaratie:

public record Translation<T>(T XOffset, T YOffset) where T : IAdditionOperators<T, T, T>;

Met IAdditionOperators<T, T, T> deze beperking voorkomt u dat een ontwikkelaar die uw klasse gebruikt, een Translation type maakt dat niet voldoet aan de beperking voor de toevoeging aan een punt. U hebt de benodigde beperkingen toegevoegd aan de parameter voor Translation<T> het type, Point<T> zodat deze code werkt. U kunt testen door code toe te voegen zoals hierboven de declaraties van Translation en Point in uw Program.cs bestand:

var pt = new Point<int>(3, 4);

var translate = new Translation<int>(5, 10);

var final = pt + translate;

Console.WriteLine(pt);
Console.WriteLine(translate);
Console.WriteLine(final);

U kunt deze code herbruikbaar maken door aan te geven dat deze typen de juiste rekenkundige interfaces implementeren. De eerste wijziging die moet worden aangebracht, is om te declareren dat Point<T, T> de IAdditionOperators<Point<T>, Translation<T>, Point<T>> interface implementeert. Het Point type maakt gebruik van verschillende typen voor operanden en het resultaat. Het Point type implementeert al een operator + met die handtekening, dus het toevoegen van de interface aan de declaratie is alles wat u nodig hebt:

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
    where T : IAdditionOperators<T, T, T>

Ten slotte is het handig om een eigenschap te hebben die de waarde van de additieve identiteit voor dat type definieert wanneer u optellen uitvoert. Er is een nieuwe interface voor die functie: IAdditiveIdentity<TSelf,TResult>. Een vertaling van {0, 0} is de additieve identiteit: het resulterende punt is hetzelfde als de linkeroperand. De IAdditiveIdentity<TSelf, TResult> interface definieert één alleen-lezen eigenschap, AdditiveIdentitydie de identiteitswaarde retourneert. Er Translation<T> zijn enkele wijzigingen nodig om deze interface te implementeren:

using System.Numerics;

public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>
    where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
    public static Translation<T> AdditiveIdentity =>
        new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);
}

Er zijn hier enkele wijzigingen, dus laten we ze een voor een doorlopen. Eerst declareert u dat het Translation type de IAdditiveIdentity interface implementeert:

public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>

Vervolgens kunt u proberen het interfacelid te implementeren, zoals wordt weergegeven in de volgende code:

public static Translation<T> AdditiveIdentity =>
    new Translation<T>(XOffset: 0, YOffset: 0);

De voorgaande code wordt niet gecompileerd, omdat 0 dit afhankelijk is van het type. Het antwoord: Gebruiken IAdditiveIdentity<T>.AdditiveIdentity voor 0. Deze wijziging betekent dat uw beperkingen nu moeten worden opgenomen die T worden geïmplementeerd IAdditiveIdentity<T>. Dit resulteert in de volgende implementatie:

public static Translation<T> AdditiveIdentity =>
    new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);

Nu u die beperking Translation<T>hebt toegevoegd, moet u dezelfde beperking toevoegen aan Point<T>:

using System.Numerics;

public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
    where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
    public static Point<T> operator +(Point<T> left, Translation<T> right) =>
        left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };
}

In dit voorbeeld ziet u hoe de interfaces voor algemene wiskundige berekeningen worden samengesteld. U hebt geleerd hoe u:

  • Schrijf een methode die afhankelijk is van de INumber<T> interface, zodat de methode kan worden gebruikt met elk numeriek type.
  • Bouw een type dat afhankelijk is van de toevoegingsinterfaces om een type te implementeren dat slechts één wiskundige bewerking ondersteunt. Dit type declareert de ondersteuning voor dezelfde interfaces, zodat deze op andere manieren kan worden samengesteld. De algoritmen worden geschreven met behulp van de meest natuurlijke syntaxis van wiskundige operatoren.

Experimenteer met deze functies en registreer feedback. U kunt het menu-item Feedback verzenden in Visual Studio gebruiken of een nieuw probleem maken in de roslyn-opslagplaats op GitHub. Bouw algemene algoritmen die met elk numeriek type werken. Bouw algoritmen met behulp van deze interfaces waarbij het typeargument alleen een subset van numerieke mogelijkheden kan implementeren. Zelfs als u geen nieuwe interfaces bouwt die gebruikmaken van deze mogelijkheden, kunt u experimenteren met het gebruik ervan in uw algoritmen.

Zie ook