Sobrecarga de operador com métodos de extensão C #

174

Estou tentando usar métodos de extensão para adicionar uma sobrecarga de operador à StringBuilderclasse C # . Especificamente, StringBuilder sbeu gostaria de sb += "text"me tornar equivalente a sb.Append("text").

Aqui está a sintaxe para criar um método de extensão para StringBuilder:

public static class sbExtensions
{
    public static StringBuilder blah(this StringBuilder sb)
    {
        return sb;
    }
} 

Ele adiciona com êxito o blahmétodo de extensão ao arquivo StringBuilder.

Infelizmente, a sobrecarga do operador não parece funcionar:

public static class sbExtensions
{
    public static StringBuilder operator +(this StringBuilder sb, string s)
    {
        return sb.Append(s);
    }
} 

Entre outros problemas, a palavra this- chave não é permitida neste contexto.

É possível adicionar sobrecargas do operador por meio de métodos de extensão? Se sim, qual é a maneira correta de fazer isso?

Jude Allred
fonte
4
Embora isso inicialmente pareça uma idéia interessante, considere var otherSb = sb + "hi";
hatchet - feito com SOverflow

Respostas:

150

No momento, isso não é possível, porque os métodos de extensão devem estar em classes estáticas e as classes estáticas não podem ter sobrecargas de operador.

Mads Torgersen, C # Language PM diz:

... para o lançamento do Orcas, decidimos adotar uma abordagem cautelosa e adicionar apenas métodos de extensão regulares, em vez de propriedades de extensão, eventos, operadores, métodos estáticos, etc. etc. um design sintaticamente mínimo que não pôde ser facilmente imitado para alguns dos outros tipos de membros.

Estamos cada vez mais conscientes de que outros tipos de membros de extensão podem ser úteis e, portanto, retornaremos a esse problema após o Orcas. Sem garantias, no entanto!

Editar:

Acabei de notar que Mads escreveu mais no mesmo artigo :

Lamento informar que não faremos isso no próximo lançamento. Levamos os membros da extensão muito a sério em nossos planos, e gastamos muito esforço tentando acertá-los, mas no final não conseguimos acertar o suficiente e decidimos dar lugar a outros recursos interessantes.

Isso ainda está em nosso radar para lançamentos futuros. O que ajudará é se obtivermos uma boa quantidade de cenários convincentes que podem ajudar a impulsionar o design certo.


Atualmente, esse recurso está na tabela (potencialmente) para C # 8.0. Mads fala um pouco mais sobre como implementá-lo aqui .

Jacob Krall
fonte
Esta página já foi retirada; esse problema ainda não foi resolvido.
Chris Moschini
17
Que pena. Eu só queria adicionar um operador para multiplicar um TimeSpan por um valor escalar ... :(
Filip Skakun
Eu esperava implementar esse mesmo conceito para converter um Stringno PowerShell ScriptBlock.
Trevor Sullivan
Isso significa que não posso sobrecarregar% para ser xou entre booleanos? :( morto true.Xor(false)então
Spark
3
O @SparK ^é o operador xor em c # #
Jacob Krall
57

Se você controlar os locais onde deseja usar esse "operador de extensão" (o que você normalmente faz com os métodos de extensão), poderá fazer algo assim:

class Program {

  static void Main(string[] args) {
    StringBuilder sb = new StringBuilder();
    ReceiveImportantMessage(sb);
    Console.WriteLine(sb.ToString());
  }

  // the important thing is to use StringBuilderWrapper!
  private static void ReceiveImportantMessage(StringBuilderWrapper sb) {
    sb += "Hello World!";
  }

}

public class StringBuilderWrapper {

  public StringBuilderWrapper(StringBuilder sb) { StringBuilder = sb; }
  public StringBuilder StringBuilder { get; private set; }

  public static implicit operator StringBuilderWrapper(StringBuilder sb) {
    return new StringBuilderWrapper(sb);
  }

  public static StringBuilderWrapper operator +(StringBuilderWrapper sbw, string s) { 
      sbw.StringBuilder.Append(s);
      return sbw;
  }

} 

A StringBuilderWrapperclasse declara um operador de conversão implícito de a StringBuilder e declara o +operador desejado . Dessa forma, StringBuilderpode-se passar um para ReceiveImportantMessage, que será convertido silenciosamente em um StringBuilderWrapper, onde o +operador pode ser usado.

Para tornar esse fato mais transparente para os chamadores, você pode declarar ReceiveImportantMessagecomo tendo um StringBuildere apenas usar código como este:

  private static void ReceiveImportantMessage(StringBuilder sb) {
    StringBuilderWrapper sbw = sb;
    sbw += "Hello World!";
  }

Ou, para usá-lo inline onde você já está usando a StringBuilder, você pode simplesmente fazer o seguinte:

 StringBuilder sb = new StringBuilder();
 StringBuilderWrapper sbw = sb;
 sbw += "Hello World!";
 Console.WriteLine(sb.ToString());

Eu criei um post sobre o uso de uma abordagem semelhante para tornar IComparablemais compreensível.

Jordão
fonte
2
@ Leon: eu realmente pretendia compor, não herdar. Enfim, eu não poderia herdar dele, pois está selado.
Jordão
5
@ Leon: Esse é o coração desta técnica. Eu posso fazer isso porque há um operador de conversão implícito declarado StringBuilderWrapperque torna isso possível.
Jordão
1
@pylover: Você está certo, isso requer a criação de um novo tipo, que envolva o StringBuildertipo e forneça um operador de conversão implícito. Depois disso, ele pode ser usado com literais de string, como demonstrado no exemplo:sb += "Hello World!";
Jordão
2
Posso sugerir, então, adicionar um método de extensão a String: PushIndent(" ".X(4))(também poderia ser chamado Times). Ou talvez usando esse construtor: PushIndent(new String(' ', 4)).
Jordão
1
@ Jordão: Great answer;)
Vinicius
8

Parece que isso não é possível no momento - há um problema de feedback aberto solicitando esse mesmo recurso no Microsoft Connect:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=168224

sugerindo que ele pode aparecer em uma versão futura, mas não está implementado para a versão atual.

Dylan Beattie
fonte
O que exatamente você quer dizer com "atualmente não é possível?" Deve ser possível no CLR porque o F # suporta a extensão de tudo.
Matthew Olenik
1
Eu acho que ele quer dizer que não é possível em C #, não no CLR. Todo o método dos métodos de extensão é um truque do compilador C # de qualquer maneira.
tofi9 31/05
1
Link está morto agora.
precisa saber é o seguinte
1

Embora não seja possível executar os operadores, você sempre pode criar os métodos Adicionar (ou Concat), Subtrair e Comparar ....

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;    

namespace Whatever.Test
{
    public static class Extensions
    {
        public static int Compare(this MyObject t1, MyObject t2)
        {
            if(t1.SomeValueField < t2.SomeValueField )
                return -1;
            else if (t1.SomeValueField > t2.SomeValueField )
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }

        public static MyObject Add(this MyObject t1, MyObject t2)
        {
            var newObject = new MyObject();
            //do something  
            return newObject;

        }

        public static MyObject Subtract(this MyObject t1, MyObject t2)
        {
            var newObject= new MyObject();
            //do something
            return newObject;    
        }
    }


}
Chuck Rostance
fonte
1

Hah! Eu estava procurando "operador de extensão sobrecarregando" com exatamente o mesmo desejo, por sb + = (coisa).

Depois de ler as respostas aqui (e ver que a resposta é "não"), para minhas necessidades específicas, fui com um método de extensão que combina sb.AppendLine e sb.AppendFormat e parece mais organizado do que qualquer um.

public static class SomeExtensions
{
    public static void Line(this StringBuilder sb, string format, params object[] args)
    {
        string s = String.Format(format + "\n", args);
        sb.Append(s);
    }

}

E entao,

sb.Line("the first thing is {0}",first);
sb.Line("the second thing is {0}", second);

Não é uma resposta geral, mas pode ser de interesse para futuros candidatos que analisam esse tipo de coisa.

david van brink
fonte
4
Eu acho que seu método de extensão seria melhor se você o nomeasse em AppendLinevez de Line.
DavidRR
0

É possível equipá-lo com um invólucro e extensões, mas impossível fazê-lo corretamente. Você termina com o lixo que derrota totalmente o objetivo. Eu tenho um post aqui em algum lugar que faz isso, mas é inútil.

Btw Todas as conversões numéricas criam lixo no construtor de strings que precisa ser corrigido. Eu tive que escrever um invólucro para o que funciona e eu o uso. Vale a pena ler isso.

motil
fonte