Quando devo usar o operador implícito de conversão de tipo do C #?

14

Em C #, podemos sobrecarregar o operador de conversão implícita como este (exemplo do MSDN ):

struct Digit
{
    /* ... */
    public static implicit operator byte(Digit d)  // implicit digit to byte conversion operator
    {
        /* ... */
    }
}

Assim, podemos ter um tipo, um tipo de valor personalizado , convertendo-se magicamente para outro tipo (não relacionado), deixando a platéia perplexa (até que eles olhem nos bastidores e vejam o operador implícito de conversão).

Não gosto de deixar ninguém que leia meu código com perplexidade. Eu não acho que muitas pessoas fazem.

A questão é: quais são os casos de uso do operador implícito de conversão de tipo que não tornarão meu código muito mais difícil de entender?

Mints97
fonte
1
Uau. Na verdade, eu não sabia que isso existia. Não que seja necessariamente uma coisa boa de usar; Eu sei que as pessoas ficaram realmente irritadas com esse tipo de funcionalidade oculta em C ++.
Katana314
@ Katana314: Não era disso que as pessoas se incomodavam, mas alguém adicionando uma sobrecarga (seja operador, função de conversão, construtor, função livre ou função de membro) com comportamento surpreendente, de preferência sutilmente surpreendente.
Desduplicador
Eu recomendo que você leia sobre "sobrecarga de operador" em C ++, especificamente os operadores de "conversão". Suspeito que muitos dos mesmos argumentos a favor / contra sejam os mesmos, exceto que o debate já dura três vezes desde que o C # exista com muito mais para ler.

Respostas:

18

Eu recomendaria apenas conversões implícitas entre tipos que representam aproximadamente os mesmos valores de maneiras diferentes. Por exemplo:

  • Diferentes tipos de cores, como RGB, HSL, HSVe CMYK.
  • Unidades diferentes para a mesma quantidade física ( Metervs Inch).
  • Sistemas de coordenadas diferentes (polar vs cartesiano).

No entanto, existem algumas orientações fortes que indicam quando é não adequado definir uma conversão implícita:

  • Se a conversão causar uma perda significativa de precisão ou alcance, ela não deve estar implícita (por exemplo: de float64 para float32 ou de longa para int).
  • Se a conversão puder gerar uma InvalidCastexceção ( ), não deverá estar implícita.
  • Se a conversão causar uma alocação de heap toda vez que for executada, ela não deverá estar implícita.
  • Se a conversão não for uma O(1) operação, ela não deverá estar implícita.
  • Se o tipo de origem ou o destino for mutável, a conversão não deverá estar implícita.
  • Se a conversão depender de algum tipo de contexto (banco de dados, configurações de cultura, configuração, sistema de arquivos etc.), isso não deve estar implícito (eu também desencorajaria um operador de conversão explícito nesse caso).

Agora, digamos que seu operador de conversão f: T1 -> T2não viole nenhuma das regras acima, o comportamento a seguir indica fortemente que a conversão pode estar implícita:

  • Se a == bentão f(a) == f(b).
  • Se a != bentão f(a) != f(b).
  • Se a.ToString() == b.ToString()então f(a).ToString() == f(b).ToString().
  • Etc. para outras operações definidas em ambos T1e T2.
Elian Ebbing
fonte
Todos os seus exemplos são provavelmente com perdas. Se eles são exatos o suficiente de qualquer maneira, ...
Deduplicator
Sim, eu percebi isso :-). Eu não conseguia pensar em um termo melhor para "com perdas". O que eu quis dizer com "perdas" são conversões em que o alcance ou a precisão são substancialmente reduzidos. Por exemplo, de float64 para float32 ou de long para int.
Elian Ebbing
Eu acho que a! = B => f (a)! = F (b), provavelmente não deve ser aplicado. Há uma abundância de funções que pode retornar o mesmo valor para diferentes entradas, floor () e ceil (), por exemplo, no lado da matemática
cdkMoose
@cdkMoose Você está certo, é claro, e é por isso que vejo essas propriedades mais como "pontos de bônus", não como regras. A segunda propriedade significa simplesmente que a função de conversão é injetiva. Geralmente, esse é o caso quando você converte para um tipo que possui um intervalo estritamente maior, por exemplo, de int32 para int64.
Elian Ebbing
@cdkMoose Por outro lado, a primeira propriedade apenas afirma que dois valores na mesma classe de equivalência de T1(implícita na ==relação on T1) sempre são mapeados para dois valores na mesma classe de equivalência de T2. Agora que penso nisso, acho que a primeira propriedade deve ser realmente necessária para uma conversão implícita.
Elian Ebbing 20/05
6

A questão é: quais são os casos de uso do operador implícito de conversão de tipo que não tornarão meu código muito mais difícil de entender?

Quando os tipos não estão relacionados (para programadores). Existem cenários (raros) em que você tem dois tipos não relacionados (no que diz respeito ao código), que são realmente relacionados (no que diz respeito ao domínio ou programadores razoáveis).

Por exemplo, algum código para fazer a correspondência de cadeias. Um cenário comum é corresponder a uma string literal. Em vez de chamar IsMatch(input, new Literal("some string")), uma conversão implícita permite que você se livre dessa cerimônia - o barulho no código - e concentre-se na cadeia de caracteres literal.

A maioria dos programadores verá IsMatch(input, "some string")e intuirá rapidamente o que está acontecendo. Isso torna seu código mais claro no site da chamada. Em resumo, torna um pouco mais fácil entender o que está acontecendo, com uma pequena despesa de como isso está acontecendo.

Agora, você pode argumentar que uma sobrecarga de função simples para fazer a mesma coisa seria melhor. E isso é. Mas se esse tipo de coisa é onipresente, ter uma conversão é mais limpo (menos código, maior consistência) do que fazer uma pilha de sobrecargas de funções.

E você pode argumentar que é melhor exigir que os programadores criem explicitamente o tipo intermediário para que vejam "o que realmente está acontecendo". Isso é menos direto. Pessoalmente, acho que o exemplo literal de correspondência de strings é muito claro sobre "o que realmente está acontecendo" - o programador não precisa conhecer a mecânica de como tudo acontece. Você sabe como todo o seu código é executado pelos vários processadores em que o código é executado? Sempre há uma linha de abstração em que os programadores param de se preocupar com o funcionamento de algo. Se você acha que as etapas implícitas de conversão são importantes, não use a conversão implícita. Se você acha que eles são apenas uma cerimônia para manter o computador feliz, e o programador seria melhor não ver esse barulho em todos os lugares,

Telastyn
fonte
Seu último ponto pode e deve ser levado ainda mais longe: há também uma linha além da qual um programador se importava muito melhor em não se importar com o que é feito, porque isso não é contratual.
Deduplicator