Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Statische virtuelle Mitglieder von Schnittstellen ermöglichen es Ihnen, Schnittstellen zu definieren, die überladene Operatoren oder andere statische Member enthalten. Nachdem Sie Schnittstellen mit statischen Membern definiert haben, können Sie diese Schnittstellen als Einschränkungen verwenden, um generische Typen zu erstellen, die Operatoren oder andere statische Methoden verwenden. Auch wenn Sie keine Schnittstellen mit überladenen Operatoren erstellen, profitieren Sie wahrscheinlich von diesem Feature und den generischen mathematischen Klassen, die durch die Sprachaktualisierung aktiviert sind.
In diesem Tutorial erfahren Sie, wie:
- Definieren von Schnittstellen mit statischen Membern.
- Verwenden Sie Schnittstellen zum Definieren von Klassen, die Schnittstellen mit definierten Operatoren implementieren.
- Erstellen Sie generische Algorithmen, die auf statischen Schnittstellenmethoden basieren.
Voraussetzungen
- Das neueste .NET SDK
- Visual Studio Code-Editor
- Das C# DevKit
Statische abstrakte Schnittstellenmethoden
Beginnen wir mit einem Beispiel. Die folgende Methode gibt den Mittelpunkt zweier double Zahlen zurück:
public static double MidPoint(double left, double right) =>
(left + right) / (2.0);
Dieselbe Logik würde für jeden numerischen Typ funktionieren: int, , short, , long, floatdecimal, oder beliebiger Typ, der eine Zahl darstellt. Sie müssen eine Möglichkeit haben, die +- und /-Operatoren zu verwenden und einen Wert für 2 festzulegen. Sie können die System.Numerics.INumber<TSelf> Schnittstelle verwenden, um die vorhergehende Methode als die folgende generische Methode zu schreiben:
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
Jeder Typ, der die INumber<TSelf> Schnittstelle implementiert, muss eine Definition für operator + und für operator /. Der Nenner wird über T.CreateChecked(2) definiert, um den Wert 2 für jedem numerischen Typ zu erstellen, was den Nenner dazu zwingt, denselben Typ wie die beiden Parameter zu haben.
INumberBase<TSelf>.CreateChecked<TOther>(TOther) erstellt eine Instanz des Typs aus dem angegebenen Wert und wirft eine OverflowException, wenn der Wert außerhalb des darstellbaren Bereichs liegt. (Diese Implementierung kann zu einem Überlauf führen, wenn sowohl left als auch right groß genug sind. Es gibt alternative Algorithmen, die dieses potenzielle Problem vermeiden können.)
Sie definieren statische abstrakte Member in einer Schnittstelle mithilfe der vertrauten Syntax: Sie fügen die static und abstract Modifizierer jedem statischen Element hinzu, das keine Implementierung bereitstellt. Im folgenden Beispiel wird eine IGetNext<T> Schnittstelle definiert, die auf jeden beliebigen Typ angewendet werden kann, der operator ++ überschreibt.
public interface IGetNext<T> where T : IGetNext<T>
{
static abstract T operator ++(T other);
}
Die Einschränkung, die vom Typargument implementiert T wird, stellt sicher, IGetNext<T>dass die Signatur für den Operator den enthaltenden Typ oder das Typargument enthält. Viele Operatoren setzen durch, dass ihre Parameter dem Typ entsprechen müssen oder ein Typparameter sein, der so eingeschränkt ist, dass er den enthaltenden Typ implementiert. Ohne diese Einschränkung konnte der ++ Operator nicht in der IGetNext<T> Schnittstelle definiert werden.
Sie können eine Struktur erstellen, die eine Zeichenfolge von "A"-Zeichen erstellt, bei der jedes Inkrement der Zeichenfolge mit dem folgenden Code ein weiteres Zeichen hinzufügt:
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;
}
Im Allgemeinen können Sie einen beliebigen Algorithmus erstellen, in dem Sie ++ definieren möchten, um "den nächsten Wert dieses Typs zu erzeugen". Die Verwendung dieser Schnittstelle führt zu klarem Code und klaren Ergebnissen.
var str = new RepeatSequence();
for (int i = 0; i < 10; i++)
Console.WriteLine(str++);
Im vorherigen Beispiel wird die folgende Ausgabe erzeugt:
A
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA
In diesem kleinen Beispiel wird die Motivation für dieses Feature veranschaulicht. Sie können natürliche Syntax für Operatoren, Konstantenwerte und andere statische Vorgänge verwenden. Sie können diese Methoden untersuchen, wenn Sie mehrere Typen erstellen, die sich auf statische Member verlassen, einschließlich überladener Operatoren. Definieren Sie die Schnittstellen, die den Funktionen Ihrer Typen entsprechen, und deklarieren Sie dann die Unterstützung dieser Typen für die neue Schnittstelle.
Generische Mathematik
Das motivierende Szenario zum Zulassen statischer Methoden, einschließlich Operatoren, in Schnittstellen besteht darin, generische mathematische Algorithmen zu unterstützen. Die .NET 7-Basisklassenbibliothek enthält Schnittstellendefinitionen für viele arithmetische Operatoren und abgeleitete Schnittstellen, die viele arithmetische Operatoren in einer INumber<T> Schnittstelle kombinieren. Wenden wir diese Typen an, um einen Point<T> Datensatz zu erstellen, für den ein beliebiger numerischer Typ verwendet werden kann T. Sie können den Punkt um einige XOffset und YOffset mit dem +-Operator verschieben.
Erstellen Sie zunächst eine neue Konsolenanwendung, entweder mithilfe dotnet new oder mit Visual Studio.
Die öffentliche Schnittstelle für die Translation<T> und Point<T> sollte wie der folgende Code aussehen:
// 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);
}
Sie verwenden den record-Typ sowohl für den Translation<T>- als auch für den Point<T>-Typ: Beide speichern zwei Werte und stehen für Datenspeicherung statt für komplexes Verhalten. Die Implementierung von operator + würde wie im folgenden Code aussehen:
public static Point<T> operator +(Point<T> left, Translation<T> right) =>
left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };
Damit der vorherige Code kompiliert werden kann, müssen Sie deklarieren, dass T die IAdditionOperators<TSelf, TOther, TResult> Schnittstelle unterstützt. Diese Schnittstelle enthält die operator + statische Methode. Es deklariert drei Typparameter: Eine für den linken Operanden, eine für den rechten Operanden und eine für das Ergebnis. Einige Typen implementieren + für verschiedene Operanden- und Ergebnistypen. Fügen Sie eine Deklaration hinzu, dass das Typargument TIAdditionOperators<T, T, T> implementiert:
public record Point<T>(T X, T Y) where T : IAdditionOperators<T, T, T>
Nachdem Sie diese Einschränkung hinzugefügt haben, kann Ihre Point<T> Klasse den + Zusatzoperator verwenden. Fügen Sie dieselbe Einschränkung für die Translation<T> Deklaration hinzu:
public record Translation<T>(T XOffset, T YOffset) where T : IAdditionOperators<T, T, T>;
Die IAdditionOperators<T, T, T> Einschränkung verhindert, dass ein Entwickler, der Ihre Klasse verwendet, einen Translation erzeugt, bei dem ein Typ verwendet wird, der die Einschränkung für die Addition zu einem Punkt nicht erfüllt. Sie haben dem Typparameter die erforderlichen Einschränkungen hinzugefügt Translation<T> , Point<T> sodass dieser Code funktioniert. Sie können testen, indem Sie Code wie die folgenden oben aufgeführten Deklarationen Translation und Point in Ihrer Program.cs Datei hinzufügen:
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);
Sie können diesen Code wiederverwendbar machen, indem Sie deklarieren, dass diese Typen die entsprechenden arithmetischen Schnittstellen implementieren. Die erste Änderung besteht darin, die Schnittstelle zu deklarieren Point<T, T> , die die IAdditionOperators<Point<T>, Translation<T>, Point<T>> Schnittstelle implementiert. Der Point Typ verwendet verschiedene Typen für Operanden und das Ergebnis. Der Point-Typ implementiert bereits ein operator + mit dieser Signatur, sodass das Hinzufügen der Schnittstelle zur Deklaration alles ist, was Sie tun müssen.
public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
where T : IAdditionOperators<T, T, T>
Schließlich, wenn Sie eine Addition durchführen, ist es hilfreich, eine Eigenschaft zu haben, die den additiven Identitätswert für diesen Typ definiert. Es gibt eine neue Schnittstelle für dieses Feature: IAdditiveIdentity<TSelf,TResult>.
{0, 0} stellt die additive Identität dar: Der resultierende Punkt ist derselbe wie der linke Operand. Die IAdditiveIdentity<TSelf, TResult> Schnittstelle definiert eine readonly-Eigenschaft, AdditiveIdentitydie den Identitätswert zurückgibt.
Translation<T> erfordert einige Änderungen, um diese Schnittstelle zu implementieren.
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);
}
Es gibt hier ein paar Änderungen, also lassen Sie uns sie einzeln durchgehen. Zunächst deklarieren Sie, dass der Translation Typ die IAdditiveIdentity Schnittstelle implementiert:
public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>
Als Nächstes können Sie versuchen, das Schnittstellenelement wie im folgenden Code dargestellt zu implementieren:
public static Translation<T> AdditiveIdentity =>
new Translation<T>(XOffset: 0, YOffset: 0);
Der vorangehende Code kompiliert nicht, da 0 vom Typ abhängt. Antwort: Verwendung IAdditiveIdentity<T>.AdditiveIdentity für 0. Diese Änderung bedeutet, dass Ihre Einschränkungen jetzt beinhalten müssen, dass TIAdditiveIdentity<T> implementiert. Dies führt zu der folgenden Implementierung:
public static Translation<T> AdditiveIdentity =>
new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);
Nachdem Sie diese Einschränkung auf Translation<T> hinzugefügt haben, müssen Sie die gleiche Einschränkung auf Point<T> hinzufügen.
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 diesem Beispiel haben Sie einen Einblick darin gewonnen, wie sich die Schnittstellen für generische Mathematik zusammensetzen. Sie haben gelernt, wie Sie:
- Schreiben Sie eine Methode, die auf der
INumber<T>Schnittstelle basiert, damit die Methode mit jedem numerischen Typ verwendet werden kann. - Erstellen Sie einen Typ, der auf den Zusätzlichen Schnittstellen basiert, um einen Typ zu implementieren, der nur einen mathematischen Vorgang unterstützt. Dieser Typ deklariert seine Unterstützung für dieselben Schnittstellen, sodass er auf andere Weise verfasst werden kann. Die Algorithmen werden mithilfe der natürlichsten Syntax mathematischer Operatoren geschrieben.
Experimentieren Sie mit diesen Features, und registrieren Sie Feedback. Sie können das Menüelement "Feedback senden " in Visual Studio verwenden oder ein neues Problem im roslyn-Repository auf GitHub erstellen. Erstellen Sie generische Algorithmen, die mit jedem numerischen Typ arbeiten. Erstellen Sie Algorithmen mithilfe dieser Schnittstellen, bei denen das Typargument nur eine Teilmenge von Funktionen wie Zahlen implementiert. Auch wenn Sie keine neuen Schnittstellen erstellen, die diese Funktionen verwenden, können Sie mit der Verwendung in Ihren Algorithmen experimentieren.