¿Hay alguna forma en C # de anular un método de clase con un método de extensión?

98

Ha habido ocasiones en las que me gustaría anular un método en una clase con un método de extensión. ¿Hay alguna forma de hacer eso en C #?

Por ejemplo:

public static class StringExtension
{
    public static int GetHashCode(this string inStr)
    {
        return MyHash(inStr);
    }
}

Un caso en el que he querido hacer esto es poder almacenar un hash de una cadena en una base de datos y hacer que el mismo valor sea utilizado por todas las clases que usan el hash de la clase de cadena (es decir, Diccionario, etc.) No se garantiza que el algoritmo hash .Net incorporado sea compatible de una versión del Framework a la siguiente, quiero reemplazarlo por el mío.

Hay otro caso con el que me he encontrado en el que también me gustaría anular un método de clase con un método de extensión, por lo que no es solo específico de la clase de cadena o el método GetHashCode.

Sé que podría hacer esto con subclases de una clase existente, pero sería útil poder hacerlo con una extensión en muchos casos.

Phred Menyhert
fuente
Dado que un diccionario es una estructura de datos en memoria, ¿qué diferencia hay si el algoritmo hash cambia de una versión del marco a la siguiente? Si la versión del marco cambia, entonces obviamente la aplicación se ha reiniciado y el diccionario se ha reconstruido.
David Nelson

Respuestas:

91

No; un método de extensión nunca tiene prioridad sobre un método de instancia con una firma adecuada y nunca participa en el polimorfismo ( GetHashCodees un virtualmétodo).

Marc Gravell
fuente
"... nunca participa en polimorfismo (GetHashCode es un método virtual)" ¿Podría explicar de otra manera por qué un método de extensión nunca participaría en polimorfismo debido a que es virtual? Tengo problemas para ver la conexión con esas dos declaraciones.
Alex
3
@Alex al mencionar virtual, simplemente estoy aclarando lo que significa ser polimórfico. En prácticamente todos los usos de GetHashCode, el tipo concreto es desconocido, por lo que el polimorfismo está en juego. Como tal, los métodos de extensión no ayudarían incluso si tuvieran prioridad en el compilador normal. Lo que el OP realmente quiere es parchear monos. Que c # y .net no facilitan.
Marc Gravell
4
Eso es triste, en C # hay tantos métodos engañosos sin sentido, como por ejemplo .ToString()en matrices. Definitivamente es una característica necesaria.
Hi-Angel
Entonces, ¿por qué no aparece un error del compilador? Por ejemplo, escribo. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } }
LowLevel
@LowLevel ¿por qué lo harías?
Marc Gravell
7

Si el método tiene una firma diferente, entonces se puede hacer, así que en su caso: no.

Pero de lo contrario, debe usar la herencia para hacer lo que está buscando.

Chris Brandsma
fuente
2

Hasta donde yo sé, la respuesta es no, porque un método de extensión no es una instancia, para mí es más como una función intellisense que te permite llamar a un método estático usando una instancia de una clase. Creo que una solución a su problema puede ser un interceptor que intercepte la ejecución de un método específico (por ejemplo, GetHashCode ()) y haga otra cosa. Para usar un interceptor de este tipo (como el que proporciona Castle Project), todos los objetos deben instalarse usando un fábrica de objetos (o un contenedor de IoC en Castle) para que sus interfaces puedan ser interceptadas a través de un proxy dinámico generado en tiempo de ejecución (Caslte también le permite interceptar miembros virtuales de clases)

Beatles1692
fuente
0

He encontrado una forma de invocar un método de extensión con la misma firma que un método de clase, sin embargo, no parece muy elegante. Al jugar con los métodos de extensión, noté un comportamiento indocumentado. Código de muestra:

public static class TestableExtensions
{
    public static string GetDesc(this ITestable ele)
    {
        return "Extension GetDesc";
    }

    public static void ValDesc(this ITestable ele, string choice)
    {
        if (choice == "ext def")
        {
            Console.WriteLine($"Base.Ext.Ext.GetDesc: {ele.GetDesc()}");
        }
        else if (choice == "ext base" && ele is BaseTest b)
        {
            Console.WriteLine($"Base.Ext.Base.GetDesc: {b.BaseFunc()}");
        }
    }

    public static string ExtFunc(this ITestable ele)
    {
        return ele.GetDesc();
    }

    public static void ExtAction(this ITestable ele, string choice)
    {
        ele.ValDesc(choice);
    }
}

public interface ITestable
{

}

public class BaseTest : ITestable
{
    public string GetDesc()
    {
        return "Base GetDesc";
    }

    public void ValDesc(string choice)
    {
        if (choice == "")
        {
            Console.WriteLine($"Base.GetDesc: {GetDesc()}");
        }
        else if (choice == "ext")
        {
            Console.WriteLine($"Base.Ext.GetDesc: {this.ExtFunc()}");
        }
        else
        {
            this.ExtAction(choice);
        }
    }

    public string BaseFunc()
    {
        return GetDesc();
    }
}

Lo que noté fue que si llamaba a un segundo método desde dentro de un método de extensión, llamaría al método de extensión que coincidía con la firma, incluso si había un método de clase que también coincidía con la firma. Por ejemplo, en el código anterior, cuando llamo a ExtFunc (), que a su vez llama a ele.GetDesc (), obtengo la cadena de retorno "Extension GetDesc" en lugar de la cadena "Base GetDesc" que esperaríamos.

Probando el código:

var bt = new BaseTest();
bt.ValDesc("");
//Output is Base.GetDesc: Base GetDesc
bt.ValDesc("ext");
//Output is Base.Ext.GetDesc: Extension GetDesc
bt.ValDesc("ext def");
//Output is Base.Ext.Ext.GetDesc: Extension GetDesc
bt.ValDesc("ext base");
//Output is Base.Ext.Base.GetDesc: Base GetDesc

Esto le permite ir y venir entre métodos de clase y métodos de extensión a voluntad, pero requiere la adición de métodos de "paso a través" duplicados para llegar al "alcance" que desea. Lo llamo alcance aquí a falta de una palabra mejor. Espero que alguien me pueda decir cómo se llama realmente.

Es posible que haya adivinado por los nombres de mis métodos de "paso a través" que también jugué con la idea de pasarles delegados con la esperanza de que uno o dos métodos pudieran actuar como un paso para varios métodos con la misma firma. Desafortunadamente, no fue así, ya que una vez que se descomprimió el delegado, siempre eligió el método de clase sobre el método de extensión, incluso desde dentro de otro método de extensión. El "alcance" ya no importaba. Sin embargo, no he usado mucho a los delegados de Action y Func, así que tal vez alguien con más experiencia pueda resolver esa parte.

Húmedo
fuente