Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
C# 14 introdujo miembros de extensión, una mejora de los métodos de extensión existentes. Los miembros de extensión te permiten añadir propiedades y operadores. También puedes extender tanto tipos como instancias de tipos. C# 15 agrega indizadores de extensión, por lo que un tipo existente puede admitir el acceso indexado desde un bloque de extensión.
En este tutorial, explorarás los miembros de extensión al mejorar el tipo System.Drawing.Point con operaciones matemáticas, transformaciones de coordenadas y propiedades útiles. A continuación, añades acceso indexado a un tipo Path que almacena desplazamientos punto a punto. Aprenderás a migrar los métodos de extensión existentes a la nueva sintaxis de los miembros de extensión y cuándo conviene usar cada enfoque.
En este tutorial, usted hará lo siguiente:
- Cree miembros de extensión de C# 14 con propiedades y operadores estáticos.
- Implementa transformaciones de coordenadas utilizando miembros de extensión.
- Migra los métodos de extensión tradicionales a la sintaxis de los miembros de extensión.
- Agregar un indizador de extensión de C# 15 para leer y actualizar puntos absolutos en una ruta.
- Compara los miembros de extensión con los métodos de extensión tradicionales.
Prerrequisitos
- SDK de .NET 11.
- Editor como Visual Studio 2026 o Visual Studio Code con el Kit de desarrollo de C#.
Este ejemplo tiene como destino net11.0, que selecciona C# 15 de forma predeterminada. Los indexadores de extensión no requieren <LangVersion>preview</LangVersion>.
Creación de la aplicación de ejemplo
Empiece por crear una aplicación de consola que muestre los métodos de extensión tradicionales y la nueva sintaxis de miembros de extensión. Crea extensiones para el tipo System.Drawing.Point. Este tipo procede del System.Drawing espacio de nombres y se usa normalmente en aplicaciones de Windows Forms.
Cree una nueva aplicación de consola.
dotnet new console -f net11.0 -n PointExtensions cd PointExtensionsCompruebe que el archivo de proyecto tiene como destino .NET 11:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net11.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project>Copie el código siguiente en un nuevo archivo denominado
ExtensionMethods.cs:using System.Drawing; using System.Numerics; namespace ExtensionMethods; public static class PointExtensions { public static Vector2 ToVector(this Point point) => new Vector2(point.X, point.Y); public static void Translate(this ref Point point, int xDist, int yDist) { point.X += xDist; point.Y += yDist; } public static void Scale(this ref Point point, int xScale, int yScale) { point.X *= xScale; point.Y *= yScale; } public static void Rotate(this ref Point point, int angleInDegrees) { double theta = ((double)angleInDegrees * Math.PI) / 180.0; double sinTheta = Math.Sin(theta); double cosTheta = Math.Cos(theta); double newX = (double)point.X * cosTheta - (double)point.Y * sinTheta; double newY = (double)point.X * sinTheta + (double)point.Y * cosTheta; point.X = (int)newX; point.Y = (int)newY; } }Copie el código siguiente que muestra estos métodos de extensión y otros usos de System.Drawing.Point:
using System.Drawing; using System.Numerics; using ExtensionMethods; public static class ExtensionMethodsDemonstrations { public static void TraditionalExtensionMethods() { OriginAsADataElement(); ArithmeticWithPoints(); DiscreteArithmeticWithPoints(); ExtensionMethodsThis(); MoreExamples(); } static void OriginAsADataElement() { // Inline implementation since Point.Origin doesn't exist in ExtensionMethods Point origin = Point.Empty; // Equivalent to Point.Origin Console.WriteLine($"Point.Origin (inline): {origin}"); Console.WriteLine($"Same as Point.Empty: {origin == Point.Empty}"); Console.WriteLine(); } static void ArithmeticWithPoints() { Point p1 = new Point(5, 3); Point p2 = new Point(2, 7); Console.WriteLine($"Point 1: {p1}"); Console.WriteLine($"Point 2: {p2}"); // Inline implementation since + and - operators don't exist in ExtensionMethods Point addition = new Point(p1.X + p2.X, p1.Y + p2.Y); Point subtraction1 = new Point(p1.X - p2.X, p1.Y - p2.Y); Point subtraction2 = new Point(p2.X - p1.X, p2.Y - p1.Y); Console.WriteLine($"Addition (p1 + p2): {addition}"); Console.WriteLine($"Subtraction (p1 - p2): {subtraction1}"); Console.WriteLine($"Subtraction (p2 - p1): {subtraction2}"); Console.WriteLine(); } static void DiscreteArithmeticWithPoints() { Point point = new Point(10, 8); int offsetX = 3; int offsetY = -2; int scaleX = 2; int scaleY = 3; int divisorX = 2; int divisorY = 4; Console.WriteLine($"Original point: {point}"); Console.WriteLine($"Offset: ({offsetX}, {offsetY})"); Console.WriteLine($"Scale: ({scaleX}, {scaleY})"); Console.WriteLine($"Divisor: ({divisorX}, {divisorY})"); Console.WriteLine(); // Inline implementations since tuple operators don't exist in ExtensionMethods Point addedOffset = new Point(point.X + offsetX, point.Y + offsetY); Point subtractedOffset = new Point(point.X - offsetX, point.Y - offsetY); Point scaledPoint = new Point(point.X * scaleX, point.Y * scaleY); Point dividedPoint = new Point(point.X / divisorX, point.Y / divisorY); Console.WriteLine($"point + offset: {addedOffset}"); Console.WriteLine($"point - offset: {subtractedOffset}"); Console.WriteLine($"point * scale: {scaledPoint}"); Console.WriteLine($"point / divisor: {dividedPoint}"); Console.WriteLine(); } static void ExtensionMethodsThis() { // ToVector demonstration - using extension method Point vectorPoint = new Point(12, 16); Vector2 vector = vectorPoint.ToVector(); Console.WriteLine($"Point {vectorPoint} as Vector2: {vector}"); Console.WriteLine(); // Translate demonstration - using extension method Point translatePoint = new Point(5, 5); Console.WriteLine($"Before Translate: {translatePoint}"); translatePoint.Translate(3, -2); Console.WriteLine($"After Translate(3, -2): {translatePoint}"); Console.WriteLine(); // Scale demonstration - using extension method Point scalePoint = new Point(4, 6); Console.WriteLine($"Before Scale: {scalePoint}"); scalePoint.Scale(2, 3); Console.WriteLine($"After Scale(2, 3): {scalePoint}"); Console.WriteLine(); // Rotate demonstration - using extension method Point rotatePoint1 = new Point(10, 0); Console.WriteLine($"Before Rotate: {rotatePoint1}"); rotatePoint1.Rotate(90); Console.WriteLine($"After Rotate(90°): {rotatePoint1}"); Point rotatePoint2 = new Point(5, 5); Console.WriteLine($"Before Rotate: {rotatePoint2}"); rotatePoint2.Rotate(45); Console.WriteLine($"After Rotate(45°): {rotatePoint2}"); Point rotatePoint3 = new Point(3, 4); Console.WriteLine($"Before Rotate: {rotatePoint3}"); rotatePoint3.Rotate(180); Console.WriteLine($"After Rotate(180°): {rotatePoint3}"); Console.WriteLine(); } static void MoreExamples() { // Combining operators and methods Console.WriteLine("Scenario 1: Building a rectangle using inline operators"); Point topLeft = Point.Empty; // Inline equivalent of Point.Origin Point bottomRight = new Point(topLeft.X + 10, topLeft.Y + 8); // Inline addition Point topRight = new Point(bottomRight.X, topLeft.Y); Point bottomLeft = new Point(topLeft.X, bottomRight.Y); Console.WriteLine($"Rectangle corners:"); Console.WriteLine($" Top-Left: {topLeft}"); Console.WriteLine($" Top-Right: {topRight}"); Console.WriteLine($" Bottom-Left: {bottomLeft}"); Console.WriteLine($" Bottom-Right: {bottomRight}"); Console.WriteLine(); // Transformation chain Console.WriteLine("Scenario 2: Transformation chain (mixed methods)"); Point transformPoint = new Point(2, 3); Console.WriteLine($"Starting point: {transformPoint}"); // Scale up - using extension method transformPoint.Scale(3, 2); Console.WriteLine($"After scaling by (3, 2): {transformPoint}"); // Translate - using inline addition transformPoint = new Point(transformPoint.X + 5, transformPoint.Y + (-3)); Console.WriteLine($"After translating by (5, -3): {transformPoint}"); // Rotate - using extension method transformPoint.Rotate(45); Console.WriteLine($"After rotating 45�: {transformPoint}"); // Convert to vector - using extension method Vector2 finalVector = transformPoint.ToVector(); Console.WriteLine($"Final result as Vector2: {finalVector}"); Console.WriteLine(); // Distance calculation using inline operators and extension methods Console.WriteLine("Scenario 3: Distance calculation (mixed methods)"); Point point1 = new Point(1, 1); Point point2 = new Point(4, 5); Point difference = new Point(point2.X - point1.X, point2.Y - point1.Y); // Inline subtraction Vector2 diffVector = difference.ToVector(); // Extension method float distance = diffVector.Length(); Console.WriteLine($"Point 1: {point1}"); Console.WriteLine($"Point 2: {point2}"); Console.WriteLine($"Difference: {difference}"); Console.WriteLine($"Distance: {distance:F2}"); Console.WriteLine(); Console.WriteLine("Traditional extension methods demonstration complete!"); } }Reemplace el contenido de
Program.cspor el código de demostración.ExtensionMethodsDemonstrations.TraditionalExtensionMethods();Ejecute la aplicación de ejemplo y examine la salida.
Los métodos de extensión tradicionales solo pueden agregar métodos de instancia a los tipos existentes. Los miembros de extensión permiten agregar propiedades estáticas, lo que proporciona una manera más natural de extender tipos con constantes o valores calculados.
Añade propiedades estáticas con miembros de extensión
En primer lugar, examine este código en el ejemplo:
// Inline implementation since Point.Origin doesn't exist in ExtensionMethods
Point origin = Point.Empty; // Equivalent to Point.Origin
Console.WriteLine($"Point.Origin (inline): {origin}");
Console.WriteLine($"Same as Point.Empty: {origin == Point.Empty}");
Console.WriteLine();
Muchas aplicaciones que usan geometría 2D utilizan el concepto de Origin, que es el mismo valor que Point.Empty. Este código usa ese hecho, pero algunos desarrolladores pueden crear un new Point(0,0), lo que incurre en algún trabajo adicional. En un dominio determinado, quiere expresar estos valores comunes a través de propiedades estáticas.
Cree NewExtensionsMembers.cs para crear miembros de extensión que resuelvan este problema:
using System.Drawing;
using System.Numerics;
namespace ExtensionMembers;
public static class PointExtensions
{
extension (Point)
{
public static Point Origin => Point.Empty;
}
}
El código anterior agrega una propiedad de extensión estática al Point struct. La extension palabra clave introduce un bloque de extensión. Este bloque de extensión extiende la Point estructura.
Puede usar esta propiedad estática como si fuera miembro de la Point estructura.
Console.WriteLine("1. Static Properties");
Console.WriteLine("-------------------");
Point origin = Point.Origin;
Console.WriteLine($"Point.Origin: {origin}");
Console.WriteLine($"Same as Point.Empty: {origin == Point.Empty}");
Console.WriteLine();
La Point.Origin propiedad ahora aparece como si formase parte del tipo original Point , lo que proporciona una API más intuitiva.
Implementación de operadores aritméticos
A continuación, examine el código siguiente que realiza la aritmética con puntos:
Point p1 = new Point(5, 3);
Point p2 = new Point(2, 7);
Console.WriteLine($"Point 1: {p1}");
Console.WriteLine($"Point 2: {p2}");
// Inline implementation since + and - operators don't exist in ExtensionMethods
Point addition = new Point(p1.X + p2.X, p1.Y + p2.Y);
Point subtraction1 = new Point(p1.X - p2.X, p1.Y - p2.Y);
Point subtraction2 = new Point(p2.X - p1.X, p2.Y - p1.Y);
Console.WriteLine($"Addition (p1 + p2): {addition}");
Console.WriteLine($"Subtraction (p1 - p2): {subtraction1}");
Console.WriteLine($"Subtraction (p2 - p1): {subtraction2}");
Console.WriteLine();
Los métodos de extensión tradicionales no pueden agregar operadores a los tipos existentes. Debe implementar manualmente las operaciones aritméticas, lo que hace que el código sea más verboso y más difícil de leer. El algoritmo se duplica cada vez que se necesita la operación, lo que crea más oportunidades para que los errores pequeños entren en la base de código. Es mejor colocar ese código en una ubicación. Agregue los operadores siguientes al bloque de extensión en NewExtensionsMembers.cs:
public static Point operator +(Point left, Point right) =>
new Point(left.X + right.X, left.Y + right.Y);
public static Point operator -(Point left, Point right) =>
new Point(left.X - right.X, left.Y - right.Y);
Mediante el uso de miembros de extensión, puede agregar operadores directamente a los tipos existentes. Ahora puede realizar operaciones aritméticas mediante la sintaxis natural:
Console.WriteLine("2. Arithmetic Operators (Point + Point, Point - Point)");
Console.WriteLine("-----------------------------------------------------");
Point p1 = new Point(5, 3);
Point p2 = new Point(2, 7);
Console.WriteLine($"Point 1: {p1}");
Console.WriteLine($"Point 2: {p2}");
Console.WriteLine($"Addition (p1 + p2): {p1 + p2}");
Console.WriteLine($"Subtraction (p1 - p2): {p1 - p2}");
Console.WriteLine($"Subtraction (p2 - p1): {p2 - p1}");
Console.WriteLine();
Los operadores de extensión hacen que la aritmética de punto sea tan natural como trabajar con tipos numéricos predefinidos.
Adición de más operadores
También puede agregar operadores de extensión para las operaciones discretas que se muestran en el ejemplo de código siguiente:
Point point = new Point(10, 8);
int offsetX = 3;
int offsetY = -2;
int scaleX = 2;
int scaleY = 3;
int divisorX = 2;
int divisorY = 4;
Console.WriteLine($"Original point: {point}");
Console.WriteLine($"Offset: ({offsetX}, {offsetY})");
Console.WriteLine($"Scale: ({scaleX}, {scaleY})");
Console.WriteLine($"Divisor: ({divisorX}, {divisorY})");
Console.WriteLine();
// Inline implementations since tuple operators don't exist in ExtensionMethods
Point addedOffset = new Point(point.X + offsetX, point.Y + offsetY);
Point subtractedOffset = new Point(point.X - offsetX, point.Y - offsetY);
Point scaledPoint = new Point(point.X * scaleX, point.Y * scaleY);
Point dividedPoint = new Point(point.X / divisorX, point.Y / divisorY);
Console.WriteLine($"point + offset: {addedOffset}");
Console.WriteLine($"point - offset: {subtractedOffset}");
Console.WriteLine($"point * scale: {scaledPoint}");
Console.WriteLine($"point / divisor: {dividedPoint}");
Console.WriteLine();
Los + operadores y - son operadores binarios y requieren dos operandos, no tres. En lugar de dos enteros discretos, use una tupla para especificar los X valores delta y Y :
public static Point operator *(Point left, (int dx, int dy) scale) =>
new Point(left.X * scale.dx, left.Y * scale.dy);
public static Point operator /(Point left, (int dx, int dy) scale) =>
new Point(left.X / scale.dx, left.Y / scale.dy);
public static Point operator +(Point left, (int dx, int dy) scale) =>
new Point(left.X + scale.dx, left.Y + scale.dy);
public static Point operator -(Point left, (int dx, int dy) scale) =>
new Point(left.X - scale.dx, left.Y - scale.dy);
El operador anterior habilita operaciones elegantes basadas en tuplas:
Console.WriteLine("3. Discrete Operators using tuples (Point with (int, int))");
Console.WriteLine("------------------------------------------");
Point point = new Point(10, 8);
var offset = (3, -2);
var scale = (2, 3);
var divisor = (2, 4);
Console.WriteLine($"Original point: {point}");
Console.WriteLine($"Offset tuple: {offset}");
Console.WriteLine($"Scale tuple: {scale}");
Console.WriteLine($"Divisor tuple: {divisor}");
Console.WriteLine();
Console.WriteLine($"point + offset: {point + offset}");
Console.WriteLine($"point - offset: {point - offset}");
Console.WriteLine($"point * scale: {point * scale}");
Console.WriteLine($"point / divisor: {point / divisor}");
Console.WriteLine();
Las extensiones pueden incluir múltiples operadores sobrecargados, siempre que los operandos sean distintos.
Migra métodos de instancia a miembros de extensión
Los miembros de extensión también admiten métodos de instancia. No es necesario cambiar los métodos de extensión existentes. Los formularios antiguos y nuevos son binarios y compatibles con el origen. Si desea mantener todas las extensiones en un contenedor, puede hacerlo. La migración de métodos de extensión tradicionales a la nueva sintaxis mantiene la misma funcionalidad.
Métodos de extensión tradicionales
El enfoque tradicional usa la sintaxis de parámetro this.
public static Vector2 ToVector(this Point point) =>
new Vector2(point.X, point.Y);
public static void Translate(this Point point, int xDist, int yDist)
{
point.X += xDist;
point.Y += yDist;
}
public static void Scale(this Point point, int xScale, int yScale)
{
point.X *= xScale;
point.Y *= yScale;
}
public static void Rotate(this Point point, int angleInDegress)
{
double theta = ((double)angleInDegress * Math.PI) / 180.0;
double sinTheta = Math.Sin(theta);
double cosTheta = Math.Cos(theta);
double newX = (double)point.X * cosTheta - (double)point.Y * sinTheta;
double newY = (double)point.X * sinTheta + (double)point.Y * cosTheta;
point.X = (int)newX;
point.Y = (int)newY;
}
Los miembros de extensión usan una sintaxis diferente, pero proporcionan la misma funcionalidad. Agregue el código siguiente a la nueva clase de miembros de extensión:
public Vector2 ToVector() =>
new Vector2(point.X, point.Y);
public void Translate(int xDist, int yDist)
{
point.X += xDist;
point.Y += yDist;
}
public void Scale(int xScale, int yScale)
{
point.X *= xScale;
point.Y *= yScale;
}
public void Rotate(int angleInDegrees)
{
double theta = ((double)angleInDegrees * Math.PI) / 180.0;
double sinTheta = Math.Sin(theta);
double cosTheta = Math.Cos(theta);
double newX = (double)point.X * cosTheta - (double)point.Y * sinTheta;
double newY = (double)point.X * sinTheta + (double)point.Y * cosTheta;
point.X = (int)newX;
point.Y = (int)newY;
}
Estos métodos extienden una instancia de la Point estructura, no el Point tipo . El bloque de extensión asigna un nombre al parámetro receptor para que el cuerpo del método pueda leer ese punto. En el ejemplo se usa extension(ref Point point) porque Translate, Scale y Rotate cambian el Point de quien realiza la llamada. Sin ref, esos métodos actualizarían una copia de la estructura y el autor de la llamada no vería el cambio.
Puede utilizar estos nuevos métodos de instancias de la misma manera que accedía a los métodos de extensión tradicionales.
Console.WriteLine("4. Instance Methods");
Console.WriteLine("------------------");
// ToVector demonstration
Point vectorPoint = new Point(12, 16);
Vector2 vector = vectorPoint.ToVector();
Console.WriteLine($"Point {vectorPoint} as Vector2: {vector}");
Console.WriteLine();
// Translate demonstration
Point translatePoint = new Point(5, 5);
Console.WriteLine($"Before Translate: {translatePoint}");
translatePoint.Translate(3, -2);
Console.WriteLine($"After Translate(3, -2): {translatePoint}");
Console.WriteLine();
// Scale demonstration
Point scalePoint = new Point(4, 6);
Console.WriteLine($"Before Scale: {scalePoint}");
scalePoint.Scale(2, 3);
Console.WriteLine($"After Scale(2, 3): {scalePoint}");
Console.WriteLine();
// Rotate demonstration
Point rotatePoint1 = new Point(10, 0);
Console.WriteLine($"Before Rotate: {rotatePoint1}");
rotatePoint1.Rotate(90);
Console.WriteLine($"After Rotate(90°): {rotatePoint1}");
Point rotatePoint2 = new Point(5, 5);
Console.WriteLine($"Before Rotate: {rotatePoint2}");
rotatePoint2.Rotate(45);
Console.WriteLine($"After Rotate(45°): {rotatePoint2}");
Point rotatePoint3 = new Point(3, 4);
Console.WriteLine($"Before Rotate: {rotatePoint3}");
rotatePoint3.Rotate(180);
Console.WriteLine($"After Rotate(180°): {rotatePoint3}");
Console.WriteLine();
La principal diferencia es la sintaxis: los miembros de extensión usan extension (Type variableName) en lugar de this Type variableName.
Agregar indexadores de extensión
C# 15 agrega indizadores a extension bloques. Un indexador no tiene ningún nombre. El código accede a él con this[...] en la declaración y con sintaxis indizada en el sitio de llamada.
Imagine un tipo de ruta que almacena cada paso como un desplazamiento relativo. Cuando solicitas path[i], lo que quieres es el punto absoluto en ese paso. Cuando asignas path[i] = target, lo que quieres es que el tipo actualice el único desplazamiento que te lleva hasta allí. El acceso indexado se interpreta como el punto en este paso y mantiene el cálculo del desplazamiento hasta el punto en un solo lugar.
Imagine que Path procede de una biblioteca. Si no eres el propietario del tipo, no puedes añadir un indexador a su código fuente. Antes de C# 15, se podían añadir métodos, pero no se podía añadir acceso indexado this[...] a un tipo sobre el que no tiene control. En este tutorial se define Path para que el ejemplo se pueda ejecutar; considérelo como si fuera ese tipo de biblioteca.
Para esta sección, añada un tipo Path. El tipo almacena una secuencia de (dX, dY) desplazamientos. Cada desplazamiento indica a qué distancia hay que desplazarse del punto anterior. El primer desplazamiento comienza en Point.Origin, la propiedad de extensión estática que has añadido anteriormente.
Cree un nuevo archivo denominado Path.cs en el mismo proyecto que los otros archivos de ejemplo. Agregue el tipo Path al espacio de nombres ExtensionMembers en ese archivo. Mantener Path en este espacio de nombres hace que sea su tipo de ejemplo, no Path. El archivo de demostración usa un using Path = ExtensionMembers.Path; alias, por lo que cada Path elemento de la demostración significa el tipo de ruta de acceso de ejemplo.
public sealed class Path
{
private readonly List<(int dX, int dY)> offsets = [];
public Path(IEnumerable<(int dX, int dY)> offsets)
{
this.offsets.AddRange(offsets);
}
public int Count => offsets.Count;
internal (int dX, int dY) GetOffset(int index) => offsets[index];
internal void SetOffset(int index, (int dX, int dY) offset) =>
offsets[index] = offset;
}
Ahora agregue un indexador para Path. Coloque este código en la clase estática existente PointExtensions en NewExtensionsMembers.cs. Agréguelo como un nuevo extension(Path path) bloque, independiente del extension(Point) bloque para los miembros y operadores estáticos y independiente del extension(ref Point point) bloque para los métodos de instancia:
extension(Path path)
{
public Point this[int index]
{
get
{
ValidatePathIndex(path, index);
Point absolutePoint = Point.Origin;
for (int current = 0; current <= index; current++)
{
var offset = path.GetOffset(current);
absolutePoint += offset;
}
return absolutePoint;
}
set
{
ValidatePathIndex(path, index);
Point previousPoint = Point.Origin;
for (int current = 0; current < index; current++)
{
var offset = path.GetOffset(current);
previousPoint += offset;
}
path.SetOffset(index, (value.X - previousPoint.X, value.Y - previousPoint.Y));
}
}
}
private static void ValidatePathIndex(Path path, int index)
{
if (index < 0 || index >= path.Count)
{
throw new ArgumentOutOfRangeException(nameof(index), index,
"Index must refer to an offset in the path.");
}
}
Los indexadores siempre son miembros de instancia, por lo que este nuevo bloque de extensión debe asignar un nombre al receptor: extension(Path path). Un bloque escrito como extension(Path) no proporcionaría una path variable para el cuerpo del indexador.
Path es una clase que posee una lista de desplazamientos. El indexador no necesita un ref receptor porque el establecedor cambia el contenido de ese objeto existente Path .
El getter comienza en Point.Origin y, a continuación, suma los desplazamientos desde el índice 0 hasta el índice solicitado. Con los desplazamientos (2, 3), (1, 1) y (-1, 4), los puntos absolutos son (2, 3), (3, 4) y (2, 8).
El setter recibe un punto absoluto de destino. Mantiene intactos los desplazamientos anteriores y solo cambia el desplazamiento correspondiente al índice solicitado. El nuevo desplazamiento es el punto de destino menos el punto absoluto correspondiente al índice anterior. Los puntos posteriores se desplazan porque permanecen relativos al desplazamiento modificado.
Ahora, use el indexador para leer y escribir puntos a lo largo de la ruta de acceso:
Console.WriteLine("5. Path Indexer");
Console.WriteLine("---------------");
Path path = new([(dX: 2, dY: 3), (dX: 1, dY: 1), (dX: -1, dY: 4)]);
Console.WriteLine($"First point: {path[0]}");
Console.WriteLine($"Second point: {path[1]}");
Console.WriteLine($"Third point: {path[2]}");
path[1] = new Point(10, 10);
Console.WriteLine("After setting the second point to {X=10,Y=10}:");
Console.WriteLine($"Second point: {path[1]}");
Console.WriteLine($"Third point: {path[2]}");
Console.WriteLine();
Ambos métodos de acceso comparten un pequeño método auxiliar privado ValidatePathIndex en la misma clase PointExtensions que comprueba que el índice esté dentro de los límites y lanza ArgumentOutOfRangeException cuando no hace referencia a un desplazamiento de la ruta.
Ejemplo completado
En el ejemplo final se muestran las ventajas al combinar propiedades estáticas, operadores, métodos de instancia y un indexador para crear extensiones de tipo completas.
Compara la versión del miembro de extensión:
Console.WriteLine("6. Complex Scenarios");
Console.WriteLine("-------------------");
// Combining operators and methods
Console.WriteLine("Scenario 1: Building a rectangle using operators");
Point topLeft = Point.Origin;
Point bottomRight = topLeft + (10, 8);
Point topRight = new Point(bottomRight.X, topLeft.Y);
Point bottomLeft = new Point(topLeft.X, bottomRight.Y);
Console.WriteLine($"Rectangle corners:");
Console.WriteLine($" Top-Left: {topLeft}");
Console.WriteLine($" Top-Right: {topRight}");
Console.WriteLine($" Bottom-Left: {bottomLeft}");
Console.WriteLine($" Bottom-Right: {bottomRight}");
Console.WriteLine();
// Transformation chain
Console.WriteLine("Scenario 2: Transformation chain");
Point transformPoint = new Point(2, 3);
Console.WriteLine($"Starting point: {transformPoint}");
// Scale up
transformPoint.Scale(3, 2);
Console.WriteLine($"After scaling by (3, 2): {transformPoint}");
// Translate
transformPoint = transformPoint + (5, -3);
Console.WriteLine($"After translating by (5, -3): {transformPoint}");
// Rotate
transformPoint.Rotate(45);
Console.WriteLine($"After rotating 45°: {transformPoint}");
// Convert to vector
Vector2 finalVector = transformPoint.ToVector();
Console.WriteLine($"Final result as Vector2: {finalVector}");
Console.WriteLine();
// Distance calculation using operators
Console.WriteLine("Scenario 3: Distance calculation");
Point point1 = new Point(1, 1);
Point point2 = new Point(4, 5);
Point difference = point2 - point1;
Vector2 diffVector = difference.ToVector();
float distance = diffVector.Length();
Console.WriteLine($"Point 1: {point1}");
Console.WriteLine($"Point 2: {point2}");
Console.WriteLine($"Difference: {difference}");
Console.WriteLine($"Distance: {distance:F2}");
Console.WriteLine();
Console.WriteLine("Demonstration complete!");
Con la versión anterior:
// Combining operators and methods
Console.WriteLine("Scenario 1: Building a rectangle using inline operators");
Point topLeft = Point.Empty; // Inline equivalent of Point.Origin
Point bottomRight = new Point(topLeft.X + 10, topLeft.Y + 8); // Inline addition
Point topRight = new Point(bottomRight.X, topLeft.Y);
Point bottomLeft = new Point(topLeft.X, bottomRight.Y);
Console.WriteLine($"Rectangle corners:");
Console.WriteLine($" Top-Left: {topLeft}");
Console.WriteLine($" Top-Right: {topRight}");
Console.WriteLine($" Bottom-Left: {bottomLeft}");
Console.WriteLine($" Bottom-Right: {bottomRight}");
Console.WriteLine();
// Transformation chain
Console.WriteLine("Scenario 2: Transformation chain (mixed methods)");
Point transformPoint = new Point(2, 3);
Console.WriteLine($"Starting point: {transformPoint}");
// Scale up - using extension method
transformPoint.Scale(3, 2);
Console.WriteLine($"After scaling by (3, 2): {transformPoint}");
// Translate - using inline addition
transformPoint = new Point(transformPoint.X + 5, transformPoint.Y + (-3));
Console.WriteLine($"After translating by (5, -3): {transformPoint}");
// Rotate - using extension method
transformPoint.Rotate(45);
Console.WriteLine($"After rotating 45�: {transformPoint}");
// Convert to vector - using extension method
Vector2 finalVector = transformPoint.ToVector();
Console.WriteLine($"Final result as Vector2: {finalVector}");
Console.WriteLine();
// Distance calculation using inline operators and extension methods
Console.WriteLine("Scenario 3: Distance calculation (mixed methods)");
Point point1 = new Point(1, 1);
Point point2 = new Point(4, 5);
Point difference = new Point(point2.X - point1.X, point2.Y - point1.Y); // Inline subtraction
Vector2 diffVector = difference.ToVector(); // Extension method
float distance = diffVector.Length();
Console.WriteLine($"Point 1: {point1}");
Console.WriteLine($"Point 2: {point2}");
Console.WriteLine($"Difference: {difference}");
Console.WriteLine($"Distance: {distance:F2}");
Console.WriteLine();
Console.WriteLine("Traditional extension methods demonstration complete!");
En este ejemplo se muestra cómo los miembros de extensión crean una API cohesiva que se siente como parte del tipo original. Ustedes pueden:
- Uso
Point.Originpara un punto de partida significativo - Aplicar operadores matemáticos naturalmente (
point + offset,point * scale) - Encadenamiento de transformaciones utilizando operadores y métodos
- Convertir entre tipos relacionados (
ToVector()) - Leer y actualizar puntos absolutos de una trayectoria con
path[index]
Ventajas de la migración
Al migrar de métodos de extensión tradicionales a miembros de extensión, obtendrá lo siguiente:
- Propiedades estáticas: agregue constantes y valores calculados a tipos.
- Operadores: habilite las operaciones matemáticas y lógicas naturales.
- Indexadores: agregue acceso indizado de C# 15 que pueda calcular valores desde un tipo existente y actualizar su estado almacenado.
-
Sintaxis unificada: toda la lógica de extensión usa la misma
extensiondeclaración. - Extensiones a nivel de tipo: Extender el tipo en sí, no solo las instancias.
Ejecute la aplicación completa para ver ambos enfoques en paralelo y observar cómo los miembros de la extensión proporcionan una experiencia de desarrollo más integrada.