Existe alguma maneira em c # para substituir um método de classe com um método de extensão?

98

Houve ocasiões em que eu desejaria substituir um método em uma classe por um método de extensão. Existe alguma maneira de fazer isso em C #?

Por exemplo:

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

Um caso em que eu queria fazer isso é ser capaz de armazenar um hash de uma string em um banco de dados e fazer com que o mesmo valor seja usado por todas as classes que usam o hash da classe de string (isto é, Dicionário, etc.). O algoritmo de hashing .Net integrado não tem garantia de compatibilidade de uma versão do Framework para a próxima, desejo substituí-lo pela minha.

Há outro caso que encontrei em que também gostaria de substituir um método de classe por um método de extensão, portanto, não é apenas específico para a classe de string ou o método GetHashCode.

Eu sei que poderia fazer isso com a subclasse de uma classe existente, mas seria útil poder fazer isso com uma extensão em muitos casos.

Phred Menyhert
fonte
Visto que um Dicionário é uma estrutura de dados na memória, que diferença faz se o algoritmo de hash muda de uma versão da estrutura para a próxima? Se a versão do framework mudar, obviamente o aplicativo foi reiniciado e o dicionário foi reconstruído.
David Nelson

Respostas:

91

Não; um método de extensão nunca tem prioridade sobre um método de instância com uma assinatura adequada e nunca participa de polimorfismo ( GetHashCodeé um virtualmétodo).

Marc Gravell
fonte
"... nunca participa de polimorfismo (GetHashCode é um método virtual)" Você poderia explicar de outra forma por que um método de extensão nunca participaria de polimorfismo por ser virtual? Estou tendo problemas para ver a conexão entre essas duas declarações.
Alex
3
@Alex, ao mencionar virtual, estou simplesmente esclarecendo o que significa ser polimórfico. Em praticamente todos os usos de GetHashCode, o tipo concreto é desconhecido - portanto, o polimorfismo está em jogo. Como tal, os métodos de extensão não ajudariam mesmo se tivessem prioridade no compilador regular. O que o OP realmente quer é o monkey-patching. Quais c # e .net não facilitam.
Marc Gravell
4
Isso é triste, em C # tantos métodos enganosos e sem sentido, como, por exemplo, .ToString()em matrizes. Definitivamente, é um recurso necessário.
Hi-Angel,
Por que você não obtém um erro do compilador então? Por exemplo, eu digito. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } }
LowLevel
@LowLevel, por que você faria isso?
Marc Gravell
7

Se o método tiver uma assinatura diferente, isso pode ser feito - no seu caso: não.

Caso contrário, você precisará usar herança para fazer o que está procurando.

Chris Brandsma
fonte
2

Pelo que eu sei, a resposta é não, porque um método de extensão não é uma instância. É mais como um recurso do intellisense para mim que permite chamar um método estático usando uma instância de uma classe. Acho que uma solução para o seu problema pode ser um interceptor que intercepte a execução de um método específico (por exemplo, GetHashCode ()) e faça outra coisa. Para usar tal interceptor (como o fornecido pelo Castle Project), todos os objetos devem ser instanciados usando um fábrica de objetos (ou um contêiner IoC em Castle) para que suas interfaces possam ser interceptadas por meio de um proxy dinâmico gerado em tempo de execução. (Caslte também permite interceptar membros virtuais de classes)

Beatles1692
fonte
0

Eu descobri uma maneira de invocar um método de extensão com a mesma assinatura de um método de classe, mas não parece muito elegante. Ao brincar com os métodos de extensão, notei alguns comportamentos não documentados. Código de amostra:

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();
    }
}

O que percebi foi que, se chamasse um segundo método de dentro de um método de extensão, ele chamaria o método de extensão que correspondia à assinatura, mesmo que houvesse um método de classe que também correspondesse à assinatura. Por exemplo, no código acima, quando chamo ExtFunc (), que por sua vez chama ele.GetDesc (), obtenho a string de retorno "Extension GetDesc" em vez da string "Base GetDesc" que esperaríamos.

Testando o 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

Isso permite que você alterne entre métodos de classe e métodos de extensão à vontade, mas requer a adição de métodos de "passagem" duplicados para chegar ao "escopo" desejado. Estou chamando de escopo aqui por falta de uma palavra melhor. Espero que alguém possa me informar como realmente se chama.

Você deve ter adivinhado por meus nomes de método de "passagem" que também brinquei com a ideia de passar delegados para eles na esperança de que um único método ou dois pudessem atuar como passagem para vários métodos com a mesma assinatura. Infelizmente, não deveria ser assim, uma vez que o delegado era descompactado, ele sempre escolhia o método de classe em vez do método de extensão, mesmo dentro de outro método de extensão. "Escopo" não importava mais. Eu não usei delegados Action e Func muito embora, então talvez alguém mais experiente possa descobrir essa parte.

DanK
fonte