Por que os métodos mágicos foram implementados em C #?


16

Em C #, comecei a ver todos esses métodos mágicos aparecendo, sem ter o backup de uma interface. Por que isso foi escolhido?

Deixe-me explicar.

Anteriormente em C #, se um objeto implementasse a IEnumerableinterface, ele seria automaticamente iterável por um foreachloop. Isso faz sentido para mim, uma vez que é apoiado por uma interface, e se eu tivesse minha própria Iteratorfunção dentro da classe sendo iterada, eu poderia fazer isso sem me preocupar com o significado mágico de outra coisa.

Agora, aparentemente, (não tenho certeza quando), essas interfaces não são mais necessárias. Ele só precisa ter as conversões de nomenclatura corretas.

Outro exemplo é tornar qualquer objeto aguardável por ter um método nomeado exatamente GetAwaiter com algumas propriedades específicas.

Por que não criar uma interface como eles fizeram com IEnumerableou INotifyPropertyChangedapoiar esta "mágica" estaticamente?

Mais detalhes sobre o que quero dizer aqui:

http://blog.nem.ec/2014/01/01/magic-methods-c-sharp/

Quais são os prós e os contras dos métodos mágicos, e há algum lugar online onde eu possa encontrar alguma coisa sobre por que essas decisões foram tomadas?


4
Você deve editar sua opinião pessoal sobre o porquê "a mágica é ruim" se não quiser ser fechada.
DougM 13/08/14

2
Se você quiser saber por que Anders Hejlsberg fez isso, terá que perguntar a ele. Só posso lhe dizer por que eu teria feito isso, e é para compatibilidade com versões anteriores. Os métodos de extensão permitem "falsificar" a adição de métodos aos tipos existentes, mas não há interfaces de extensão. Se você precisar de uma interface para, digamos, async/ await, isso funcionará apenas com o código que foi escrito após o .NET 4.5 ter se espalhado o suficiente para ser um destino viável ... o que é basicamente agora. Mas uma tradução puramente sintática em chamadas de método permite adicionar awaitfuncionalidade aos tipos existentes após o fato.
Jörg W Mittag

2
Observe que isso é basicamente aplicado à orientação a objetos: desde que um objeto responda às mensagens apropriadas, ele será considerado do tipo correto.
Jörg W Mittag

7
Seu "anteriormente" e "agora" são invertidos - o método mágico foi implementado como a funcionalidade básica de um foreachloop no início. Há não tem sido uma exigência para o objeto a implementar IEnumerablepara foreacha obra. Foi apenas uma convenção fazer isso.
Jesse C. Slicer

2
@gbulmer é aqui que me lembro de ter recebido a ideia: blogs.msdn.com/b/ericlippert/archive/2011/06/30/… (e em menor grau ericlippert.com/2013/07/22/… )
Jesse C. Slicer

Respostas:


16

Em geral, “métodos mágicos” são usados ​​quando não é possível criar uma interface que funcione da mesma maneira.

Quando foreachfoi introduzido pela primeira vez no C # 1.0 (esse comportamento certamente não é nada recente), ele teve que usar métodos mágicos, porque não havia genéricos. As opções eram basicamente:

  1. Use o não genérico IEnumerablee IEnumeratorque funcione com objects, o que significa tipos de valor de boxe. Como iterar algo como uma lista de ints deve ser muito rápido e certamente não deve criar muitos valores em caixas de lixo, essa não é uma boa escolha.

  2. Aguarde genéricos. Isso provavelmente significaria adiar o .Net 1.0 (ou pelo menos foreach) por mais de 3 anos.

  3. Use métodos mágicos.

Por isso, eles escolheram a opção 3 e ela permaneceu conosco por motivos de compatibilidade com versões anteriores, mesmo que desde o .Net 2.0, a solicitação IEnumerable<T>também tivesse funcionado.


Os inicializadores de coleção podem parecer diferentes em cada tipo de coleção. Compare List<T>:

public void Add(T item)

e Dictionary<TKey, TValue>:

public void Add(TKey key, TValue value)

Você não pode ter uma única interface que suporte apenas o primeiro formulário para List<T>e apenas o segundo para Dictionary<TKey, TValue>.


Os métodos LINQ são geralmente implementados como métodos de extensão (para que possa haver apenas uma única implementação, por exemplo, LINQ to Object, para todos os tipos que implementam IEnumerable<T>), o que significa que não é possível usar uma interface.


Para await, o GetResult()método pode retornar um voidou algum tipo T. Novamente, você não pode ter uma única interface que possa lidar com ambos. Embora awaitseja parcialmente baseado em interface: o garçom precisa implementar INotifyCompletione também pode implementar ICriticalNotifyCompletion.


1
Você tem certeza "eles escolheram a opção 3" para C # 1.0? Minha lembrança é que era a opção 1. Minha memória é que o C # reivindicou superioridade significativa sobre Java porque o compilador C # fez 'auto-boxing' e 'auto-unboxing'. Foi alegado que o C # fez códigos como esse para cada trabalho muito melhor do que o Java em parte por causa disso.
precisa saber é o seguinte

1
A documentação do foreachC # 1.2 (não há nada no MSDN para C # 1.0) diz que a expressão foreach“Avalia para um tipo que implementa IEnumerableou que declara um GetEnumeratormétodo”. E não tenho certeza do que você quer dizer com isso, unboxing em c # é sempre explícito.
svick

1
@gbulmer E a especificação ECMA para C # de dezembro de 2001 (que significa que é para C # 1.0) também diz que (§15.8.4): “Um tipo C é considerado um tipo de coleção se implementar a interface System.IEnumerable ou implementa o padrão de coleta atendendo a todos os seguintes critérios […] ”.
svick

1
O @svick foreach(T x in sequence)aplica conversões explícitas Tnos elementos da sequência. Portanto, se a sequência é simples IEnumerablee Té um tipo de valor, ela será unboxada sem que nenhuma conversão explícita seja escrita no seu código. Uma das partes mais feias do C #.
precisa saber é o seguinte

@CodesInChaos "Desembalagem automática" parece bastante geral, não percebi que fazia referência a esse recurso específico. (Acho que eu deveria ter, uma vez que estávamos falando foreach.)
svick
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.