Problemas de búho y globo: conectando dos ensamblajes con espacios de nombres y nombres de clase idénticos


Esta noche, gelas comenzó una conversación sobre cómo los administradores de paquetes trabajan en diferentes plataformas. Durante la conversación, llegamos a un debate sobre la situación en la que necesita conectar dos bibliotecas al proyecto .NET Core que contienen clases con el mismo nombre en el mismo espacio de nombres. Como estoy haciendo bastante de .NET Core, quería comprobar cómo se puede resolver este problema. Lo que vino de esto se describe más adelante


Descargo de responsabilidad . ¿Estas situaciones ocurren con frecuencia? Durante más de 10 años de trabajo con .NET, nunca he tenido que lidiar con una situación similar en un proyecto real. Pero el experimento fue interesante.


Por si acaso, aclararé que realizaré el experimento usando:


  • macOS 10.13,
  • .NET Core SDK 2.1.302
  • Jinete 2018.2

Entonces, simularemos una situación cuando tengamos dos bibliotecas que tengan las clases que necesitamos y que debemos usar en nuestro proyecto. Al mismo tiempo, no tenemos acceso al código fuente, pero no podemos descompilar los ensamblados para cambiar el espacio de nombres en ellos, y tampoco podemos volver a compilarlos.


Preparación del experimento


Y así, para empezar, prepara un búho y dos globos. Como búho, tendremos un proyecto dirigido a netcoreapp2.1. Crearemos dos proyectos como globos, uno de los cuales también estará dirigido a netcoreapp2.1, y el segundo a netstandard2.0



En cada proyecto, colocamos la clase Globe, que estará en espacios de nombres idénticos, pero tendrán implementaciones diferentes:


Primer archivo:


using System; namespace Space { public class Globe { public string GetColor() => "Green"; } } 

Segundo archivo:


 using System; namespace Space { public class Globe { public string GetColor() => "Blue"; } } 

Intento número uno


Dado que, de acuerdo con las condiciones del problema, debemos trabajar con ensamblajes externos y no con proyectos, agregaremos enlaces al proyecto en consecuencia, como si realmente fueran solo bibliotecas. Para hacer esto, primero compile todos los proyectos para que tengamos el Globe1.dll y el Globe2.dll que necesitamos. Luego agregue enlaces a ellos en el proyecto de la siguiente manera:



Ahora intente crear una variable de clase Globe:



Como puede ver, ya en esta etapa, el IDE nos advierte que hay un problema para entender de dónde debería tomarse la clase Globe que necesitamos.


Al principio parece que la situación es bastante típica y ya debería haber una respuesta preparada, fundida en granito, al desbordamiento de pila. Al final resultó que, todavía no se ha propuesto una solución a este problema para .NET Core. O mi Google me decepcionó. Pero logré encontrar algo útil en Stack Overflow. La única publicación sensata que logré googlear fue en 2006 y describió una situación similar para la versión clásica de .NET. En este caso, se discute un problema muy similar en el repositorio del proyecto NuGet .


Hasta ahora, no hay mucha información útil, pero todavía está allí:


  • En la versión clásica de .NET, se implementó un mecanismo de alias
  • De acuerdo con la especificación, C # admite el uso de alias en el código

Queda por entender cómo hacer esto en .NET Core.


Desafortunadamente, la versión actual de la documentación habla con modestia sobre las posibilidades de conectar paquetes / tarifas externas. Y la descripción del archivo csproj tampoco arroja luz sobre la posibilidad de crear alias. Sin embargo, por prueba y error, pude descubrir que los alias para ensamblados en .NET Core todavía son compatibles. Y se hacen de la siguiente manera:


 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.1</TargetFramework> </PropertyGroup> <ItemGroup> <Reference Include="Globe1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <HintPath>..\Globe1\bin\Debug\netcoreapp2.1\Globe1.dll</HintPath> <Aliases>Lib1</Aliases> </Reference> <Reference Include="Globe2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"> <HintPath>..\Globe2\bin\Debug\netstandard2.0\Globe2.dll</HintPath> <Aliases>Lib2</Aliases> </Reference> </ItemGroup> </Project> 

Ahora queda por aprender cómo usar estos alias. La palabra clave externa anteriormente mencionada nos ayudará con esto:


Lo siguiente está escrito en la documentación al respecto:


En algunos casos, es posible que deba hacer referencia a dos versiones de ensamblajes con los mismos nombres de tipo totalmente calificados. Por ejemplo, debe usar dos o más versiones de un ensamblaje en una aplicación. Usando un alias de ensamblaje externo, puede incluir espacios de nombres para cada ensamblaje en un contenedor dentro de los espacios de nombres de nivel raíz nombrados por este alias, lo que le permite usarlos en un solo archivo.
...
Cada declaración de un alias externo introduce un espacio de nombres de nivel raíz adicional que coincide con el espacio de nombres global (pero no está dentro de él). Por lo tanto, las referencias a los tipos de cada ensamblaje sin ambigüedad se pueden crear utilizando su nombre completo, cuya raíz es el alias del espacio de nombres correspondiente.

La verdad aquí no es olvidar que extern también se usa en C # para declarar un método con una implementación externa a partir de código no administrado. En este caso, extern se usa generalmente con el atributo DllImport. Puede leer más sobre esto en la sección correspondiente de la documentación .


Entonces, intentemos usar nuestros alias:


 extern alias Lib1; extern alias Lib2; using System; namespace Owl { ... public class SuperOwl { private Lib1::Space.Globe _firstGlobe; private Lib2::Space.Globe _secondGlobe; public void IntegrateGlobe(Lib1::Space.Globe globe) => _firstGlobe = globe; public void IntegrateGlobe(Lib2::Space.Globe globe) => _secondGlobe = globe; ... } } 

Este código ya funciona. Y funciona correctamente. Pero aun así quiero hacerlo un poco más elegante. Esto se puede hacer de una manera muy simple:


 extern alias Lib1; extern alias Lib2; using System; using SpaceOne=Lib1::Space; using SpaceTwo=Lib2::Space; 

Ahora puede usar la sintaxis habitual y obvia:


 var globe1 = new SpaceOne.Globe() var globe2 = new SpaceTwo.Globe() 

Prueba


Probemos nuestro búho:



Como puede ver, el código funcionó bien y sin errores. ¡La integración de búhos y globos ha sido completada con éxito!


→ El código de muestra está disponible en GitHub

Source: https://habr.com/ru/post/es421851/


All Articles