Por que a estrutura .NET não tem conceito de classes como tipos de primeira classe?

20

É bem sabido para aqueles familiarizados com a história que o C # e o .NET framework começaram como "Delphi reescrito para se parecer com Java", arquitetado pelo desenvolvedor principal por trás do Delphi, Anders Hejlsberg. As coisas divergiram bastante desde então, mas no início as semelhanças eram tão óbvias que havia até algumas especulações sérias de que o .NET era realmente o produto da Borland.

Ultimamente, tenho observado algumas coisas sobre .NET, e um dos recursos mais interessantes e úteis do Delphi parece estar totalmente ausente: o conceito de classe como um tipo de dados de primeira classe. Para quem não conhece, o tipo TClassrepresenta uma referência a uma classe, semelhante ao Typetipo no .NET. Mas onde o .NET usa Typepara reflexão, o Delphi usa TClasscomo uma parte interna muito importante da linguagem. Ele permite vários idiomas úteis que simplesmente não existem e não podem existir sem ele, como variáveis ​​de subtipo de classe e métodos de classe virtual.

Toda linguagem OO possui métodos virtuais, nos quais diferentes classes implementam o mesmo conceito fundamental de um método de maneiras diferentes e, em seguida, o método correto é chamado em tempo de execução com base no tipo real da instância do objeto em que é chamado. O Delphi estende esse conceito para classes: se você tiver uma referência TClass definida como um subtipo de classe específico (ou seja, class of TMyClasssignifica que a variável pode aceitar qualquer referência de classe que herda TMyClass, mas não algo fora da hierarquia) que possui métodos virtuais de escopo de classe anexados a eles podem ser chamados sem uma instância usando o tipo real da classe. A aplicação desse padrão aos construtores torna uma implementação de fábrica trivial, por exemplo.

Parece não haver nada equivalente no .NET. Por mais úteis que sejam as referências de classe (e principalmente os construtores virtuais e outros métodos de classe virtual!), Alguém disse alguma coisa sobre por que foram deixados de fora?

Exemplos Específicos

Desserialização de formulários

O Delphi VCL salva formulários em DFMformato, um DSL para descrever uma hierarquia de componentes. Quando o leitor de formulários analisa dados do DFM, ele é executado nos objetos descritos desta maneira:

object Name: ClassName
   property = value
   property = value
   ...
   object SubObjectName: ClassName
      ...
   end
end

O interessante aqui é a ClassNameparte. Cada classe de componente registra o seu TClasscom o sistema de streaming de componente no initializationmomento (pense que os construtores estáticos, apenas um pouco diferentes, garantem que aconteça imediatamente na inicialização.) Isso registra cada classe em um mapa de hash de string-> TClass com o nome da classe como chave.

Cada componente é descendente TComponent, que possui um construtor virtual que usa um único argumento Owner: TComponent,. Qualquer componente pode substituir esse construtor para fornecer sua própria inicialização. Quando o leitor DFM lê um nome de classe, ele pesquisa o nome no hashmap mencionado acima e recupera a referência de classe correspondente (ou gera uma exceção se não estiver lá) e chama o construtor TComponent virtual, conhecido por ser bom porque a função de registro usa uma referência de classe necessária para descender do TComponent e você acaba com um objeto do tipo apropriado.

Na falta disso, o equivalente do WinForms é ... bem ... uma grande bagunça para ser franco, exigindo que qualquer nova linguagem .NET reimplemente completamente sua própria forma (des) serialização. Isso é um pouco chocante quando você pensa sobre isso; como o objetivo de ter um CLR é permitir que vários idiomas usem a mesma infraestrutura básica, um sistema no estilo DFM faria todo sentido.

Extensibilidade

Uma classe de gerenciador de imagens que escrevi pode ser fornecida com uma fonte de dados (como um caminho para seus arquivos de imagem) e carregar novos objetos de imagem automaticamente se você tentar recuperar um nome que não esteja na coleção, mas esteja disponível na fonte de dados. Possui uma variável de classe digitada como class ofa classe de imagem base, representando a classe de quaisquer novos objetos a serem criados. Ele vem com um padrão, mas há alguns pontos, ao criar novas imagens com finalidades especiais, que as imagens devem ser configuradas de maneiras diferentes. (Criando-o sem um canal alfa, recuperando metadados especiais de um arquivo PNG para especificar o tamanho do sprite etc.)

Isso pode ser feito escrevendo quantidades extensas de código de configuração e passando opções especiais para todos os métodos que podem acabar criando um novo objeto ... ou você pode simplesmente criar uma subclasse da classe de imagem base que substitui um método virtual em que o aspecto em questão é configurado e, em seguida, use um bloco try / finalmente para substituir temporariamente a propriedade "classe padrão", conforme necessário, e depois restaurá-la. Fazer isso com variáveis ​​de referência de classe é muito mais simples, e não é algo que poderia ser feito com genéricos.

Mason Wheeler
fonte
3
Você pode fornecer um ou dois exemplos concretos onde a TClassé útil, com algum código de exemplo? Na minha pesquisa superficial na Internet TClass, estou descobrindo que isso TClasspode ser passado como um parâmetro. Isso é feito no .NET usando Genéricos. Os métodos de fábrica são simplesmente marcados staticno .NET e não requerem uma instância de classe para serem executados.
Robert Harvey
7
@RobertHarvey: Para mim, pessoalmente, o C # começou a fazer muito mais sentido quando parei de vê- lo como Java / C ++ recarregado e comecei a tratá-lo como Modula-2 com objetos. É o mesmo com Java: todo mundo diz que foi influenciado pelo C ++, mas isso não é verdade. Sua principal influência é Objective-C (e Smalltalk por isso), e Java fará muito mais sentido quando você o tratar como Smalltalk com tipos em vez de C ++ com GC.
Jörg W Mittag
3
@RobertHarvey: TClassé um recurso de linguagem fundamental que requer suporte do compilador. Você não pode "escrever o seu próprio" sem escrever seu próprio idioma e, para o .NET, isso não seria suficiente porque o modelo de objeto é definido pelo CLR, não por idiomas individuais. É algo que literalmente precisa fazer parte da própria estrutura .NET, ou não pode existir.
Mason Wheeler
4
Antes de tudo, posso dizer com absoluta certeza que o .NET não era originalmente um produto "Borland". Como eu sei disso? Eu era (ainda sou) parte da equipe principal original que desenvolveu o Delphi. Trabalhei em estreita colaboração com Anders, Chuck, Gary e outros. Claro, tenho certeza que você sabe disso. Quanto à existência de uma referência de classe (como TClass e construções semelhantes são chamadas) no .NET provavelmente foi considerada desnecessária devido à existência de informações ricas do tipo acessível em tempo de execução. O Delphi tinha muito menos informações de tipo no início e os representantes de classe eram um compromisso.
Allen Bauer

Respostas:

5

O .NET (o CLR) foi a terceira geração do COM (Component Object Model) da Microsoft, que nos primeiros dias se referia ao "tempo de execução do COM +". O Microsoft Visual Basic e o mercado de controles COM / ActiveX tiveram muito mais influência nas opções específicas de compatibilidade arquitetônica do CLR do que o Borland Delphi. (É certo que o fato de o Delphi ter adotado os controles ActiveX certamente ajudou a crescer o ecossistema ActiveX, mas o COM / ActiveX existia antes do Delphi)

A arquitetura do COM foi elaborada em C (não em C ++) e focada em interfaces, em vez de classes. Além disso, a composição de objetos é suportada, o que significa que um objeto COM pode de fato ser composto de vários objetos diferentes, com a interface IUnknown os vinculando. Mas IUnknown não teve nenhum papel na criação de objetos COM, que foi projetada para ser o mais independente de idioma possível. A criação de objetos geralmente era tratada pelo IClassFactory e a reflexão pelo ITypeLibrary e interfaces relacionadas. Essa separação de preocupações era independente da linguagem de implementação e os recursos de cada interface COM principal eram mínimos e ortogonais.

Portanto, como resultado da popularidade dos controles COM e ActiveX, a arquitetura .NET foi criada para oferecer suporte no COM IUnknown, IClassFactory e ITypeLibrary. No COM, essas interfaces não estavam necessariamente no mesmo objeto, portanto, agrupá-las não fazia necessariamente sentido.

Burt_Harris
fonte