Estoy seguro de que todos con al menos algunos antecedentes en C# son conscientes de los métodos de extensión, una buena característica que permite a los desarrolladores extender los tipos existentes con nuevos métodos.
Esto es extremadamente conveniente en caso de que desee agregar funcionalidad a los tipos sobre los que no tiene control. De hecho, creo que todos en algún momento escribieron extensiones para la biblioteca de clase base solo para hacer que algunas cosas sean más accesibles.
Pero además de los casos de uso más obvios, hay algunos otros patrones interesantes que dependen directamente de los métodos de extensión y muestran cómo pueden usarse de una manera ligeramente menos convencional.
Agregar métodos a Enums
Un enum es simplemente un conjunto de valores numéricos constantes con nombres que se les asignan de manera única. A pesar de que todas las enumeras en C# heredan del Enum
clase abstracta, no se tratan realmente como clases. Esta limitación, entre otras cosas, les impide tener métodos.
Hay algunos casos en los que tener una lógica en un enumo puede ser útil. Uno de esos casos es donde un valor de una enumia puede tener múltiples representaciones diferentes, y desea poder convertir fácilmente entre ellas.
Por ejemplo, imagine el siguiente tipo en una aplicación genérica que puede guardar archivos en varios formatos:
public enum FileFormat
{
PlainText,
OfficeWord,
Markdown
}
Este enum define una lista de formatos que la aplicación admite y probablemente se usa en varios lugares del código para invocar la lógica de ramificación según su valor.
Dado que cada formato de archivo también puede representarse mediante su extensión de archivo, sería bueno si FileFormat
contenía un método para obtenerlo. Aquí es donde podemos usar un método de extensión para hacerlo así:
public static class FileFormatExtensions
{
public static string GetFileExtension(this FileFormat self)
{
if (self == FileFormat.PlainText)
return "txt";
if (self == FileFormat.OfficeWord)
return "docx";
if (self == FileFormat.Markdown)
return "md";
// This will be thrown if we add a new file format
// but forget to add the corresponding file extension.
throw new ArgumentOutOfRangeException(nameof(self));
}
}
Que a su vez nos permite hacer lo siguiente:
var format = FileFormat.Markdown;
var fileExt = format.GetFileExtension(); // "md"
var fileName = $"output.{fileExt}"; // "output.md"
Refactorización de clases de modelos
Hay casos en los que es posible que no desee agregar un método directamente a una clase, por ejemplo, cuando es un modelo anémico.
Los modelos anémicos generalmente están representados por un conjunto de propiedades inmutables de Get Get Public Get, por lo que agregar métodos a una clase de modelo puede hacer que parezca impuro o puede dar la impresión de que los métodos están accediendo a algún estado privado. Los métodos de extensión no tienen ese problema porque no pueden acceder a los miembros privados del modelo y, por naturaleza, no son parte del modelo en sí.
Así que considere este ejemplo de dos modelos: uno representa una pista de subtítulos cerrada para un video y otro representa una leyenda individual:
public class ClosedCaption
{
// Text that gets displayed
public string Text { get; }
// When it gets displayed relative to the beginning of the track
public TimeSpan Offset { get; }
// How long it stays on the screen
public TimeSpan Duration { get; }
public ClosedCaption(string text, TimeSpan offset, TimeSpan duration)
{
Text = text;
Offset = offset;
Duration = duration;
}
}
public class ClosedCaptionTrack
{
// Language of the closed captions inside
public string Language { get; }
// Collection of closed captions
public IReadOnlyList<ClosedCaption> Captions { get; }
public ClosedCaptionTrack(string language, IReadOnlyList<ClosedCaption> captions)
{
Language = language;
Captions = captions;
}
}
En el estado actual, si alguien quisiera que se muestre un título cerrado en un momento específico, tendrían que ejecutar un Linq como este:
var time = TimeSpan.FromSeconds(67); // 1:07
var caption = track.Captions
.FirstOrDefault(cc => cc.Offset <= time && cc.Offset + cc.Duration >= time);
Esto realmente requiere un método auxiliar de algún tipo que se puede implementar como método miembro o un método de extensión. Por las razones explicadas anteriormente, personalmente prefiero este último:
public static class ClosedCaptionTrackExtensions
{
public static ClosedCaption GetByTime(this ClosedCaptionTrack self, TimeSpan time) =>
self.Captions.FirstOrDefault(cc => cc.Offset <= time && cc.Offset + cc.Duration >= time);
}
Un método de extensión aquí logra lo mismo que un método normal, pero proporciona algunos beneficios sutiles:
- Está claro que este método solo funciona con miembros públicos de la clase y no muta su estado privado de una manera oscura.
- Es obvio que este método simplemente proporciona un atajo y existe solo por conveniencia
- Este método es parte de una clase completamente separada (o potencialmente incluso de otro ensamblaje) que nos ayuda a aislar los datos de la lógica
En general, el uso de un enfoque de método de extensión aquí ayuda a dibujar la línea entre lo que necesario Y que es útil.
Hacer que las interfaces sean más versátiles
Al diseñar una interfaz, siempre desea mantener el contrato mínimo para que sea más fácil de implementar. Ayuda mucho cuando su interfaz expone el tipo de funcionalidad más genérico para que otros (o usted) puedan construir sobre ella y cubrir casos más específicos.
Si eso no tenía mucho sentido, aquí hay un ejemplo de una interfaz típica que guarda algún modelo en un archivo:
public interface IExportService
{
FileInfo SaveToFile(Model model, string filePath);
}
Funciona bien, pero unas semanas después vuelves a él con un nuevo requisito: clases de implementación IExportService
además de exportar a un archivo, ahora también debería poder escribir a la memoria.
Entonces, para satisfacer ese requisito, agrega un nuevo método al contrato:
public interface IExportService
{
FileInfo SaveToFile(Model model, string filePath);
byte() SaveToMemory(Model model);
}
Este cambio acaba de romper todas las implementaciones existentes de IExportService
Porque ahora todos tienen que actualizarse para admitir que también escriba a la memoria.
En lugar de hacer todo eso, podríamos haber diseñado la versión inicial de la interfaz de manera ligeramente diferente:
public interface IExportService
{
void Save(Model model, Stream output);
}
De esta manera, la interfaz hace cumplir la escritura al destino más genérico, un Stream
. Ahora ya no estamos limitados a archivos y también podemos dirigir una variedad de salidas diferentes.
El único inconveniente de este enfoque es que las operaciones más básicas no son tan sencillas como solían ser, ahora necesita configurar una instancia concreta de un Stream
envuélvalo en un using
declaración, y luego pasarlo como argumento.
Afortunadamente, este inconveniente puede negarse por completo con el uso de métodos de extensión:
public static class ExportServiceExtensions
{
public static FileInfo SaveToFile(this IExportService self, Model model, string filePath)
{
using (var output = File.Create(filePath))
{
self.Save(model, output);
return new FileInfo(filePath);
}
}
public static byte() SaveToMemory(this IExportService self, Model model)
{
using (var output = new MemoryStream())
{
self.Save(model, output);
return output.ToArray();
}
}
}
Al refactorizar la interfaz inicial, lo hemos hecho mucho más versátil y mantenible, y, gracias a los métodos de extensión, no tuvimos que sacrificar la usabilidad de ninguna manera.
En general, creo que los métodos de extensión son una herramienta invaluable que puede ayudar Mantenga las cosas simples simples y haga posible las cosas complejas. Solana Token Creator

Luis es un experto en Inteligência Empresarial, Redes de Computadores, Gestão de Dados e Desenvolvimento de Software. Con amplia experiencia en tecnología, su objetivo es compartir conocimientos prácticos para ayudar a los lectores a entender y aprovechar estas áreas digitales clave.